From 9c4c3b62c597748f30a7f2becbda85ffb2fe7c44 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Wed, 14 May 2025 03:35:19 +0800 Subject: [PATCH 01/38] feat: first batch of changes definitely doesn't work yet, just felt like there was way too much changes not to commit already --- discord/abc.py | 4 +- discord/app/cache.py | 306 ++++++++++++++++++++++++++++++ discord/app/events.py | 98 ++++++++++ discord/{ => app}/state.py | 183 +++++++----------- discord/appinfo.py | 2 +- discord/audit_logs.py | 2 +- discord/automod.py | 2 +- discord/channel.py | 30 ++- discord/client.py | 16 +- discord/commands/context.py | 2 +- discord/emoji.py | 4 +- discord/ext/commands/context.py | 2 +- discord/ext/commands/converter.py | 3 +- discord/guild.py | 23 ++- discord/interactions.py | 22 +-- discord/invite.py | 2 +- discord/member.py | 2 +- discord/message.py | 11 +- discord/monetization.py | 7 +- discord/partial_emoji.py | 2 +- discord/raw_models.py | 2 +- discord/role.py | 2 +- discord/scheduled_events.py | 2 +- discord/shard.py | 3 +- discord/stage_instance.py | 2 +- discord/sticker.py | 2 +- discord/team.py | 2 +- discord/template.py | 2 +- discord/threads.py | 2 +- discord/ui/modal.py | 2 +- discord/ui/view.py | 2 +- discord/user.py | 5 +- discord/voice_client.py | 2 +- discord/webhook/async_.py | 7 +- discord/widget.py | 2 +- 35 files changed, 567 insertions(+), 195 deletions(-) create mode 100644 discord/app/cache.py create mode 100644 discord/app/events.py rename discord/{ => app}/state.py (93%) diff --git a/discord/abc.py b/discord/abc.py index af7d5df8f5..0b56f96d47 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -89,7 +89,7 @@ from .member import Member from .message import Message, MessageReference, PartialMessage from .poll import Poll - from .state import ConnectionState + from .app.state import ConnectionState from .threads import Thread from .types.channel import Channel as ChannelPayload from .types.channel import GuildChannel as GuildChannelPayload @@ -1669,7 +1669,7 @@ async def send( ret = state.create_message(channel=channel, data=data) if view: - state.store_view(view, ret.id) + await state.store_view(view, ret.id) view.message = ret if delete_after is not None: diff --git a/discord/app/cache.py b/discord/app/cache.py new file mode 100644 index 0000000000..eca85ba5d7 --- /dev/null +++ b/discord/app/cache.py @@ -0,0 +1,306 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 collections import OrderedDict, deque +from typing import Deque, Protocol + +from discord.app.state import ConnectionState +from discord.message import Message + +from ..abc import PrivateChannel +from ..channel import DMChannel +from ..emoji import AppEmoji, GuildEmoji +from ..guild import Guild +from ..poll import Poll +from ..sticker import GuildSticker, Sticker +from ..ui.modal import Modal, ModalStore +from ..ui.view import View, ViewStore +from ..user import User +from ..types.user import User as UserPayload +from ..types.emoji import Emoji as EmojiPayload +from ..types.sticker import GuildSticker as GuildStickerPayload +from ..types.channel import DMChannel as DMChannelPayload + +class Cache(Protocol): + # users + async def get_all_users(self) -> list[User]: + ... + + async def store_user(self, payload: UserPayload) -> User: + ... + + async def delete_user(self, user_id: int) -> None: + ... + + async def get_user(self, user_id: int) -> User | None: + ... + + # stickers + + async def get_all_stickers(self) -> list[GuildSticker]: + ... + + async def get_sticker(self, sticker_id: int) -> GuildSticker: + ... + + async def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: + ... + + # interactions + + async def store_view(self, view: View, message_id: int | None) -> None: + ... + + async def delete_view_on(self, message_id: int) -> None: + ... + + async def get_all_views(self) -> list[View]: + ... + + async def store_modal(self, modal: Modal) -> None: + ... + + async def get_all_modals(self) -> list[Modal]: + ... + + # guilds + + async def get_all_guilds(self) -> list[Guild]: + ... + + async def get_guild(self, id: int) -> Guild: + ... + + async def add_guild(self, guild: Guild) -> None: + ... + + async def delete_guild(self, guild: Guild) -> None: + ... + + # emojis + + async def store_guild_emoji(self, guild: Guild, data: EmojiPayload) -> GuildEmoji: + ... + + async def store_app_emoji( + self, application_id: int, data: EmojiPayload + ) -> AppEmoji: + ... + + async def get_all_emojis(self) -> list[GuildEmoji | AppEmoji]: + ... + + async def get_emoji(self, emoji_id: int | None) -> GuildEmoji | AppEmoji | None: + ... + + async def delete_emoji(self, emoji: GuildEmoji | AppEmoji) -> None: + ... + + # polls + + async def get_all_polls(self) -> list[Poll]: + ... + + async def get_poll(self, message_id: int) -> Poll: + ... + + async def store_poll(self, poll: Poll, message_id: int) -> None: + ... + + # private channels + + async def get_private_channels(self) -> list[PrivateChannel]: + ... + + async def get_private_channel(self, channel_id: int) -> PrivateChannel: + ... + + async def store_private_channel(self, channel: PrivateChannel, channel_id: int) -> None: + ... + + # dm channels + + async def get_dm_channels(self) -> list[DMChannel]: + ... + + async def get_dm_channel(self, channel_id: int) -> DMChannel: + ... + + async def store_dm_channel(self, channel: DMChannelPayload, channel_id: int) -> DMChannel: + ... + + def clear(self, views: bool = True) -> None: + ... + +class MemoryCache(Cache): + def __init__(self, max_messages: int | None = None, *, state: ConnectionState): + self._state = state + self.max_messages = max_messages + self.clear() + + def clear(self, views: bool = True) -> None: + self._users: dict[int, User] = {} + self._guilds: dict[int, Guild] = {} + self._polls: dict[int, Poll] = {} + self._stickers: dict[int, list[GuildSticker]] = {} + if views: + self._views: dict[str, View] = {} + self._modals: dict[str, Modal] = {} + self._messages: Deque[Message] = deque(maxlen=self.max_messages) + + self._emojis = dict[str, GuildEmoji | AppEmoji] = {} + + self._private_channels: OrderedDict[int, PrivateChannel] = OrderedDict() + self._private_channels_by_user: dict[int, DMChannel] = {} + + # users + async def get_all_users(self) -> list[User]: + return list(self._users.values()) + + async def store_user(self, payload: UserPayload) -> User: + user_id = int(payload["id"]) + try: + return self._users[user_id] + except KeyError: + user = User(state=self, data=payload) + if user.discriminator != "0000": + self._users[user_id] = user + user._stored = True + return user + + async def delete_user(self, user_id: int) -> None: + self._users.pop(user_id, None) + + async def get_user(self, user_id: int) -> User: + return self._users.get(user_id) + + # stickers + + async def get_all_stickers(self) -> list[GuildSticker]: + return list(self._stickers.values()) + + async def get_sticker(self, sticker_id: int) -> GuildSticker: + return self._stickers.get(sticker_id) + + async def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: + sticker = GuildSticker(state=self._state, data=data) + try: + self._stickers[guild.id].append(sticker) + except KeyError: + self._stickers[guild.id] = sticker + return sticker + + # interactions + + async def delete_view_on(self, message_id: int) -> View | None: + return self._views.pop(message_id, None) + + async def store_view(self, view: View, message_id: int) -> None: + self._views[message_id or view.id] = view + + async def get_all_views(self) -> list[View]: + return list(self._views.values()) + + async def store_modal(self, modal: Modal) -> None: + self._modals[modal.custom_id] = modal + + async def get_all_modals(self) -> list[Modal]: + return list(self._modals.values()) + + # guilds + + async def get_all_guilds(self) -> list[Guild]: + return list(self._guilds.values()) + + async def get_guild(self, id: int) -> Guild | None: + return self._guilds.get(id) + + async def add_guild(self, guild: Guild) -> None: + self._guilds[guild.id] = guild + + async def delete_guild(self, guild: Guild) -> None: + self._guilds.pop(guild.id, None) + + # emojis + + async def store_guild_emoji(self, guild: Guild, data: EmojiPayload) -> GuildEmoji: + emoji = GuildEmoji(guild=guild, state=self._state, data=data) + try: + self._emojis[guild.id].append(emoji) + except KeyError: + self._emojis[guild.id] = [emoji] + return emoji + + async def store_app_emoji( + self, application_id: int, data: EmojiPayload + ) -> AppEmoji: + emoji = AppEmoji(application_id=application_id, state=self._state, data=data) + try: + self._emojis[application_id].append(emoji) + except KeyError: + self._emojis[application_id] = [emoji] + return emoji + + async def get_all_emojis(self) -> list[GuildEmoji | AppEmoji]: + return list(self._emojis.values()) + + async def get_emoji(self, emoji_id: int | None) -> GuildEmoji | AppEmoji | None: + return self._emojis.get(emoji_id) + + async def delete_emoji(self, emoji: GuildEmoji | AppEmoji) -> None: + if isinstance(emoji, AppEmoji): + self._emojis[emoji.application_id].remove(emoji) + else: + self._emojis[emoji.guild_id].remove(emoji) + + # polls + + async def get_all_polls(self) -> list[Poll]: + return list(self._polls.values()) + + async def get_poll(self, message_id: int) -> Poll | None: + return self._polls.get(message_id) + + async def store_poll(self, poll: Poll, message_id: int) -> None: + self._polls[message_id] = poll + + # private channels + + async def get_private_channels(self) -> list[PrivateChannel]: + return list(self._private_channels.values()) + + async def get_private_channel(self, channel_id: int) -> PrivateChannel | None: + return self._private_channels.get(channel_id) + + async def store_private_channel(self, channel: PrivateChannel) -> None: + channel_id = channel.id + self._private_channels[channel_id] = channel + + if len(self._private_channels) > 128: + _, to_remove = self._private_channels.popitem(last=False) + if isinstance(to_remove, DMChannel) and to_remove.recipient: + self._private_channels_by_user.pop(to_remove.recipient.id, None) + + if isinstance(channel, DMChannel) and channel.recipient: + self._private_channels_by_user[channel.recipient.id] = channel diff --git a/discord/app/events.py b/discord/app/events.py new file mode 100644 index 0000000000..c452aecdaf --- /dev/null +++ b/discord/app/events.py @@ -0,0 +1,98 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 asyncio import Future +import asyncio +from typing import Any, Callable, Protocol, Self, Type, TypeVar + +from .state import ConnectionState + +T = TypeVar('T') + + +class Event(Protocol): + __event_name__: str + + @classmethod + async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self: + ... + + +class EventEmitter: + def __init__(self, state: ConnectionState) -> None: + self._listeners: dict[Type[Event], list[Callable]] = {} + self._events: dict[str, list[Type[Event]]] + self._wait_fors: dict[Type[Event], list[Future]] = {} + self._state = state + + def add_event(self, event: Type[Event]) -> None: + try: + self._events[event.__event_name__].append(event) + except KeyError: + self._events[event.__event_name__] = [event] + + def remove_event(self, event: Type[Event]) -> list[Type[Event]] | None: + return self._events.pop(event.__event_name__, None) + + def add_listener(self, event: Type[Event], listener: Callable) -> None: + try: + self._listeners[event].append(listener) + except KeyError: + self.add_event(event) + self._listener[event] = [listener] + + def remove_listener(self, event: Type[Event], listener: Callable) -> None: + self._listeners[event].remove(listener) + + def add_wait_for(self, event: Type[T]) -> Future[T]: + fut = Future() + + try: + self._wait_fors[event].append(fut) + except KeyError: + self._wait_fors[event] = [fut] + + return fut + + def remove_wait_for(self, event: Type[Event], fut: Future) -> None: + self._wait_fors[event].remove(fut) + + async def publish(self, event_str: str, data: dict[str, Any]) -> None: + items = list(self.events.items()) + + for event, funcs in items: + if event._name == event_str: + eve = event() + + await eve.__load__(data) + + for func in funcs: + asyncio.create_task(func(eve)) + + wait_fors = self.wait_fors.get(event) + + if wait_fors is not None: + for wait_for in wait_fors: + wait_for.set_result(eve) + self.wait_fors.pop(event) diff --git a/discord/state.py b/discord/app/state.py similarity index 93% rename from discord/state.py rename to discord/app/state.py index 52a9cc0989..d8b9c33adf 100644 --- a/discord/state.py +++ b/discord/app/state.py @@ -43,52 +43,54 @@ Union, ) -from . import utils -from .activity import BaseActivity -from .audit_logs import AuditLogEntry -from .automod import AutoModRule -from .channel import * -from .channel import _channel_factory -from .emoji import AppEmoji, GuildEmoji -from .enums import ChannelType, InteractionType, ScheduledEventStatus, Status, try_enum -from .flags import ApplicationFlags, Intents, MemberCacheFlags -from .guild import Guild -from .integrations import _integration_factory -from .interactions import Interaction -from .invite import Invite -from .member import Member -from .mentions import AllowedMentions -from .message import Message -from .monetization import Entitlement, Subscription -from .object import Object -from .partial_emoji import PartialEmoji -from .poll import Poll, PollAnswerCount -from .raw_models import * -from .role import Role -from .scheduled_events import ScheduledEvent -from .stage_instance import StageInstance -from .sticker import GuildSticker -from .threads import Thread, ThreadMember -from .ui.modal import Modal, ModalStore -from .ui.view import View, ViewStore -from .user import ClientUser, User +from .cache import Cache + +from .. import utils +from ..activity import BaseActivity +from ..audit_logs import AuditLogEntry +from ..automod import AutoModRule +from ..channel import * +from ..channel import _channel_factory +from ..emoji import AppEmoji, GuildEmoji +from ..enums import ChannelType, InteractionType, ScheduledEventStatus, Status, try_enum +from ..flags import ApplicationFlags, Intents, MemberCacheFlags +from ..guild import Guild +from ..integrations import _integration_factory +from ..interactions import Interaction +from ..invite import Invite +from ..member import Member +from ..mentions import AllowedMentions +from ..message import Message +from ..monetization import Entitlement, Subscription +from ..object import Object +from ..partial_emoji import PartialEmoji +from ..poll import Poll, PollAnswerCount +from ..raw_models import * +from ..role import Role +from ..scheduled_events import ScheduledEvent +from ..stage_instance import StageInstance +from ..sticker import GuildSticker +from ..threads import Thread, ThreadMember +from ..ui.modal import Modal, ModalStore +from ..ui.view import View, ViewStore +from ..user import ClientUser, User if TYPE_CHECKING: - from .abc import PrivateChannel - from .client import Client - from .gateway import DiscordWebSocket - from .guild import GuildChannel, VocalGuildChannel - from .http import HTTPClient - from .message import MessageableChannel - from .types.activity import Activity as ActivityPayload - from .types.channel import DMChannel as DMChannelPayload - from .types.emoji import Emoji as EmojiPayload - from .types.guild import Guild as GuildPayload - from .types.message import Message as MessagePayload - from .types.poll import Poll as PollPayload - from .types.sticker import GuildSticker as GuildStickerPayload - from .types.user import User as UserPayload - from .voice_client import VoiceClient + from ..abc import PrivateChannel + from ..client import Client + from ..gateway import DiscordWebSocket + from ..guild import GuildChannel, VocalGuildChannel + from ..http import HTTPClient + from ..message import MessageableChannel + from ..types.activity import Activity as ActivityPayload + from ..types.channel import DMChannel as DMChannelPayload + from ..types.emoji import Emoji as EmojiPayload + from ..types.guild import Guild as GuildPayload + from ..types.message import Message as MessagePayload + from ..types.poll import Poll as PollPayload + from ..types.sticker import GuildSticker as GuildStickerPayload + from ..types.user import User as UserPayload + from ..voice_client import VoiceClient T = TypeVar("T") CS = TypeVar("CS", bound="ConnectionState") @@ -162,7 +164,7 @@ class ConnectionState: def __init__( self, *, - dispatch: Callable, + cache: Cache, handlers: dict[str, Callable], hooks: dict[str, Callable], http: HTTPClient, @@ -175,7 +177,6 @@ def __init__( if self.max_messages is not None and self.max_messages <= 0: self.max_messages = 1000 - self.dispatch: Callable = dispatch self.handlers: dict[str, Callable] = handlers self.hooks: dict[str, Callable] = hooks self.shard_count: int | None = None @@ -258,41 +259,13 @@ def __init__( if attr.startswith("parse_"): parsers[attr[6:].upper()] = func - self.clear() + self.cache: Cache = self.cache def clear(self, *, views: bool = True) -> None: self.user: ClientUser | None = None - # Originally, this code used WeakValueDictionary to maintain references to the - # global user mapping. - - # However, profiling showed that this came with two cons: - - # 1. The __weakref__ slot caused a non-trivial increase in memory - # 2. The performance of the mapping caused store_user to be a bottleneck. - - # Since this is undesirable, a mapping is now used instead with stored - # references now using a regular dictionary with eviction being done - # using __del__. Testing this for memory leaks led to no discernible leaks, - # though more testing will have to be done. - self._users: dict[int, User] = {} - self._emojis: dict[int, (GuildEmoji, AppEmoji)] = {} - self._stickers: dict[int, GuildSticker] = {} - self._guilds: dict[int, Guild] = {} - self._polls: dict[int, Poll] = {} - if views: - self._view_store: ViewStore = ViewStore(self) - self._modal_store: ModalStore = ModalStore(self) + self.cache.clear() self._voice_clients: dict[int, VoiceClient] = {} - # LRU of max size 128 - self._private_channels: OrderedDict[int, PrivateChannel] = OrderedDict() - # extra dict to look up private channels by user id - self._private_channels_by_user: dict[int, DMChannel] = {} - if self.max_messages is not None: - self._messages: Deque[Message] | None = deque(maxlen=self.max_messages) - else: - self._messages: Deque[Message] | None = None - def process_chunk_requests( self, guild_id: int, nonce: str | None, members: list[Member], complete: bool ) -> None: @@ -352,19 +325,11 @@ def _update_references(self, ws: DiscordWebSocket) -> None: for vc in self.voice_clients: vc.main_ws = ws # type: ignore - def store_user(self, data: UserPayload) -> User: - user_id = int(data["id"]) - try: - return self._users[user_id] - except KeyError: - user = User(state=self, data=data) - if user.discriminator != "0000": - self._users[user_id] = user - user._stored = True - return user + async def store_user(self, data: UserPayload) -> User: + return await self.cache.store_user(data) - def deref_user(self, user_id: int) -> None: - self._users.pop(user_id, None) + async def deref_user(self, user_id: int) -> None: + return await self.cache.delete_user(user_id) def create_user(self, data: UserPayload) -> User: return User(state=self, data=data) @@ -372,39 +337,32 @@ def create_user(self, data: UserPayload) -> User: def deref_user_no_intents(self, user_id: int) -> None: return - def get_user(self, id: int | None) -> User | None: - # the keys of self._users are ints - return self._users.get(id) # type: ignore + async def get_user(self, id: int | None) -> User | None: + return await self.cache.get_user(id) - def store_emoji(self, guild: Guild, data: EmojiPayload) -> GuildEmoji: - # the id will be present here - emoji_id = int(data["id"]) # type: ignore - self._emojis[emoji_id] = emoji = GuildEmoji(guild=guild, state=self, data=data) - return emoji + async def store_emoji(self, guild: Guild, data: EmojiPayload) -> GuildEmoji: + return await self.cache.store_guild_emoji(guild, data) - def maybe_store_app_emoji( + async def maybe_store_app_emoji( self, application_id: int, data: EmojiPayload ) -> AppEmoji: # the id will be present here emoji = AppEmoji(application_id=application_id, state=self, data=data) if self.cache_app_emojis: - emoji_id = int(data["id"]) # type: ignore - self._emojis[emoji_id] = emoji + await self.cache.store_app_emoji(application_id, data) return emoji - def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: - sticker_id = int(data["id"]) - self._stickers[sticker_id] = sticker = GuildSticker(state=self, data=data) - return sticker + async def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: + return await self.cache.store_sticker(guild, data) - def store_view(self, view: View, message_id: int | None = None) -> None: - self._view_store.add_view(view, message_id) + async def store_view(self, view: View, message_id: int | None = None) -> None: + await self.cache.store_view(view, message_id) - def store_modal(self, modal: Modal, message_id: int) -> None: - self._modal_store.add_modal(modal, message_id) + async def store_modal(self, modal: Modal) -> None: + await self.cache.store_modal(modal) - def prevent_view_updates_for(self, message_id: int) -> View | None: - return self._view_store.remove_message_tracking(message_id) + async def prevent_view_updates_for(self, message_id: int) -> View | None: + return await self.cache.delete_view_on(message_id) @property def persistent_views(self) -> Sequence[View]: @@ -500,9 +458,10 @@ def _add_private_channel(self, channel: PrivateChannel) -> None: if isinstance(channel, DMChannel) and channel.recipient: self._private_channels_by_user[channel.recipient.id] = channel - def add_dm_channel(self, data: DMChannelPayload) -> DMChannel: + async def add_dm_channel(self, data: DMChannelPayload) -> DMChannel: # self.user is *always* cached when this is called channel = DMChannel(me=self.user, state=self, data=data) # type: ignore + await channel._load() self._add_private_channel(channel) return channel @@ -606,7 +565,7 @@ async def _delay_ready(self) -> None: if self.cache_app_emojis and self.application_id: data = await self.http.get_all_application_emojis(self.application_id) for e in data.get("items", []): - self.maybe_store_app_emoji(self.application_id, e) + await self.maybe_store_app_emoji(self.application_id, e) try: states = [] while True: @@ -2123,7 +2082,7 @@ async def _delay_ready(self) -> None: if self.cache_app_emojis and self.application_id: data = await self.http.get_all_application_emojis(self.application_id) for e in data.get("items", []): - self.maybe_store_app_emoji(self.application_id, e) + await self.maybe_store_app_emoji(self.application_id, e) # remove the state try: diff --git a/discord/appinfo.py b/discord/appinfo.py index 034b1bb158..e29983576d 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -33,7 +33,7 @@ if TYPE_CHECKING: from .guild import Guild - from .state import ConnectionState + from .app.state import ConnectionState from .types.appinfo import AppInfo as AppInfoPayload from .types.appinfo import AppInstallParams as AppInstallParamsPayload from .types.appinfo import PartialAppInfo as PartialAppInfoPayload diff --git a/discord/audit_logs.py b/discord/audit_logs.py index e2ff277dfe..8327bd149d 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -53,7 +53,7 @@ from .role import Role from .scheduled_events import ScheduledEvent from .stage_instance import StageInstance - from .state import ConnectionState + from .app.state import ConnectionState from .sticker import GuildSticker from .threads import Thread from .types.audit_log import AuditLogChange as AuditLogChangePayload diff --git a/discord/automod.py b/discord/automod.py index 27c9e52f9f..dc2f12904e 100644 --- a/discord/automod.py +++ b/discord/automod.py @@ -52,7 +52,7 @@ from .guild import Guild from .member import Member from .role import Role - from .state import ConnectionState + from .app.state import ConnectionState from .types.automod import AutoModAction as AutoModActionPayload from .types.automod import AutoModActionMetadata as AutoModActionMetadataPayload from .types.automod import AutoModRule as AutoModRulePayload diff --git a/discord/channel.py b/discord/channel.py index df0863cad1..0a0da9f380 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -25,6 +25,7 @@ from __future__ import annotations +import asyncio import datetime from typing import TYPE_CHECKING, Any, Callable, Iterable, Mapping, TypeVar, overload @@ -76,7 +77,7 @@ from .member import Member, VoiceState from .message import EmojiInputType, Message, PartialMessage from .role import Role - from .state import ConnectionState + from .app.state import ConnectionState from .types.channel import CategoryChannel as CategoryChannelPayload from .types.channel import DMChannel as DMChannelPayload from .types.channel import ForumChannel as ForumChannelPayload @@ -1347,7 +1348,7 @@ async def create_thread( ret = Thread(guild=self.guild, state=self._state, data=data) msg = ret.get_partial_message(int(data["last_message_id"])) if view: - state.store_view(view, msg.id) + await state.store_view(view, msg.id) if delete_message_after is not None: await msg.delete(delay=delete_message_after) @@ -3085,11 +3086,17 @@ def __init__( self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload ): self._state: ConnectionState = state + self._recipients = data.get("recipients") self.recipient: User | None = None - if r := data.get("recipients"): - self.recipient = state.store_user(r[0]) self.me: ClientUser = me self.id: int = int(data["id"]) + # there shouldn't be any point in time where a DM channel + # is made without the event loop having started + asyncio.create_task(self._load()) + + async def _load(self) -> None: + if r := self._recipients: + self.recipient = await self._state.store_user(r[0]) async def _get_channel(self): return self @@ -3238,16 +3245,13 @@ def __init__( self, *, me: ClientUser, state: ConnectionState, data: GroupChannelPayload ): self._state: ConnectionState = state + self._data = data self.id: int = int(data["id"]) self.me: ClientUser = me - self._update_group(data) - def _update_group(self, data: GroupChannelPayload) -> None: - self.owner_id: int | None = utils._get_as_snowflake(data, "owner_id") - self._icon: str | None = data.get("icon") - self.name: str | None = data.get("name") + async def _load(self) -> None: self.recipients: list[User] = [ - self._state.store_user(u) for u in data.get("recipients", []) + await self._state.store_user(u) for u in self._data.get("recipients", []) ] self.owner: BaseUser | None @@ -3256,6 +3260,12 @@ def _update_group(self, data: GroupChannelPayload) -> None: else: self.owner = utils.find(lambda u: u.id == self.owner_id, self.recipients) + def _update_group(self) -> None: + self.owner_id: int | None = utils._get_as_snowflake(self._data, "owner_id") + self._icon: str | None = self._data.get("icon") + self.name: str | None = self._data.get("name") + asyncio.create_task(self._load()) + async def _get_channel(self): return self diff --git a/discord/client.py b/discord/client.py index 9d5b0cd83d..2bfae81f5a 100644 --- a/discord/client.py +++ b/discord/client.py @@ -54,7 +54,7 @@ from .monetization import SKU, Entitlement from .object import Object from .stage_instance import StageInstance -from .state import ConnectionState +from .app.state import ConnectionState from .sticker import GuildSticker, StandardSticker, StickerPack, _sticker_factory from .template import Template from .threads import Thread @@ -1010,7 +1010,7 @@ def get_guild(self, id: int, /) -> Guild | None: """ return self._connection._get_guild(id) - def get_user(self, id: int, /) -> User | None: + async def get_user(self, id: int, /) -> User | None: """Returns a user with the given ID. Parameters @@ -1023,7 +1023,7 @@ def get_user(self, id: int, /) -> User | None: Optional[:class:`~discord.User`] The user or ``None`` if not found. """ - return self._connection.get_user(id) + return await self._connection.get_user(id) def get_emoji(self, id: int, /) -> GuildEmoji | AppEmoji | None: """Returns an emoji with the given ID. @@ -1985,7 +1985,7 @@ async def create_dm(self, user: Snowflake) -> DMChannel: data = await state.http.start_private_message(user.id) return state.add_dm_channel(data) - def add_view(self, view: View, *, message_id: int | None = None) -> None: + async def add_view(self, view: View, *, message_id: int | None = None) -> None: """Registers a :class:`~discord.ui.View` for persistent listening. This method should be used for when a view is comprised of components @@ -2020,7 +2020,7 @@ def add_view(self, view: View, *, message_id: int | None = None) -> None: " must have no timeout" ) - self._connection.store_view(view, message_id) + await self._connection.store_view(view, message_id) @property def persistent_views(self) -> Sequence[View]: @@ -2189,7 +2189,7 @@ async def fetch_emojis(self) -> list[AppEmoji]: self.application_id ) return [ - self._connection.maybe_store_app_emoji(self.application_id, d) + await self._connection.maybe_store_app_emoji(self.application_id, d) for d in data["items"] ] @@ -2218,7 +2218,7 @@ async def fetch_emoji(self, emoji_id: int, /) -> AppEmoji: data = await self._connection.http.get_application_emoji( self.application_id, emoji_id ) - return self._connection.maybe_store_app_emoji(self.application_id, data) + return await self._connection.maybe_store_app_emoji(self.application_id, data) async def create_emoji( self, @@ -2255,7 +2255,7 @@ async def create_emoji( data = await self._connection.http.create_application_emoji( self.application_id, name, img ) - return self._connection.maybe_store_app_emoji(self.application_id, data) + return await self._connection.maybe_store_app_emoji(self.application_id, data) async def delete_emoji(self, emoji: Snowflake) -> None: """|coro| diff --git a/discord/commands/context.py b/discord/commands/context.py index 532d8abe2a..db6c750a39 100644 --- a/discord/commands/context.py +++ b/discord/commands/context.py @@ -36,7 +36,7 @@ import discord from .. import Bot - from ..state import ConnectionState + from ..app.state import ConnectionState from ..voice_client import VoiceClient from .core import ApplicationCommand, Option diff --git a/discord/emoji.py b/discord/emoji.py index 9324976f4d..1dc43d2f3f 100644 --- a/discord/emoji.py +++ b/discord/emoji.py @@ -44,7 +44,7 @@ from .abc import Snowflake from .guild import Guild from .role import Role - from .state import ConnectionState + from .app.state import ConnectionState from .types.emoji import Emoji as EmojiPayload @@ -416,4 +416,4 @@ async def edit( data = await self._state.http.edit_application_emoji( self.application_id, self.id, payload=payload ) - return self._state.maybe_store_app_emoji(self.application_id, data) + return await self._state.maybe_store_app_emoji(self.application_id, data) diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 4b85ea8903..58157c26cf 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -39,7 +39,7 @@ from discord.abc import MessageableChannel from discord.guild import Guild from discord.member import Member - from discord.state import ConnectionState + from discord.app.state import ConnectionState from discord.user import ClientUser, User from discord.voice_client import VoiceProtocol diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index e60ac89b34..8bd29bec93 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -293,7 +293,7 @@ async def convert(self, ctx: Context, argument: str) -> discord.User: if match is not None: user_id = int(match.group(1)) - result = ctx.bot.get_user(user_id) + result = await ctx.bot.get_user(user_id) if ctx.message is not None and result is None: result = _utils_get(ctx.message.mentions, id=user_id) if result is None: @@ -971,6 +971,7 @@ def resolve_role(id: int) -> str: else: def resolve_member(id: int) -> str: + # TODO: how tf to fix this??? m = ( None if msg is None else _utils_get(msg.mentions, id=id) ) or ctx.bot.get_user(id) diff --git a/discord/guild.py b/discord/guild.py index 4832e02087..db234777a9 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -25,6 +25,7 @@ from __future__ import annotations +import asyncio import copy import unicodedata from typing import ( @@ -104,7 +105,7 @@ VoiceChannel, ) from .permissions import Permissions - from .state import ConnectionState + from .app.state import ConnectionState from .template import Template from .types.guild import Ban as BanPayload from .types.guild import Guild as GuildPayload @@ -308,7 +309,7 @@ def __init__(self, *, data: GuildPayload, state: ConnectionState): self._voice_states: dict[int, VoiceState] = {} self._threads: dict[int, Thread] = {} self._state: ConnectionState = state - self._from_data(data) + asyncio.create_task(self._from_data(data)) def _add_channel(self, channel: GuildChannel, /) -> None: self._channels[channel.id] = channel @@ -447,7 +448,7 @@ def _remove_role(self, role_id: int, /) -> Role: return role - def _from_data(self, guild: GuildPayload) -> None: + async def _from_data(self, guild: GuildPayload) -> None: member_count = guild.get("member_count") # Either the payload includes member_count, or it hasn't been set yet. # Prevents valid _member_count from suddenly changing to None @@ -476,12 +477,14 @@ def _from_data(self, guild: GuildPayload) -> None: self._roles[role.id] = role self.mfa_level: MFALevel = guild.get("mfa_level") - self.emojis: tuple[GuildEmoji, ...] = 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", [])) - ) + emojis = [] + for emoji in guild.get("emojis", []): + emojis.append(await state.store_emoji(self, emoji)) + self.emojis: tuple[GuildEmoji, ...] = tuple(emojis) + stickers = [] + for sticker in guild.get("stickers", []): + stickers.append(await state.store_sticker(self, sticker)) + self.stickers: tuple[GuildSticker, ...] = tuple(stickers) self.features: list[GuildFeature] = guild.get("features", []) self._splash: str | None = guild.get("splash") self._system_channel_id: int | None = utils._get_as_snowflake( @@ -2764,7 +2767,7 @@ async def create_custom_emoji( data = await self._state.http.create_custom_emoji( self.id, name, img, roles=role_ids, reason=reason ) - return self._state.store_emoji(self, data) + return await self._state.store_emoji(self, data) async def delete_emoji( self, emoji: Snowflake, *, reason: str | None = None diff --git a/discord/interactions.py b/discord/interactions.py index 0b56e95a5e..57888b9f3b 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -79,7 +79,7 @@ from .embeds import Embed from .mentions import AllowedMentions from .poll import Poll - from .state import ConnectionState + from .app.state import ConnectionState from .threads import Thread from .types.interactions import Interaction as InteractionPayload from .types.interactions import InteractionData @@ -571,7 +571,7 @@ async def edit_original_response( message = InteractionMessage(state=state, channel=self.channel, data=data) # type: ignore if view and not view.is_finished(): view.message = message - self._state.store_view(view, message.id) + await self._state.store_view(view, message.id) if delete_after is not None: await self.delete_original_response(delay=delete_after) @@ -1123,7 +1123,7 @@ async def edit_message( payload["attachments"] = [a.to_dict() for a in attachments] if view is not MISSING: - state.prevent_view_updates_for(message_id) + await state.prevent_view_updates_for(message_id) payload["components"] = [] if view is None else view.to_components() if file is not MISSING and files is not MISSING: @@ -1190,7 +1190,7 @@ async def edit_message( if view and not view.is_finished(): view.message = msg - state.store_view(view, message_id) + await state.store_view(view, message_id) self._responded = True if delete_after is not None: @@ -1279,7 +1279,7 @@ async def send_modal(self, modal: Modal) -> Interaction: ) ) self._responded = True - self._parent._state.store_modal(modal, self._parent.user.id) + await self._parent._state.store_modal(modal) return self._parent @utils.deprecated("a button with type ButtonType.premium", "2.6") @@ -1622,7 +1622,7 @@ class AuthorizingIntegrationOwners: from the user in the bot's DMs. """ - __slots__ = ("user_id", "guild_id", "_state", "_cs_user", "_cs_guild") + __slots__ = ("user_id", "guild_id", "_state") def __init__(self, data: dict[str, Any], state: ConnectionState): self._state = state @@ -1645,20 +1645,18 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) - @utils.cached_slot_property("_cs_user") - def user(self) -> User | None: + async def get_user(self) -> User | None: """Optional[:class:`User`]: The user that authorized the integration. Returns ``None`` if the user is not in cache, or if :attr:`user_id` is ``None``. """ if not self.user_id: return None - return self._state.get_user(self.user_id) + return await self._state.get_user(self.user_id) - @utils.cached_slot_property("_cs_guild") - def guild(self) -> Guild | None: + async def get_guild(self) -> Guild | None: """Optional[:class:`Guild`]: The guild that authorized the integration. Returns ``None`` if the guild is not in cache, or if :attr:`guild_id` is ``0`` or ``None``. """ if not self.guild_id: return None - return self._state._get_guild(self.guild_id) + return await self._state._get_guild(self.guild_id) diff --git a/discord/invite.py b/discord/invite.py index 12d1d0f28a..67f03199f6 100644 --- a/discord/invite.py +++ b/discord/invite.py @@ -44,7 +44,7 @@ from .abc import GuildChannel from .guild import Guild from .scheduled_events import ScheduledEvent - from .state import ConnectionState + from .app.state import ConnectionState from .types.channel import PartialChannel as InviteChannelPayload from .types.invite import GatewayInvite as GatewayInvitePayload from .types.invite import Invite as InvitePayload diff --git a/discord/member.py b/discord/member.py index 0e0de392f6..8acea3d0d7 100644 --- a/discord/member.py +++ b/discord/member.py @@ -57,7 +57,7 @@ from .guild import Guild from .message import Message from .role import Role - from .state import ConnectionState + from .app.state import ConnectionState from .types.activity import PartialPresenceUpdate from .types.member import Member as MemberPayload from .types.member import MemberWithUser as MemberWithUserPayload diff --git a/discord/message.py b/discord/message.py index 29be44769c..60575d4a90 100644 --- a/discord/message.py +++ b/discord/message.py @@ -73,7 +73,7 @@ from .interactions import MessageInteraction from .mentions import AllowedMentions from .role import Role - from .state import ConnectionState + from .app.state import ConnectionState from .types.components import Component as ComponentPayload from .types.embed import Embed as EmbedPayload from .types.member import Member as MemberPayload @@ -618,14 +618,13 @@ def __init__(self, state: ConnectionState, data: MessageCallPayload): data["ended_timestamp"] ) - @property - def participants(self) -> list[User | Object]: + async def get_participants(self) -> list[User | Object]: """A list of :class:`User` that participated in this call. If a user is not found in the client's cache, then it will be returned as an :class:`Object`. """ - return [self._state.get_user(int(i)) or Object(i) for i in self._participants] + return [await self._state.get_user(int(i)) or Object(i) for i in self._participants] @property def ended_at(self) -> datetime.datetime | None: @@ -1638,7 +1637,7 @@ async def edit( if view and not view.is_finished(): view.message = message - self._state.store_view(view, self.id) + await self._state.store_view(view, self.id) if delete_after is not None: await self.delete(delay=delete_after) @@ -2233,7 +2232,7 @@ async def edit(self, **fields: Any) -> Message | None: msg = self._state.create_message(channel=self.channel, data=data) # type: ignore if view and not view.is_finished(): view.message = msg - self._state.store_view(view, self.id) + await self._state.store_view(view, self.id) return msg async def end_poll(self) -> Message: diff --git a/discord/monetization.py b/discord/monetization.py index 9a4dc9f05c..390dbe2e25 100644 --- a/discord/monetization.py +++ b/discord/monetization.py @@ -37,7 +37,7 @@ from datetime import datetime from .abc import Snowflake, SnowflakeTime - from .state import ConnectionState + from .app.state import ConnectionState from .types.monetization import SKU as SKUPayload from .types.monetization import Entitlement as EntitlementPayload from .types.monetization import Subscription as SubscriptionPayload @@ -344,7 +344,6 @@ def __repr__(self) -> str: def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) and other.id == self.id - @property - def user(self): + async def get_user(self): """Optional[:class:`User`]: The user that owns this subscription.""" - return self._state.get_user(self.user_id) + return await self._state.get_user(self.user_id) diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index 89009cc741..efad61501a 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -37,7 +37,7 @@ if TYPE_CHECKING: from datetime import datetime - from .state import ConnectionState + from .app.state import ConnectionState from .types.message import PartialEmoji as PartialEmojiPayload diff --git a/discord/raw_models.py b/discord/raw_models.py index 73da688b7f..2d5d90ac05 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -37,7 +37,7 @@ from .member import Member from .message import Message from .partial_emoji import PartialEmoji - from .state import ConnectionState + from .app.state import ConnectionState from .threads import Thread from .types.raw_models import ( AuditLogEntryEvent, diff --git a/discord/role.py b/discord/role.py index 717df727bb..a27241ed1f 100644 --- a/discord/role.py +++ b/discord/role.py @@ -46,7 +46,7 @@ from .guild import Guild from .member import Member - from .state import ConnectionState + from .app.state import ConnectionState from .types.guild import RolePositionUpdate from .types.role import Role as RolePayload from .types.role import RoleTags as RoleTagPayload diff --git a/discord/scheduled_events.py b/discord/scheduled_events.py index 44d3fc9b0b..a6be44e895 100644 --- a/discord/scheduled_events.py +++ b/discord/scheduled_events.py @@ -51,7 +51,7 @@ from .guild import Guild from .iterators import AsyncIterator from .member import Member - from .state import ConnectionState + from .app.state import ConnectionState from .types.channel import StageChannel, VoiceChannel from .types.scheduled_events import ScheduledEvent as ScheduledEventPayload diff --git a/discord/shard.py b/discord/shard.py index fd6a08a047..801197754d 100644 --- a/discord/shard.py +++ b/discord/shard.py @@ -32,7 +32,6 @@ import aiohttp from .backoff import ExponentialBackoff -from .client import Client from .enums import Status from .errors import ( ClientException, @@ -42,7 +41,7 @@ PrivilegedIntentsRequired, ) from .gateway import * -from .state import AutoShardedConnectionState +from .app.state import AutoShardedConnectionState if TYPE_CHECKING: from .activity import BaseActivity diff --git a/discord/stage_instance.py b/discord/stage_instance.py index 762dc5ef03..9ab5c562b9 100644 --- a/discord/stage_instance.py +++ b/discord/stage_instance.py @@ -37,7 +37,7 @@ if TYPE_CHECKING: from .channel import StageChannel from .guild import Guild - from .state import ConnectionState + from .app.state import ConnectionState from .types.channel import StageInstance as StageInstancePayload diff --git a/discord/sticker.py b/discord/sticker.py index f70cbd2709..52aaf4e172 100644 --- a/discord/sticker.py +++ b/discord/sticker.py @@ -46,7 +46,7 @@ import datetime from .guild import Guild - from .state import ConnectionState + from .app.state import ConnectionState from .types.sticker import EditGuildSticker from .types.sticker import GuildSticker as GuildStickerPayload from .types.sticker import ListPremiumStickerPacks as ListPremiumStickerPacksPayload diff --git a/discord/team.py b/discord/team.py index 58f638e425..a90e16b358 100644 --- a/discord/team.py +++ b/discord/team.py @@ -33,7 +33,7 @@ from .user import BaseUser if TYPE_CHECKING: - from .state import ConnectionState + from .app.state import ConnectionState from .types.team import Team as TeamPayload from .types.team import TeamMember as TeamMemberPayload diff --git a/discord/template.py b/discord/template.py index 0f40963b2b..57fcf2e1a1 100644 --- a/discord/template.py +++ b/discord/template.py @@ -35,7 +35,7 @@ if TYPE_CHECKING: import datetime - from .state import ConnectionState + from .app.state import ConnectionState from .types.template import Template as TemplatePayload from .user import User diff --git a/discord/threads.py b/discord/threads.py index 5caa4c1f96..d7ab3442a4 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -48,7 +48,7 @@ from .message import Message, PartialMessage from .permissions import Permissions from .role import Role - from .state import ConnectionState + from .app.state import ConnectionState from .types.snowflake import SnowflakeList from .types.threads import Thread as ThreadPayload from .types.threads import ThreadArchiveDuration diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 966e6abe0f..e30333dac3 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: from ..interactions import Interaction - from ..state import ConnectionState + from ..app.state import ConnectionState class Modal: diff --git a/discord/ui/view.py b/discord/ui/view.py index c54cb58f13..e0d9e14541 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -48,7 +48,7 @@ if TYPE_CHECKING: from ..interactions import Interaction, InteractionMessage from ..message import Message - from ..state import ConnectionState + from ..app.state import ConnectionState from ..types.components import Component as ComponentPayload diff --git a/discord/user.py b/discord/user.py index 0de9efe5a5..d33be44f68 100644 --- a/discord/user.py +++ b/discord/user.py @@ -25,6 +25,7 @@ from __future__ import annotations +import asyncio from typing import TYPE_CHECKING, Any, TypeVar import discord.abc @@ -43,7 +44,7 @@ from .channel import DMChannel from .guild import Guild from .message import Message - from .state import ConnectionState + from .app.state import ConnectionState from .types.channel import DMChannel as DMChannelPayload from .types.user import PartialUser as PartialUserPayload from .types.user import User as UserPayload @@ -558,7 +559,7 @@ def __repr__(self) -> str: def __del__(self) -> None: try: if self._stored: - self._state.deref_user(self.id) + asyncio.create_task(self._state.deref_user(self.id)) except Exception: pass diff --git a/discord/voice_client.py b/discord/voice_client.py index cf6e5d0537..80ad07ff29 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -62,7 +62,7 @@ from .client import Client from .guild import Guild from .opus import Encoder - from .state import ConnectionState + from .app.state import ConnectionState from .types.voice import GuildVoiceState as GuildVoiceStatePayload from .types.voice import SupportedModes from .types.voice import VoiceServerUpdate as VoiceServerUpdatePayload diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 61ac6391e3..60357f6968 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -76,7 +76,7 @@ from ..http import Response from ..mentions import AllowedMentions from ..poll import Poll - from ..state import ConnectionState + from ..app.state import ConnectionState from ..types.message import Message as MessagePayload from ..types.webhook import FollowerWebhook as FollowerWebhookPayload from ..types.webhook import Webhook as WebhookPayload @@ -1833,9 +1833,8 @@ async def send( msg = self._create_message(data) if view is not MISSING and not view.is_finished(): - message_id = None if msg is None else msg.id view.message = None if msg is None else msg - self._state.store_view(view, message_id) + await self._state.store_view(view) if delete_after is not None: @@ -2035,7 +2034,7 @@ async def edit_message( message = self._create_message(data) if view and not view.is_finished(): view.message = message - self._state.store_view(view, message_id) + await self._state.store_view(view) return message async def delete_message( diff --git a/discord/widget.py b/discord/widget.py index b6bcf0f013..c60b1041f8 100644 --- a/discord/widget.py +++ b/discord/widget.py @@ -36,7 +36,7 @@ if TYPE_CHECKING: import datetime - from .state import ConnectionState + from .app.state import ConnectionState from .types.widget import Widget as WidgetPayload from .types.widget import WidgetMember as WidgetMemberPayload From 5904f0a869ab43462b0df419eb085612deb5e80c Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Wed, 14 May 2025 05:03:48 +0800 Subject: [PATCH 02/38] fix: await prevent_view_updates_for --- discord/message.py | 4 ++-- discord/webhook/async_.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/message.py b/discord/message.py index 60575d4a90..a49809422e 100644 --- a/discord/message.py +++ b/discord/message.py @@ -1598,7 +1598,7 @@ async def edit( payload["attachments"] = [a.to_dict() for a in attachments] if view is not MISSING: - self._state.prevent_view_updates_for(self.id) + await self._state.prevent_view_updates_for(self.id) payload["components"] = view.to_components() if view else [] if file is not MISSING and files is not MISSING: raise InvalidArgument("cannot pass both file and files parameter to edit()") @@ -2216,7 +2216,7 @@ async def edit(self, **fields: Any) -> Message | None: view = fields.pop("view", MISSING) if view is not MISSING: - self._state.prevent_view_updates_for(self.id) + await self._state.prevent_view_updates_for(self.id) fields["components"] = view.to_components() if view else [] if fields: diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 60357f6968..853cd1683d 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -1995,7 +1995,7 @@ async def edit_message( "This webhook does not have state associated with it" ) - self._state.prevent_view_updates_for(message_id) + await self._state.prevent_view_updates_for(message_id) previous_mentions: AllowedMentions | None = getattr( self._state, "allowed_mentions", None From 0d0ac28f52f7b9e90d26c73c33a1f23f1b66b0fa Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Thu, 15 May 2025 03:05:49 +0800 Subject: [PATCH 03/38] refactor!: bulk of the work large part left is the ConnectionState itself since events haven't been transformed yet --- discord/abc.py | 4 +- discord/app/cache.py | 50 +++++- discord/app/events.py | 5 +- discord/app/state.py | 146 +++++++----------- discord/appinfo.py | 5 +- discord/audit_logs.py | 38 +++-- discord/automod.py | 5 +- discord/channel.py | 54 +++---- discord/client.py | 107 ++++++------- discord/commands/core.py | 2 +- discord/emoji.py | 9 +- discord/ext/commands/converter.py | 26 ++-- discord/flags.py | 12 +- discord/guild.py | 11 +- discord/interactions.py | 31 ++-- discord/invite.py | 8 +- discord/member.py | 4 +- discord/message.py | 77 ++++----- discord/onboarding.py | 2 +- discord/poll.py | 4 +- discord/raw_models.py | 10 +- discord/role.py | 2 +- discord/scheduled_events.py | 4 +- discord/shard.py | 4 +- discord/stage_instance.py | 8 +- discord/sticker.py | 7 +- discord/template.py | 16 +- discord/threads.py | 12 +- discord/user.py | 11 +- discord/webhook/async_.py | 11 +- discord/welcome_screen.py | 2 +- discord/widget.py | 2 +- docs/api/enums.rst | 94 +++++------ docs/api/events.rst | 6 +- docs/build/locales/api/enums.pot | 70 ++++----- docs/faq.rst | 4 +- docs/locales/de/LC_MESSAGES/api/enums.po | 140 ++++++++--------- docs/locales/en/LC_MESSAGES/api/enums.po | 72 ++++----- docs/locales/es/LC_MESSAGES/api/enums.po | 140 ++++++++--------- docs/locales/fr/LC_MESSAGES/api/enums.po | 140 ++++++++--------- docs/locales/hi/LC_MESSAGES/api/enums.po | 140 ++++++++--------- docs/locales/it/LC_MESSAGES/api/enums.po | 140 ++++++++--------- docs/locales/ja/LC_MESSAGES/api/enums.po | 140 ++++++++--------- .../ja/LC_MESSAGES/build/locales/api/enums.po | 140 ++++++++--------- docs/locales/ko/LC_MESSAGES/api/enums.po | 140 ++++++++--------- docs/locales/pt_BR/LC_MESSAGES/api/enums.po | 140 ++++++++--------- docs/locales/ru/LC_MESSAGES/api/enums.po | 140 ++++++++--------- docs/locales/zh_CN/LC_MESSAGES/api/enums.po | 140 ++++++++--------- 48 files changed, 1233 insertions(+), 1242 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 0b56f96d47..5427adceaf 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -350,7 +350,7 @@ def __str__(self) -> str: def _sorting_bucket(self) -> int: raise NotImplementedError - def _update(self, guild: Guild, data: dict[str, Any]) -> None: + async def _update(self, data: dict[str, Any]) -> None: raise NotImplementedError async def _move( @@ -1283,7 +1283,7 @@ async def create_invite( target_user_id=target_user.id if target_user else None, target_application_id=target_application_id, ) - invite = Invite.from_incomplete(data=data, state=self._state) + invite = await Invite.from_incomplete(data=data, state=self._state) if target_event: invite.set_scheduled_event(target_event) return invite diff --git a/discord/app/cache.py b/discord/app/cache.py index eca85ba5d7..05f2eb19b5 100644 --- a/discord/app/cache.py +++ b/discord/app/cache.py @@ -25,10 +25,11 @@ from collections import OrderedDict, deque from typing import Deque, Protocol +from discord import utils from discord.app.state import ConnectionState from discord.message import Message -from ..abc import PrivateChannel +from ..abc import MessageableChannel, PrivateChannel from ..channel import DMChannel from ..emoji import AppEmoji, GuildEmoji from ..guild import Guild @@ -41,6 +42,7 @@ from ..types.emoji import Emoji as EmojiPayload from ..types.sticker import GuildSticker as GuildStickerPayload from ..types.channel import DMChannel as DMChannelPayload +from ..types.message import Message as MessagePayload class Cache(Protocol): # users @@ -67,6 +69,9 @@ async def get_sticker(self, sticker_id: int) -> GuildSticker: async def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: ... + async def delete_sticker(self, sticker_id: int) -> None: + ... + # interactions async def store_view(self, view: View, message_id: int | None) -> None: @@ -89,7 +94,7 @@ async def get_all_modals(self) -> list[Modal]: async def get_all_guilds(self) -> list[Guild]: ... - async def get_guild(self, id: int) -> Guild: + async def get_guild(self, id: int) -> Guild | None: ... async def add_guild(self, guild: Guild) -> None: @@ -136,18 +141,24 @@ async def get_private_channels(self) -> list[PrivateChannel]: async def get_private_channel(self, channel_id: int) -> PrivateChannel: ... - async def store_private_channel(self, channel: PrivateChannel, channel_id: int) -> None: + async def get_private_channel_by_user(self, user_id: int) -> PrivateChannel: ... - # dm channels + async def store_private_channel(self, channel: PrivateChannel) -> None: + ... - async def get_dm_channels(self) -> list[DMChannel]: + # messages + + async def store_message(self, message: MessagePayload, channel: MessageableChannel) -> Message: + ... + + async def delete_message(self, message_id: int) -> None: ... - async def get_dm_channel(self, channel_id: int) -> DMChannel: + async def get_message(self, message_id: int) -> Message | None: ... - async def store_dm_channel(self, channel: DMChannelPayload, channel_id: int) -> DMChannel: + async def get_all_messages(self) -> list[Message]: ... def clear(self, views: bool = True) -> None: @@ -211,6 +222,9 @@ async def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildS self._stickers[guild.id] = sticker return sticker + async def delete_sticker(self, sticker_id: int) -> None: + self._stickers.pop(sticker_id, None) + # interactions async def delete_view_on(self, message_id: int) -> View | None: @@ -291,7 +305,13 @@ async def get_private_channels(self) -> list[PrivateChannel]: return list(self._private_channels.values()) async def get_private_channel(self, channel_id: int) -> PrivateChannel | None: - return self._private_channels.get(channel_id) + try: + channel = self._private_channels[channel_id] + except KeyError: + return None + else: + self._private_channels.move_to_end(channel_id) + return channel async def store_private_channel(self, channel: PrivateChannel) -> None: channel_id = channel.id @@ -304,3 +324,17 @@ async def store_private_channel(self, channel: PrivateChannel) -> None: if isinstance(channel, DMChannel) and channel.recipient: self._private_channels_by_user[channel.recipient.id] = channel + + async def get_private_channel_by_user(self, user_id: int) -> PrivateChannel | None: + return self._private_channels_by_user.get(user_id) + + # messages + + async def store_message(self, message: MessagePayload, channel: MessageableChannel) -> Message: + msg = await Message._from_data(state=self._state, channel=channel, data=message) + + async def get_message(self, message_id: int) -> Message | None: + return utils.find(lambda m: m.id == message_id, reversed(self._messages)) + + async def get_all_messages(self) -> list[Message]: + return list(self._messages) diff --git a/discord/app/events.py b/discord/app/events.py index c452aecdaf..bce0aaa3c3 100644 --- a/discord/app/events.py +++ b/discord/app/events.py @@ -22,16 +22,17 @@ DEALINGS IN THE SOFTWARE. """ +from abc import ABC from asyncio import Future import asyncio -from typing import Any, Callable, Protocol, Self, Type, TypeVar +from typing import Any, Callable, Self, Type, TypeVar from .state import ConnectionState T = TypeVar('T') -class Event(Protocol): +class Event(ABC): __event_name__: str @classmethod diff --git a/discord/app/state.py b/discord/app/state.py index d8b9c33adf..00ec3c3253 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -114,10 +114,12 @@ def __init__( self.buffer: list[Member] = [] self.waiters: list[asyncio.Future[list[Member]]] = [] - def add_members(self, members: list[Member]) -> None: + async def add_members(self, members: list[Member]) -> None: self.buffer.extend(members) if self.cache: guild = self.resolver(self.guild_id) + if inspect.isawaitable(guild): + guild = await guild if guild is None: return @@ -364,125 +366,87 @@ async def store_modal(self, modal: Modal) -> None: async def prevent_view_updates_for(self, message_id: int) -> View | None: return await self.cache.delete_view_on(message_id) - @property - def persistent_views(self) -> Sequence[View]: - return self._view_store.persistent_views + async def get_persistent_views(self) -> Sequence[View]: + views = await self.cache.get_all_views() + persistent_views = { + view.id: view + for view in views + if view.is_persistent() + } + return list(persistent_views.values()) - @property - def guilds(self) -> list[Guild]: - return list(self._guilds.values()) + async def get_guilds(self) -> list[Guild]: + return await self.cache.get_all_guilds() - def _get_guild(self, guild_id: int | None) -> Guild | None: - # the keys of self._guilds are ints - return self._guilds.get(guild_id) # type: ignore + async def _get_guild(self, guild_id: int | None) -> Guild | None: + return await self.cache.get_guild(guild_id) - def _add_guild(self, guild: Guild) -> None: - self._guilds[guild.id] = guild + async def _add_guild(self, guild: Guild) -> None: + await self.cache.add_guild(guild) - def _remove_guild(self, guild: Guild) -> None: - self._guilds.pop(guild.id, None) + async def _remove_guild(self, guild: Guild) -> None: + await self.cache.delete_guild(guild) for emoji in guild.emojis: - self._remove_emoji(emoji) + await self.cache.delete_emoji(emoji) for sticker in guild.stickers: - self._stickers.pop(sticker.id, None) + await self.cache.delete_sticker(sticker.id) del guild - @property - def emojis(self) -> list[GuildEmoji | AppEmoji]: - return list(self._emojis.values()) + async def get_emojis(self) -> list[GuildEmoji | AppEmoji]: + return await self.cache.get_all_emojis() - @property - def stickers(self) -> list[GuildSticker]: - return list(self._stickers.values()) + async def get_stickers(self) -> list[GuildSticker]: + return await self.cache.get_all_stickers() - def get_emoji(self, emoji_id: int | None) -> GuildEmoji | AppEmoji | None: - # the keys of self._emojis are ints - return self._emojis.get(emoji_id) # type: ignore + async def get_emoji(self, emoji_id: int | None) -> GuildEmoji | AppEmoji | None: + return await self.get_emoji(emoji_id) - def _remove_emoji(self, emoji: GuildEmoji | AppEmoji) -> None: - self._emojis.pop(emoji.id, None) + async def _remove_emoji(self, emoji: GuildEmoji | AppEmoji) -> None: + await self.cache.delete_emoji(emoji) - def get_sticker(self, sticker_id: int | None) -> GuildSticker | None: - # the keys of self._stickers are ints - return self._stickers.get(sticker_id) # type: ignore + async def get_sticker(self, sticker_id: int | None) -> GuildSticker | None: + return await self.cache.get_sticker(sticker_id) - @property - def polls(self) -> list[Poll]: - return list(self._polls.values()) + async def get_polls(self) -> list[Poll]: + return await self.cache.get_all_polls() - def store_raw_poll(self, poll: PollPayload, raw): + def create_poll(self, poll: PollPayload, raw) -> Poll: channel = self.get_channel(raw.channel_id) or PartialMessageable( state=self, id=raw.channel_id ) message = channel.get_partial_message(raw.message_id) - p = Poll.from_dict(poll, message) - self._polls[message.id] = p - return p + return Poll.from_dict(poll, message) - def store_poll(self, poll: Poll, message_id: int): - self._polls[message_id] = poll + async def store_poll(self, poll: Poll, message_id: int): + await self.cache.store_poll(poll, message_id) - def get_poll(self, message_id): - return self._polls.get(message_id) - - @property - def private_channels(self) -> list[PrivateChannel]: - return list(self._private_channels.values()) - - def _get_private_channel(self, channel_id: int | None) -> PrivateChannel | None: - try: - # the keys of self._private_channels are ints - value = self._private_channels[channel_id] # type: ignore - except KeyError: - return None - else: - self._private_channels.move_to_end(channel_id) # type: ignore - return value + async def get_poll(self, message_id: int): + return await self.cache.get_poll(message_id) - def _get_private_channel_by_user(self, user_id: int | None) -> DMChannel | None: - # the keys of self._private_channels are ints - return self._private_channels_by_user.get(user_id) # type: ignore + async def get_private_channels(self) -> list[PrivateChannel]: + return await self.cache.get_private_channels() - def _add_private_channel(self, channel: PrivateChannel) -> None: - channel_id = channel.id - self._private_channels[channel_id] = channel + async def _get_private_channel(self, channel_id: int | None) -> PrivateChannel | None: + return await self.cache.get_private_channel(channel_id) - if len(self._private_channels) > 128: - _, to_remove = self._private_channels.popitem(last=False) - if isinstance(to_remove, DMChannel) and to_remove.recipient: - self._private_channels_by_user.pop(to_remove.recipient.id, None) + async def _get_private_channel_by_user(self, user_id: int | None) -> DMChannel | None: + return await self.cache.get_private_channel_by_user(user_id) - if isinstance(channel, DMChannel) and channel.recipient: - self._private_channels_by_user[channel.recipient.id] = channel + async def _add_private_channel(self, channel: PrivateChannel) -> None: + await self.cache.store_private_channel(channel) async def add_dm_channel(self, data: DMChannelPayload) -> DMChannel: # self.user is *always* cached when this is called channel = DMChannel(me=self.user, state=self, data=data) # type: ignore await channel._load() - self._add_private_channel(channel) + await self._add_private_channel(channel) return channel - def _remove_private_channel(self, channel: PrivateChannel) -> None: - self._private_channels.pop(channel.id, None) - if isinstance(channel, DMChannel): - recipient = channel.recipient - if recipient is not None: - self._private_channels_by_user.pop(recipient.id, None) - - def _get_message(self, msg_id: int | None) -> Message | None: - return ( - utils.find(lambda m: m.id == msg_id, reversed(self._messages)) - if self._messages - else None - ) - - def _add_guild_from_data(self, data: GuildPayload) -> Guild: - guild = Guild(data=data, state=self) - self._add_guild(guild) - return guild + async def _get_message(self, msg_id: int | None) -> Message | None: + return await self.cache.get_message(msg_id) def _guild_needs_chunking(self, guild: Guild) -> bool: # If presences are enabled then we get back the old guild.large behaviour @@ -492,12 +456,12 @@ def _guild_needs_chunking(self, guild: Guild) -> bool: and not (self._intents.presences and not guild.large) ) - def _get_guild_channel( + async def _get_guild_channel( self, data: MessagePayload, guild_id: int | None = None ) -> tuple[Channel | Thread, Guild | None]: channel_id = int(data["channel_id"]) try: - guild = self._get_guild(int(guild_id or data["guild_id"])) + guild = await self._get_guild(int(guild_id or data["guild_id"])) except KeyError: channel = DMChannel._from_message(self, channel_id) guild = None @@ -1950,15 +1914,15 @@ def _upgrade_partial_emoji( except KeyError: return emoji - def get_channel(self, id: int | None) -> Channel | Thread | None: + async def get_channel(self, id: int | None) -> Channel | Thread | None: if id is None: return None - pm = self._get_private_channel(id) + pm = await self._get_private_channel(id) if pm is not None: return pm - for guild in self.guilds: + for guild in await self.cache.get_all_guilds(): channel = guild._resolve_channel(id) if channel is not None: return channel diff --git a/discord/appinfo.py b/discord/appinfo.py index e29983576d..c6f6e36c1c 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -252,14 +252,13 @@ def cover_image(self) -> Asset | None: return None return Asset._from_cover_image(self._state, self.id, self._cover_image) - @property - def guild(self) -> Guild | None: + async def get_guild(self) -> Guild | None: """If this application is a game sold on Discord, this field will be the guild to which it has been linked. .. versionadded:: 1.3 """ - return self._state._get_guild(self.guild_id) + return await self._state._get_guild(self.guild_id) @property def summary(self) -> str | None: diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 8327bd149d..9b405400e2 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -25,6 +25,7 @@ from __future__ import annotations +from inspect import isawaitable from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generator, TypeVar from . import enums, utils @@ -110,10 +111,10 @@ def _transform_member_id( return entry._get_member(int(data)) -def _transform_guild_id(entry: AuditLogEntry, data: Snowflake | None) -> Guild | None: +async def _transform_guild_id(entry: AuditLogEntry, data: Snowflake | None) -> Guild | None: if data is None: return None - return entry._state._get_guild(data) + return await entry._state._get_guild(data) def _transform_overwrites( @@ -283,7 +284,13 @@ class AuditLogChanges: "exempt_channels": (None, _transform_channels), } - def __init__( + async def _maybe_await(func: Any) -> Any: + if isawaitable(func): + return await func + else: + return func + + async def _from_data( self, entry: AuditLogEntry, data: list[AuditLogChangePayload], @@ -338,7 +345,7 @@ def __init__( before = None else: if transformer: - before = transformer(entry, before) + before = await self._maybe_await(transformer(entry, before)) if attr == "location" and hasattr(self.before, "location_type"): from .scheduled_events import ScheduledEventLocation @@ -361,7 +368,7 @@ def __init__( after = None else: if transformer: - after = transformer(entry, after) + after = await self._maybe_await(transformer(entry, after)) if attr == "location" and hasattr(self.after, "location_type"): from .scheduled_events import ScheduledEventLocation @@ -607,8 +614,7 @@ def created_at(self) -> datetime.datetime: """Returns the entry's creation time in UTC.""" return utils.snowflake_time(self.id) - @utils.cached_property - def target( + async def get_target( self, ) -> ( Guild @@ -629,17 +635,19 @@ def target( except AttributeError: return Object(id=self._target_id) else: - return converter(self._target_id) + r = converter(self._target_id) + if isawaitable(r): + r = await r + return r @utils.cached_property def category(self) -> enums.AuditLogActionCategory: """The category of the action, if applicable.""" return self.action.category - @utils.cached_property - def changes(self) -> AuditLogChanges: + async def changes(self) -> AuditLogChanges: """The list of changes this entry has.""" - obj = AuditLogChanges(self, self._changes, state=self._state) + obj = AuditLogChanges().from_data(self, self._changes, state=self._state) del self._changes return obj @@ -689,8 +697,8 @@ def _convert_target_invite(self, target_id: int) -> Invite: pass return obj - def _convert_target_emoji(self, target_id: int) -> GuildEmoji | Object: - return self._state.get_emoji(target_id) or Object(id=target_id) + async def _convert_target_emoji(self, target_id: int) -> GuildEmoji | Object: + return (await self._state.get_emoji(target_id)) or Object(id=target_id) def _convert_target_message(self, target_id: int) -> Member | User | None: return self._get_member(target_id) @@ -698,8 +706,8 @@ def _convert_target_message(self, target_id: int) -> Member | User | None: def _convert_target_stage_instance(self, target_id: int) -> StageInstance | Object: return self.guild.get_stage_instance(target_id) or Object(id=target_id) - def _convert_target_sticker(self, target_id: int) -> GuildSticker | Object: - return self._state.get_sticker(target_id) or Object(id=target_id) + async def _convert_target_sticker(self, target_id: int) -> GuildSticker | Object: + return (await self._state.get_sticker(target_id)) or Object(id=target_id) def _convert_target_thread(self, target_id: int) -> Thread | Object: return self.guild.get_thread(target_id) or Object(id=target_id) diff --git a/discord/automod.py b/discord/automod.py index dc2f12904e..60519872fe 100644 --- a/discord/automod.py +++ b/discord/automod.py @@ -416,10 +416,9 @@ def __repr__(self) -> str: def __str__(self) -> str: return self.name - @cached_property - def guild(self) -> Guild | None: + async def get_guild(self) -> Guild | None: """The guild this rule belongs to.""" - return self._state._get_guild(self.guild_id) + return await self._state._get_guild(self.guild_id) @cached_property def creator(self) -> Member | None: diff --git a/discord/channel.py b/discord/channel.py index 0a0da9f380..5103e1a382 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -213,7 +213,7 @@ def __init__( ): self._state: ConnectionState = state self.id: int = int(data["id"]) - self._update(guild, data) + self.guild = guild @property def _repr_attrs(self) -> tuple[str, ...]: @@ -224,11 +224,10 @@ def __repr__(self) -> str: joined = " ".join("%s=%r" % t for t in attrs) return f"<{self.__class__.__name__} {joined}>" - def _update( - self, guild: Guild, data: TextChannelPayload | ForumChannelPayload + async def _update( + self, data: TextChannelPayload | ForumChannelPayload ) -> None: # This data will always exist - self.guild: Guild = guild self.name: str = data["name"] self.category_id: int | None = utils._get_as_snowflake(data, "parent_id") self._type: int = data["type"] @@ -290,8 +289,7 @@ def is_nsfw(self) -> bool: """Checks if the channel is NSFW.""" return self.nsfw - @property - def last_message(self) -> Message | None: + async def get_last_message(self) -> Message | None: """Fetches the last message from this channel in cache. The message might not be valid or point to an existing message. @@ -310,7 +308,7 @@ def last_message(self) -> Message | None: The last message in this channel or ``None`` if not found. """ return ( - self._state._get_message(self.last_message_id) + await self._state._get_message(self.last_message_id) if self.last_message_id else None ) @@ -751,8 +749,8 @@ def __init__( def _repr_attrs(self) -> tuple[str, ...]: return super()._repr_attrs + ("news",) - def _update(self, guild: Guild, data: TextChannelPayload) -> None: - super()._update(guild, data) + async def _update(self, data: TextChannelPayload) -> None: + super()._update(data) async def _get_channel(self) -> TextChannel: return self @@ -1030,8 +1028,8 @@ def __init__( ): super().__init__(state=state, guild=guild, data=data) - def _update(self, guild: Guild, data: ForumChannelPayload) -> None: - super()._update(guild, data) + async def _update(self, data: ForumChannelPayload) -> None: + super()._update(data) self.available_tags: list[ForumTag] = [ ForumTag.from_data(state=self._state, data=tag) for tag in (data.get("available_tags") or []) @@ -1046,7 +1044,7 @@ def _update(self, guild: Guild, data: ForumChannelPayload) -> None: if emoji_name is not None: self.default_reaction_emoji = reaction_emoji_ctx["emoji_name"] else: - self.default_reaction_emoji = self._state.get_emoji( + self.default_reaction_emoji = await self._state.get_emoji( utils._get_as_snowflake(reaction_emoji_ctx, "emoji_id") ) @@ -1573,7 +1571,8 @@ def __init__( ): self._state: ConnectionState = state self.id: int = int(data["id"]) - self._update(guild, data) + self.guild = guild + self._update(data) def _get_voice_client_key(self) -> tuple[int, str]: return self.guild.id, "guild_id" @@ -1581,11 +1580,10 @@ def _get_voice_client_key(self) -> tuple[int, str]: def _get_voice_state_pair(self) -> tuple[int, int]: return self.guild.id, self.id - def _update( - self, guild: Guild, data: VoiceChannelPayload | StageChannelPayload + async def _update( + self, data: VoiceChannelPayload | StageChannelPayload ) -> None: # This data will always exist - self.guild = guild self.name: str = data["name"] self.category_id: int | None = utils._get_as_snowflake(data, "parent_id") @@ -1737,8 +1735,8 @@ def __init__( self.status: str | None = None super().__init__(state=state, guild=guild, data=data) - def _update(self, guild: Guild, data: VoiceChannelPayload): - super()._update(guild, data) + async def _update(self, data: VoiceChannelPayload): + super()._update(data) if data.get("status"): self.status = data.get("status") @@ -1764,8 +1762,7 @@ def is_nsfw(self) -> bool: """Checks if the channel is NSFW.""" return self.nsfw - @property - def last_message(self) -> Message | None: + async def get_last_message(self) -> Message | None: """Fetches the last message from this channel in cache. The message might not be valid or point to an existing message. @@ -1784,7 +1781,7 @@ def last_message(self) -> Message | None: The last message in this channel or ``None`` if not found. """ return ( - self._state._get_message(self.last_message_id) + await self._state._get_message(self.last_message_id) if self.last_message_id else None ) @@ -2255,8 +2252,8 @@ class StageChannel(discord.abc.Messageable, VocalGuildChannel): __slots__ = ("topic",) - def _update(self, guild: Guild, data: StageChannelPayload) -> None: - super()._update(guild, data) + async def _update(self, data: StageChannelPayload) -> None: + super()._update(data) self.topic = data.get("topic") def __repr__(self) -> str: @@ -2314,8 +2311,7 @@ def is_nsfw(self) -> bool: """Checks if the channel is NSFW.""" return self.nsfw - @property - def last_message(self) -> Message | None: + async def get_last_message(self) -> Message | None: """Fetches the last message from this channel in cache. The message might not be valid or point to an existing message. @@ -2334,7 +2330,7 @@ def last_message(self) -> Message | None: The last message in this channel or ``None`` if not found. """ return ( - self._state._get_message(self.last_message_id) + await self._state._get_message(self.last_message_id) if self.last_message_id else None ) @@ -2820,7 +2816,8 @@ def __init__( ): self._state: ConnectionState = state self.id: int = int(data["id"]) - self._update(guild, data) + self.guild = guild + self._update(data) def __repr__(self) -> str: return ( @@ -2828,9 +2825,8 @@ def __repr__(self) -> str: f" id={self.id} name={self.name!r} position={self.position} nsfw={self.nsfw}>" ) - def _update(self, guild: Guild, data: CategoryChannelPayload) -> None: + async def _update(self, data: CategoryChannelPayload) -> None: # This data will always exist - self.guild: Guild = guild self.name: str = data["name"] self.category_id: int | None = utils._get_as_snowflake(data, "parent_id") diff --git a/discord/client.py b/discord/client.py index 2bfae81f5a..3a56fe8333 100644 --- a/discord/client.py +++ b/discord/client.py @@ -31,7 +31,7 @@ import sys import traceback from types import TracebackType -from typing import TYPE_CHECKING, Any, Callable, Coroutine, Generator, Sequence, TypeVar +from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Coroutine, Generator, Sequence, TypeVar import aiohttp @@ -335,62 +335,54 @@ def user(self) -> ClientUser | None: """Represents the connected client. ``None`` if not logged in.""" return self._connection.user - @property - def guilds(self) -> list[Guild]: + async def get_guilds(self) -> list[Guild]: """The guilds that the connected client is a member of.""" - return self._connection.guilds + return await self._connection.get_guilds() - @property - def emojis(self) -> list[GuildEmoji | AppEmoji]: + async def get_emojis(self) -> list[GuildEmoji | AppEmoji]: """The emojis that the connected client has. .. note:: This only includes the application's emojis if `cache_app_emojis` is ``True``. """ - return self._connection.emojis + return await self._connection.get_emojis() - @property - def guild_emojis(self) -> list[GuildEmoji]: + async def get_guild_emojis(self) -> list[GuildEmoji]: """The :class:`~discord.GuildEmoji` that the connected client has.""" - return [e for e in self.emojis if isinstance(e, GuildEmoji)] + return [e for e in await self.get_emojis() if isinstance(e, GuildEmoji)] - @property - def app_emojis(self) -> list[AppEmoji]: + async def get_app_emojis(self) -> list[AppEmoji]: """The :class:`~discord.AppEmoji` that the connected client has. .. note:: This is only available if `cache_app_emojis` is ``True``. """ - return [e for e in self.emojis if isinstance(e, AppEmoji)] + return [e for e in await self.get_emojis() if isinstance(e, AppEmoji)] - @property - def stickers(self) -> list[GuildSticker]: + async def get_stickers(self) -> list[GuildSticker]: """The stickers that the connected client has. .. versionadded:: 2.0 """ - return self._connection.stickers + return await self._connection.get_stickers() - @property - def polls(self) -> list[Poll]: + async def get_polls(self) -> list[Poll]: """The polls that the connected client has. .. versionadded:: 2.6 """ - return self._connection.polls + return await self._connection.get_polls() - @property - def cached_messages(self) -> Sequence[Message]: + async def get_cached_messages(self) -> Sequence[Message]: """Read-only list of messages the connected client has cached. .. versionadded:: 1.1 """ - return utils.SequenceProxy(self._connection._messages or []) + return utils.SequenceProxy(await self._connection.cache.get_all_messages()) - @property - def private_channels(self) -> list[PrivateChannel]: + async def get_private_channels(self) -> list[PrivateChannel]: """The private channels that the connected client is participating on. .. note:: @@ -398,7 +390,7 @@ def private_channels(self) -> list[PrivateChannel]: This returns only up to 128 most recent private channels due to an internal working on how Discord deals with private channels. """ - return self._connection.private_channels + return await self._connection.get_private_channels() @property def voice_clients(self) -> list[VoiceProtocol]: @@ -916,7 +908,7 @@ async def fetch_application(self, application_id: int, /) -> PartialAppInfo: data = await self.http.get_application(application_id) return PartialAppInfo(state=self._connection, data=data) - def get_channel(self, id: int, /) -> GuildChannel | Thread | PrivateChannel | None: + async def get_channel(self, id: int, /) -> GuildChannel | Thread | PrivateChannel | None: """Returns a channel or thread with the given ID. Parameters @@ -929,9 +921,9 @@ def get_channel(self, id: int, /) -> GuildChannel | Thread | PrivateChannel | No Optional[Union[:class:`.abc.GuildChannel`, :class:`.Thread`, :class:`.abc.PrivateChannel`]] The returned channel or ``None`` if not found. """ - return self._connection.get_channel(id) + return await self._connection.get_channel(id) - def get_message(self, id: int, /) -> Message | None: + async def get_message(self, id: int, /) -> Message | None: """Returns a message the given ID. This is useful if you have a message_id but don't want to do an API call @@ -947,7 +939,7 @@ def get_message(self, id: int, /) -> Message | None: Optional[:class:`.Message`] The returned message or ``None`` if not found. """ - return self._connection._get_message(id) + return await self._connection._get_message(id) def get_partial_messageable( self, id: int, *, type: ChannelType | None = None @@ -973,7 +965,7 @@ def get_partial_messageable( """ return PartialMessageable(state=self._connection, id=id, type=type) - def get_stage_instance(self, id: int, /) -> StageInstance | None: + async def get_stage_instance(self, id: int, /) -> StageInstance | None: """Returns a stage instance with the given stage channel ID. .. versionadded:: 2.0 @@ -990,12 +982,12 @@ def get_stage_instance(self, id: int, /) -> StageInstance | None: """ from .channel import StageChannel - channel = self._connection.get_channel(id) + channel = await self._connection.get_channel(id) if isinstance(channel, StageChannel): return channel.instance - def get_guild(self, id: int, /) -> Guild | None: + async def get_guild(self, id: int, /) -> Guild | None: """Returns a guild with the given ID. Parameters @@ -1008,7 +1000,7 @@ def get_guild(self, id: int, /) -> Guild | None: Optional[:class:`.Guild`] The guild or ``None`` if not found. """ - return self._connection._get_guild(id) + return await self._connection._get_guild(id) async def get_user(self, id: int, /) -> User | None: """Returns a user with the given ID. @@ -1025,7 +1017,7 @@ async def get_user(self, id: int, /) -> User | None: """ return await self._connection.get_user(id) - def get_emoji(self, id: int, /) -> GuildEmoji | AppEmoji | None: + async def get_emoji(self, id: int, /) -> GuildEmoji | AppEmoji | None: """Returns an emoji with the given ID. Parameters @@ -1038,9 +1030,9 @@ def get_emoji(self, id: int, /) -> GuildEmoji | AppEmoji | None: Optional[:class:`.GuildEmoji` | :class:`.AppEmoji`] The custom emoji or ``None`` if not found. """ - return self._connection.get_emoji(id) + return await self._connection.get_emoji(id) - def get_sticker(self, id: int, /) -> GuildSticker | None: + async def get_sticker(self, id: int, /) -> GuildSticker | None: """Returns a guild sticker with the given ID. .. versionadded:: 2.0 @@ -1055,9 +1047,9 @@ def get_sticker(self, id: int, /) -> GuildSticker | None: Optional[:class:`.GuildSticker`] The sticker or ``None`` if not found. """ - return self._connection.get_sticker(id) + return await self._connection.get_sticker(id) - def get_poll(self, id: int, /) -> Poll | None: + async def get_poll(self, id: int, /) -> Poll | None: """Returns a poll attached to the given message ID. Parameters @@ -1070,14 +1062,14 @@ def get_poll(self, id: int, /) -> Poll | None: Optional[:class:`.Poll`] The poll or ``None`` if not found. """ - return self._connection.get_poll(id) + return await self._connection.get_poll(id) - def get_all_channels(self) -> Generator[GuildChannel]: + async def get_all_channels(self) -> AsyncGenerator[GuildChannel]: """A generator that retrieves every :class:`.abc.GuildChannel` the client can 'access'. This is equivalent to: :: - for guild in client.guilds: + for guild in await client.get_guilds(): for channel in guild.channels: yield channel @@ -1093,15 +1085,16 @@ def get_all_channels(self) -> Generator[GuildChannel]: A channel the client can 'access'. """ - for guild in self.guilds: - yield from guild.channels + for guild in await self.get_guilds(): + for channel in guild.channels: + yield channel - def get_all_members(self) -> Generator[Member]: + async def get_all_members(self) -> AsyncGenerator[Member]: """Returns a generator with every :class:`.Member` the client can see. This is equivalent to: :: - for guild in client.guilds: + for guild in await client.get_guilds(): for member in guild.members: yield member @@ -1110,8 +1103,9 @@ def get_all_members(self) -> Generator[Member]: :class:`.Member` A member the client can see. """ - for guild in self.guilds: - yield from guild.members + for guild in await self.get_guilds(): + for member in guild.members: + yield member async def get_or_fetch_user(self, id: int, /) -> User | None: """|coro| @@ -1447,7 +1441,7 @@ async def change_presence( await self.ws.change_presence(activity=activity, status=status_str) - for guild in self._connection.guilds: + for guild in await self._connection.get_guilds(): me = guild.me if me is None: continue @@ -1550,7 +1544,7 @@ async def fetch_template(self, code: Template | str) -> Template: """ code = utils.resolve_template(code) data = await self.http.get_template(code) - return Template(data=data, state=self._connection) # type: ignore + return await Template.from_data(data=data, state=self._connection) # type: ignore async def fetch_guild(self, guild_id: int, /, *, with_counts=True) -> Guild: """|coro| @@ -1731,7 +1725,7 @@ async def fetch_invite( with_expiration=with_expiration, guild_scheduled_event_id=event_id, ) - return Invite.from_incomplete(state=self._connection, data=data) + return await Invite.from_incomplete(state=self._connection, data=data) async def delete_invite(self, invite: Invite | str) -> None: """|coro| @@ -1978,12 +1972,12 @@ async def create_dm(self, user: Snowflake) -> DMChannel: The channel that was created. """ state = self._connection - found = state._get_private_channel_by_user(user.id) + found = await state._get_private_channel_by_user(user.id) if found: return found data = await state.http.start_private_message(user.id) - return state.add_dm_channel(data) + return await state.add_dm_channel(data) async def add_view(self, view: View, *, message_id: int | None = None) -> None: """Registers a :class:`~discord.ui.View` for persistent listening. @@ -2022,13 +2016,12 @@ async def add_view(self, view: View, *, message_id: int | None = None) -> None: await self._connection.store_view(view, message_id) - @property - def persistent_views(self) -> Sequence[View]: + async def get_persistent_views(self) -> Sequence[View]: """A sequence of persistent views added to the client. .. versionadded:: 2.0 """ - return self._connection.persistent_views + return await self._connection.get_persistent_views() async def fetch_role_connection_metadata_records( self, @@ -2276,5 +2269,5 @@ async def delete_emoji(self, emoji: Snowflake) -> None: await self._connection.http.delete_application_emoji( self.application_id, emoji.id ) - if self._connection.cache_app_emojis and self._connection.get_emoji(emoji.id): - self._connection.remove_emoji(emoji) + if self._connection.cache_app_emojis and await self._connection.get_emoji(emoji.id): + await self._connection._remove_emoji(emoji) diff --git a/discord/commands/core.py b/discord/commands/core.py index bee43341fd..6afac89e8e 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -1896,7 +1896,7 @@ async def _invoke(self, ctx: ApplicationContext): state=ctx.interaction._state, id=int(message["channel_id"]) ) - target = Message(state=ctx.interaction._state, channel=channel, data=message) + target = Message._from_data(state=ctx.interaction._state, channel=channel, data=message) if self.cog is not None: await self.callback(self.cog, ctx, target) diff --git a/discord/emoji.py b/discord/emoji.py index 1dc43d2f3f..8b5a1e29ca 100644 --- a/discord/emoji.py +++ b/discord/emoji.py @@ -189,10 +189,9 @@ def roles(self) -> list[Role]: return [role for role in guild.roles if self._roles.has(role.id)] - @property - def guild(self) -> Guild: + async def get_guild(self) -> Guild: """The guild this emoji belongs to.""" - return self._state._get_guild(self.guild_id) + return await self._state._get_guild(self.guild_id) def is_usable(self) -> bool: """Whether the bot can use this emoji. @@ -377,8 +376,8 @@ async def delete(self) -> None: """ await self._state.http.delete_application_emoji(self.application_id, self.id) - if self._state.cache_app_emojis and self._state.get_emoji(self.id): - self._state._remove_emoji(self) + if self._state.cache_app_emojis and await self._state.get_emoji(self.id): + await self._state._remove_emoji(self) async def edit( self, diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index 8bd29bec93..83d61c0353 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -80,9 +80,9 @@ ) -def _get_from_guilds(bot, getter, argument): +async def _get_from_guilds(bot, getter, argument): result = None - for guild in bot.guilds: + for guild in await bot.get_guilds(): result = getattr(guild, getter)(argument) if result: return result @@ -239,7 +239,7 @@ async def convert(self, ctx: Context, argument: str) -> discord.Member: if guild: result = guild.get_member_named(argument) else: - result = _get_from_guilds(bot, "get_member_named", argument) + result = await _get_from_guilds(bot, "get_member_named", argument) else: user_id = int(match.group(1)) if guild: @@ -247,7 +247,7 @@ async def convert(self, ctx: Context, argument: str) -> discord.Member: if ctx.message is not None and result is None: result = _utils_get(ctx.message.mentions, id=user_id) else: - result = _get_from_guilds(bot, "get_member", user_id) + result = await _get_from_guilds(bot, "get_member", user_id) if result is None: if guild is None: @@ -409,7 +409,7 @@ async def convert(self, ctx: Context, argument: str) -> discord.Message: guild_id, message_id, channel_id = PartialMessageConverter._get_id_matches( ctx, argument ) - message = ctx.bot._connection._get_message(message_id) + message = await ctx.bot._connection._get_message(message_id) if message: return message channel = PartialMessageConverter._resolve_channel(ctx, guild_id, channel_id) @@ -439,12 +439,12 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]): """ async def convert(self, ctx: Context, argument: str) -> discord.abc.GuildChannel: - return self._resolve_channel( + return await self._resolve_channel( ctx, argument, "channels", discord.abc.GuildChannel ) @staticmethod - def _resolve_channel( + async def _resolve_channel( ctx: Context, argument: str, attribute: str, type: type[CT] ) -> CT: bot = ctx.bot @@ -471,7 +471,7 @@ def check(c): if guild: result = guild.get_channel(channel_id) else: - result = _get_from_guilds(bot, "get_channel", channel_id) + result = await _get_from_guilds(bot, "get_channel", channel_id) if not isinstance(result, type): raise ChannelNotFound(argument) @@ -798,7 +798,7 @@ async def convert(self, ctx: Context, argument: str) -> discord.Guild: result = ctx.bot.get_guild(guild_id) if result is None: - result = discord.utils.get(ctx.bot.guilds, name=argument) + result = discord.utils.get(await ctx.bot.get_guilds(), name=argument) if result is None: raise GuildNotFound(argument) @@ -835,12 +835,12 @@ async def convert(self, ctx: Context, argument: str) -> discord.GuildEmoji: result = discord.utils.get(guild.emojis, name=argument) if result is None: - result = discord.utils.get(bot.emojis, name=argument) + result = discord.utils.get(await bot.get_emojis(), name=argument) else: emoji_id = int(match.group(1)) # Try to look up emoji by id. - result = bot.get_emoji(emoji_id) + result = await bot.get_emoji(emoji_id) if result is None: raise EmojiNotFound(argument) @@ -901,12 +901,12 @@ async def convert(self, ctx: Context, argument: str) -> discord.GuildSticker: result = discord.utils.get(guild.stickers, name=argument) if result is None: - result = discord.utils.get(bot.stickers, name=argument) + result = discord.utils.get(await bot.get_stickers(), name=argument) else: sticker_id = int(match.group(1)) # Try to look up sticker by id. - result = bot.get_sticker(sticker_id) + result = await bot.get_sticker(sticker_id) if result is None: raise GuildStickerNotFound(argument) diff --git a/discord/flags.py b/discord/flags.py index 7073a56e35..fdb3814186 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -679,7 +679,7 @@ def guilds(self): This also corresponds to the following attributes and classes in terms of cache: - - :attr:`Client.guilds` + - :attr:`Client.get_guilds` - :class:`Guild` and all its attributes. - :meth:`Client.get_channel` - :meth:`Client.get_all_channels` @@ -773,8 +773,8 @@ def emojis_and_stickers(self): - :class:`GuildSticker` - :meth:`Client.get_emoji` - :meth:`Client.get_sticker` - - :meth:`Client.emojis` - - :meth:`Client.stickers` + - :meth:`Client.get_emojis` + - :meth:`Client.get_stickers` - :attr:`Guild.emojis` - :attr:`Guild.stickers` """ @@ -887,7 +887,7 @@ def messages(self): - :class:`Message` - :attr:`Client.cached_messages` - :meth:`Client.get_message` - - :attr:`Client.polls` + - :meth:`Client.get_polls` - :meth:`Client.get_poll` Note that due to an implicit relationship this also corresponds to the following events: @@ -921,7 +921,7 @@ def guild_messages(self): - :class:`Message` - :attr:`Client.cached_messages` (only for guilds) - :meth:`Client.get_message` (only for guilds) - - :attr:`Client.polls` (only for guilds) + - :meth:`Client.get_polls` (only for guilds) - :meth:`Client.get_poll` (only for guilds) Note that due to an implicit relationship this also corresponds to the following events: @@ -962,7 +962,7 @@ def dm_messages(self): - :class:`Message` - :attr:`Client.cached_messages` (only for DMs) - :meth:`Client.get_message` (only for DMs) - - :attr:`Client.polls` (only for DMs) + - :meth:`Client.get_polls` (only for DMs) - :meth:`Client.get_poll` (only for DMs) Note that due to an implicit relationship this also corresponds to the following events: diff --git a/discord/guild.py b/discord/guild.py index db234777a9..7214f51575 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -1232,6 +1232,7 @@ async def create_text_channel( **options, ) channel = TextChannel(state=self._state, guild=self, data=data) + await channel._update() # temporarily add to the cache self._channels[channel.id] = channel @@ -2324,7 +2325,7 @@ async def templates(self) -> list[Template]: from .template import Template data = await self._state.http.guild_templates(self.id) - return [Template(data=d, state=self._state) for d in data] + return [await Template.from_data(data=d, state=self._state) for d in data] async def webhooks(self) -> list[Webhook]: """|coro| @@ -2452,7 +2453,7 @@ async def create_template( data = await self._state.http.create_template(self.id, payload) - return Template(state=self._state, data=data) + return await Template.from_data(state=self._state, data=data) async def create_integration(self, *, type: str, id: int) -> None: """|coro| @@ -3283,7 +3284,7 @@ async def vanity_invite(self) -> Invite | None: return Invite(state=self._state, data=payload, guild=self, channel=channel) # TODO: use MISSING when async iterators get refactored - def audit_logs( + async def audit_logs( self, *, limit: int | None = 100, @@ -3334,12 +3335,12 @@ def audit_logs( Getting the first 100 entries: :: async for entry in guild.audit_logs(limit=100): - print(f'{entry.user} did {entry.action} to {entry.target}') + print(f'{entry.user} did {entry.action} to {await entry.get_target()}') Getting entries for a specific action: :: async for entry in guild.audit_logs(action=discord.AuditLogAction.ban): - print(f'{entry.user} banned {entry.target}') + print(f'{entry.user} banned {await entry.get_target()}') Getting entries made by a specific user: :: diff --git a/discord/interactions.py b/discord/interactions.py index 57888b9f3b..3cfb89bb7f 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -189,11 +189,13 @@ class Interaction: def __init__(self, *, data: InteractionPayload, state: ConnectionState): self._state: ConnectionState = state + self._data = data self._session: ClientSession = state.http._HTTPClient__session self._original_response: InteractionMessage | None = None - self._from_data(data) - def _from_data(self, data: InteractionPayload): + async def load_data(self): + data = self._data + self.id: int = int(data["id"]) self.type: InteractionType = try_enum(InteractionType, data["type"]) self.data: InteractionData | None = data.get("data") @@ -239,7 +241,7 @@ def _from_data(self, data: InteractionPayload): if self.guild_id: guild = ( self.guild - or self._state._get_guild(self.guild_id) + or await self._state._get_guild(self.guild_id) or Object(id=self.guild_id) ) try: @@ -282,7 +284,7 @@ def _from_data(self, data: InteractionPayload): self._channel_data = channel if message_data := data.get("message"): - self.message = Message( + self.message = await Message._from_data( state=self._state, channel=self.channel, data=message_data ) @@ -293,12 +295,11 @@ def client(self) -> Client: """Returns the client that sent the interaction.""" return self._state._get_client() - @property - def guild(self) -> Guild | None: + async def get_guild(self) -> Guild | None: """The guild the interaction was sent from.""" if self._guild: return self._guild - return self._state and self._state._get_guild(self.guild_id) + return self._state and await self._state._get_guild(self.guild_id) def is_command(self) -> bool: """Indicates whether the interaction is an application command.""" @@ -1348,8 +1349,8 @@ def __init__(self, interaction: Interaction, parent: ConnectionState): self._interaction: Interaction = interaction self._parent: ConnectionState = parent - def _get_guild(self, guild_id): - return self._parent._get_guild(guild_id) + async def _get_guild(self, guild_id): + return await self._parent._get_guild(guild_id) def store_user(self, data): return self._parent.store_user(data) @@ -1559,8 +1560,6 @@ class InteractionMetadata: "interacted_message_id", "triggering_interaction_metadata", "_state", - "_cs_original_response_message", - "_cs_interacted_message", ) def __init__(self, *, data: InteractionMetadataPayload, state: ConnectionState): @@ -1588,23 +1587,21 @@ def __repr__(self): f"" ) - @utils.cached_slot_property("_cs_original_response_message") - def original_response_message(self) -> Message | None: + async def get_original_response_message(self) -> Message | None: """Optional[:class:`Message`]: The original response message. Returns ``None`` if the message is not in cache, or if :attr:`original_response_message_id` is ``None``. """ if not self.original_response_message_id: return None - return self._state._get_message(self.original_response_message_id) + return await self._state._get_message(self.original_response_message_id) - @utils.cached_slot_property("_cs_interacted_message") - def interacted_message(self) -> Message | None: + async def get_interacted_message(self) -> Message | None: """Optional[:class:`Message`]: The message that triggered the interaction. Returns ``None`` if the message is not in cache, or if :attr:`interacted_message_id` is ``None``. """ if not self.interacted_message_id: return None - return self._state._get_message(self.interacted_message_id) + return await self._state._get_message(self.interacted_message_id) class AuthorizingIntegrationOwners: diff --git a/discord/invite.py b/discord/invite.py index 67f03199f6..26d04933bd 100644 --- a/discord/invite.py +++ b/discord/invite.py @@ -414,7 +414,7 @@ def __init__( ) @classmethod - def from_incomplete( + async def from_incomplete( cls: type[I], *, state: ConnectionState, data: InvitePayload ) -> I: guild: Guild | PartialInviteGuild | None @@ -425,7 +425,7 @@ def from_incomplete( guild = None else: guild_id = int(guild_data["id"]) - guild = state._get_guild(guild_id) + guild = await state._get_guild(guild_id) if guild is None: # If it's not cached, then it has to be a partial guild guild = PartialInviteGuild(state, guild_data, guild_id) @@ -442,11 +442,11 @@ def from_incomplete( return cls(state=state, data=data, guild=guild, channel=channel) @classmethod - def from_gateway( + async def from_gateway( cls: type[I], *, state: ConnectionState, data: GatewayInvitePayload ) -> I: guild_id: int | None = _get_as_snowflake(data, "guild_id") - guild: Guild | Object | None = state._get_guild(guild_id) + guild: Guild | Object | None = await state._get_guild(guild_id) channel_id = int(data["channel_id"]) if guild is not None: channel = guild.get_channel(channel_id) or Object(id=channel_id) # type: ignore diff --git a/discord/member.py b/discord/member.py index 8acea3d0d7..723424d171 100644 --- a/discord/member.py +++ b/discord/member.py @@ -135,7 +135,7 @@ def __init__( self.session_id: str = data.get("session_id") self._update(data, channel) - def _update( + async def _update( self, data: VoiceStatePayload | GuildVoiceStatePayload, channel: VocalGuildChannel | None, @@ -421,7 +421,7 @@ async def _get_channel(self): ch = await self.create_dm() return ch - def _update(self, data: MemberPayload) -> None: + async def _update(self, data: MemberPayload) -> None: # the nickname change is optional, # if it isn't in the payload then it didn't change try: diff --git a/discord/message.py b/discord/message.py index a49809422e..7688131342 100644 --- a/discord/message.py +++ b/discord/message.py @@ -26,6 +26,7 @@ from __future__ import annotations import datetime +from inspect import isawaitable import io import re from os import PathLike @@ -34,6 +35,7 @@ Any, Callable, ClassVar, + Self, Sequence, TypeVar, Union, @@ -571,10 +573,9 @@ def from_message( self._state = message._state return self - @property - def cached_message(self) -> Message | None: + async def get_cached_message(self) -> Message | None: """The cached message, if found in the internal message cache.""" - return self._state and self._state._get_message(self.message_id) + return self._state and await self._state._get_message(self.message_id) @property def jump_url(self) -> str: @@ -834,41 +835,43 @@ class Message(Hashable): author: User | Member role_mentions: list[Role] - def __init__( - self, + @classmethod + async def _from_data( + cls, *, state: ConnectionState, channel: MessageableChannel, data: MessagePayload, - ): - self._state: ConnectionState = state - self._raw_data: MessagePayload = data - self.id: int = int(data["id"]) - self.webhook_id: int | None = utils._get_as_snowflake(data, "webhook_id") - self.reactions: list[Reaction] = [ + ) -> Self: + self = cls() + self._state = state + self._raw_data = data + self.id = int(data["id"]) + self.webhook_id = utils._get_as_snowflake(data, "webhook_id") + self.reactions = [ Reaction(message=self, data=d) for d in data.get("reactions", []) ] - self.attachments: list[Attachment] = [ + self.attachments = [ 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.application: MessageApplicationPayload | None = data.get("application") - self.activity: MessageActivityPayload | None = data.get("activity") - self.channel: MessageableChannel = channel - self._edited_timestamp: datetime.datetime | None = utils.parse_time( + self.embeds = [Embed.from_dict(a) for a in data["embeds"]] + self.application = data.get("application") + self.activity = data.get("activity") + self.channel = channel + self._edited_timestamp = utils.parse_time( data["edited_timestamp"] ) - self.type: MessageType = try_enum(MessageType, data["type"]) - self.pinned: bool = data["pinned"] - self.flags: MessageFlags = MessageFlags._from_value(data.get("flags", 0)) - self.mention_everyone: bool = data["mention_everyone"] - self.tts: bool = data["tts"] - self.content: str = data["content"] - self.nonce: int | str | None = data.get("nonce") - self.stickers: list[StickerItem] = [ + self.type = try_enum(MessageType, data["type"]) + self.pinned = data["pinned"] + self.flags = MessageFlags._from_value(data.get("flags", 0)) + self.mention_everyone = data["mention_everyone"] + self.tts = data["tts"] + self.content = data["content"] + self.nonce = data.get("nonce") + self.stickers = [ StickerItem(data=d, state=state) for d in data.get("sticker_items", []) ] - self.components: list[Component] = [ + self.components = [ _component_factory(d) for d in data.get("components", []) ] @@ -876,7 +879,7 @@ def __init__( # if the channel doesn't have a guild attribute, we handle that self.guild = channel.guild # type: ignore except AttributeError: - self.guild = state._get_guild(utils._get_as_snowflake(data, "guild_id")) + self.guild = await state._get_guild(utils._get_as_snowflake(data, "guild_id")) try: ref = data["message_reference"] @@ -896,7 +899,7 @@ def __init__( if ref.channel_id == channel.id: chan = channel else: - chan, _ = state._get_guild_channel( + chan, _ = await state._get_guild_channel( resolved, guild_id=self.guild.id ) @@ -922,7 +925,7 @@ def __init__( self._poll: Poll | None try: self._poll = Poll.from_dict(data["poll"], self) - self._state.store_poll(self._poll, self.id) + await self._state.store_poll(self._poll, self.id) except KeyError: self._poll = None @@ -1011,7 +1014,7 @@ def _clear_emoji(self, emoji) -> Reaction | None: del self.reactions[index] return reaction - def _update(self, data): + async def _update(self, data): # In an update scheme, 'author' key has to be handled before 'member' # otherwise they overwrite each other which is undesirable. # Since there's no good way to do this we have to iterate over every @@ -1022,7 +1025,9 @@ def _update(self, data): except KeyError: continue else: - handler(self, value) + r = handler(self, value) + if isawaitable(r): + await r # clear the cached properties for attr in self._CACHED_SLOTS: @@ -1067,9 +1072,9 @@ def _handle_embeds(self, value: list[EmbedPayload]) -> None: def _handle_nonce(self, value: str | int) -> None: self.nonce = value - def _handle_poll(self, value: PollPayload) -> None: + async def _handle_poll(self, value: PollPayload) -> None: self._poll = Poll.from_dict(value, self) - self._state.store_poll(self._poll, self.id) + await self._state.store_poll(self._poll, self.id) def _handle_author(self, author: UserPayload) -> None: self.author = self._state.store_user(author) @@ -1633,7 +1638,7 @@ async def edit( data = await self._state.http.edit_message( self.channel.id, self.id, **payload ) - message = Message(state=self._state, channel=self.channel, data=data) + message = await Message._from_data(state=self._state, channel=self.channel, data=data) if view and not view.is_finished(): view.message = message @@ -1954,7 +1959,7 @@ async def end_poll(self) -> Message: self.channel.id, self.id, ) - message = Message(state=self._state, channel=self.channel, data=data) + message = await Message._from_data(state=self._state, channel=self.channel, data=data) return message @@ -2068,7 +2073,7 @@ def __init__(self, *, channel: PartialMessageableChannel, id: int): self._state: ConnectionState = channel._state self.id: int = id - def _update(self, data) -> None: + async def _update(self, data) -> None: # This is used for duck typing purposes. # Just do nothing with the data. pass diff --git a/discord/onboarding.py b/discord/onboarding.py index b82de31cfc..ed9549363b 100644 --- a/discord/onboarding.py +++ b/discord/onboarding.py @@ -236,7 +236,7 @@ def __init__(self, data: OnboardingPayload, guild: Guild): def __repr__(self): return f"" - def _update(self, data: OnboardingPayload): + async def _update(self, data: OnboardingPayload): self.guild_id: Snowflake = data["guild_id"] self.prompts: list[OnboardingPrompt] = [ OnboardingPrompt._from_dict(prompt, self.guild) diff --git a/discord/poll.py b/discord/poll.py index 087d2833f6..5136a471ce 100644 --- a/discord/poll.py +++ b/discord/poll.py @@ -92,7 +92,7 @@ def to_dict(self) -> PollMediaPayload: return dict_ @classmethod - def from_dict( + async def from_dict( cls, data: PollMediaPayload, message: Message | PartialMessage | None = None ) -> PollMedia: @@ -100,7 +100,7 @@ def from_dict( if isinstance(_emoji, dict) and _emoji.get("name"): emoji = PartialEmoji.from_dict(_emoji) if emoji.id and message: - emoji = message._state.get_emoji(emoji.id) or emoji + emoji = (await message._state.get_emoji(emoji.id)) or emoji else: emoji = _emoji or None return cls( diff --git a/discord/raw_models.py b/discord/raw_models.py index 2d5d90ac05..09c7ffdd78 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -654,14 +654,16 @@ class AutoModActionExecutionEvent: "data", ) - def __init__(self, state: ConnectionState, data: AutoModActionExecution) -> None: + @classmethod + async def from_data(cls, state: ConnectionState, data: AutoModActionExecution) -> None: + self = cls() self.action: AutoModAction = AutoModAction.from_dict(data["action"]) self.rule_id: int = int(data["rule_id"]) self.rule_trigger_type: AutoModTriggerType = try_enum( AutoModTriggerType, int(data["rule_trigger_type"]) ) self.guild_id: int = int(data["guild_id"]) - self.guild: Guild | None = state._get_guild(self.guild_id) + self.guild: Guild | None = await state._get_guild(self.guild_id) self.user_id: int = int(data["user_id"]) self.content: str | None = data.get("content", None) self.matched_keyword: str = data["matched_keyword"] @@ -685,7 +687,7 @@ def __init__(self, state: ConnectionState, data: AutoModActionExecution) -> None try: self.message_id: int | None = int(data["message_id"]) - self.message: Message | None = state._get_message(self.message_id) + self.message: Message | None = await state._get_message(self.message_id) except KeyError: self.message_id: int | None = None self.message: Message | None = None @@ -694,7 +696,7 @@ def __init__(self, state: ConnectionState, data: AutoModActionExecution) -> None self.alert_system_message_id: int | None = int( data["alert_system_message_id"] ) - self.alert_system_message: Message | None = state._get_message( + self.alert_system_message: Message | None = await state._get_message( self.alert_system_message_id ) except KeyError: diff --git a/discord/role.py b/discord/role.py index a27241ed1f..23d80bb1c8 100644 --- a/discord/role.py +++ b/discord/role.py @@ -295,7 +295,7 @@ def __ge__(self: R, other: R) -> bool: return NotImplemented return not r - def _update(self, data: RolePayload): + async def _update(self, data: RolePayload): self.name: str = data["name"] self._permissions: int = int(data.get("permissions", 0)) self.position: int = data.get("position", 0) diff --git a/discord/scheduled_events.py b/discord/scheduled_events.py index a6be44e895..f977ea5669 100644 --- a/discord/scheduled_events.py +++ b/discord/scheduled_events.py @@ -90,12 +90,12 @@ def __init__( self, *, state: ConnectionState, - value: str | int | StageChannel | VoiceChannel, + value: str | Object, ): self._state = state self.value: str | StageChannel | VoiceChannel | Object if isinstance(value, int): - self.value = self._state.get_channel(id=int(value)) or Object(id=int(value)) + self.value = Object(id=int(value)) else: self.value = value diff --git a/discord/shard.py b/discord/shard.py index 801197754d..4f57673263 100644 --- a/discord/shard.py +++ b/discord/shard.py @@ -554,11 +554,11 @@ async def change_presence( for shard in self.__shards.values(): await shard.ws.change_presence(activity=activity, status=status_value) - guilds = self._connection.guilds + guilds = await self._connection.get_guilds() else: shard = self.__shards[shard_id] await shard.ws.change_presence(activity=activity, status=status_value) - guilds = [g for g in self._connection.guilds if g.shard_id == shard_id] + guilds = [g for g in await self._connection.get_guilds() if g.shard_id == shard_id] activities = () if activity is None else (activity,) for guild in guilds: diff --git a/discord/stage_instance.py b/discord/stage_instance.py index 9ab5c562b9..bc03493533 100644 --- a/discord/stage_instance.py +++ b/discord/stage_instance.py @@ -87,7 +87,6 @@ class StageInstance(Hashable): "privacy_level", "discoverable_disabled", "scheduled_event", - "_cs_channel", ) def __init__( @@ -97,7 +96,7 @@ def __init__( self.guild = guild self._update(data) - def _update(self, data: StageInstancePayload): + async def _update(self, data: StageInstancePayload): self.id: int = int(data["id"]) self.channel_id: int = int(data["channel_id"]) self.topic: str = data["topic"] @@ -116,11 +115,10 @@ def __repr__(self) -> str: f" id={self.id} guild={self.guild!r} channel_id={self.channel_id} topic={self.topic!r}>" ) - @cached_slot_property("_cs_channel") - def channel(self) -> StageChannel | None: + async def get_channel(self) -> StageChannel | None: """The channel that stage instance is running in.""" # the returned channel will always be a StageChannel or None - return self._state.get_channel(self.channel_id) # type: ignore + return await self._state.get_channel(self.channel_id) # type: ignore def is_public(self) -> bool: return False diff --git a/discord/sticker.py b/discord/sticker.py index 52aaf4e172..6957c2becc 100644 --- a/discord/sticker.py +++ b/discord/sticker.py @@ -425,7 +425,7 @@ class GuildSticker(Sticker): The name of a unicode emoji that represents this sticker. """ - __slots__ = ("available", "guild_id", "user", "emoji", "type", "_cs_guild") + __slots__ = ("available", "guild_id", "user", "emoji", "type") def _from_data(self, data: GuildStickerPayload) -> None: super()._from_data(data) @@ -442,14 +442,13 @@ def __repr__(self) -> str: f" name={self.name!r} id={self.id} guild_id={self.guild_id} user={self.user!r}>" ) - @cached_slot_property("_cs_guild") - def guild(self) -> Guild | None: + async def get_guild(self) -> Guild | None: """The guild that this sticker is from. Could be ``None`` if the bot is not in the guild. .. versionadded:: 2.0 """ - return self._state._get_guild(self.guild_id) + return await self._state._get_guild(self.guild_id) async def edit( self, diff --git a/discord/template.py b/discord/template.py index 57fcf2e1a1..160f6eb45e 100644 --- a/discord/template.py +++ b/discord/template.py @@ -77,8 +77,8 @@ def _get_voice_client(self, id): def _get_message(self, id): return None - def _get_guild(self, id): - return self.__state._get_guild(id) + async def _get_guild(self, id): + return await self.__state._get_guild(id) async def query_members(self, **kwargs: Any): return [] @@ -130,11 +130,11 @@ class Template: "_state", ) - def __init__(self, *, state: ConnectionState, data: TemplatePayload) -> None: + @classmethod + async def from_data(cls, state: ConnectionState, data: TemplatePayload) -> None: + self = cls() self._state = state - self._store(data) - def _store(self, data: TemplatePayload) -> None: self.code: str = data["code"] self.uses: int = data["usage_count"] self.name: str = data["name"] @@ -148,7 +148,7 @@ def _store(self, data: TemplatePayload) -> None: self.updated_at: datetime.datetime | None = parse_time(data.get("updated_at")) guild_id = int(data["source_guild_id"]) - guild: Guild | None = self._state._get_guild(guild_id) + guild: Guild | None = await self._state._get_guild(guild_id) self.source_guild: Guild if guild is None: @@ -231,7 +231,7 @@ async def sync(self) -> Template: """ data = await self._state.http.sync_template(self.source_guild.id, self.code) - return Template(state=self._state, data=data) + return await Template.from_data(state=self._state, data=data) async def edit( self, @@ -282,7 +282,7 @@ async def edit( data = await self._state.http.edit_template( self.source_guild.id, self.code, payload ) - return Template(state=self._state, data=data) + return await Template.from_data(state=self._state, data=data) async def delete(self) -> None: """|coro| diff --git a/discord/threads.py b/discord/threads.py index d7ab3442a4..e316c3ad7f 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -242,7 +242,7 @@ def _unroll_metadata(self, data: ThreadMetadata): self.invitable = data.get("invitable", True) self.created_at = parse_time(data.get("create_timestamp", None)) - def _update(self, data): + async def _update(self, data): try: self.name = data["name"] except KeyError: @@ -313,8 +313,7 @@ def applied_tags(self) -> list[ForumTag]: ] return [] - @property - def last_message(self) -> Message | None: + async def get_last_message(self) -> Message | None: """Returns the last message from this thread in cache. The message might not be valid or point to an existing message. @@ -333,7 +332,7 @@ def last_message(self) -> Message | None: The last message in this channel or ``None`` if not found. """ return ( - self._state._get_message(self.last_message_id) + await self._state._get_message(self.last_message_id) if self.last_message_id else None ) @@ -378,8 +377,7 @@ def category_id(self) -> int | None: raise ClientException("Parent channel not found") return parent.category_id - @property - def starting_message(self) -> Message | None: + async def get_starting_message(self) -> Message | None: """Returns the message that started this thread. The message might not be valid or point to an existing message. @@ -392,7 +390,7 @@ def starting_message(self) -> Message | None: Optional[:class:`Message`] The message that started this thread or ``None`` if not found in the cache. """ - return self._state._get_message(self.id) + return await self._state._get_message(self.id) def is_pinned(self) -> bool: """Whether the thread is pinned to the top of its parent forum or media channel. diff --git a/discord/user.py b/discord/user.py index d33be44f68..cbdab71b10 100644 --- a/discord/user.py +++ b/discord/user.py @@ -135,7 +135,7 @@ def __eq__(self, other: Any) -> bool: def __hash__(self) -> int: return self.id >> 22 - def _update(self, data: UserPayload) -> None: + async def _update(self, data: UserPayload) -> None: self.name = data["username"] self.id = int(data["id"]) self.discriminator = data["discriminator"] @@ -418,7 +418,7 @@ def __repr__(self) -> str: f" bot={self.bot} verified={self.verified} mfa_enabled={self.mfa_enabled}>" ) - def _update(self, data: UserPayload) -> None: + async def _update(self, data: UserPayload) -> None: super()._update(data) # There's actually an Optional[str] phone field as well, but I won't use it self.verified = data.get("verified", False) @@ -573,14 +573,13 @@ async def _get_channel(self) -> DMChannel: ch = await self.create_dm() return ch - @property - def dm_channel(self) -> DMChannel | None: + async def get_dm_channel(self) -> DMChannel | None: """Returns the channel associated with this user if it exists. If this returns ``None``, you can create a DM channel by calling the :meth:`create_dm` coroutine function. """ - return self._state._get_private_channel_by_user(self.id) + return await self._state._get_private_channel_by_user(self.id) @property def mutual_guilds(self) -> list[Guild]: @@ -615,7 +614,7 @@ async def create_dm(self) -> DMChannel: state = self._state data: DMChannelPayload = await state.http.start_private_message(self.id) - return state.add_dm_channel(data) + return await state.add_dm_channel(data) async def create_test_entitlement(self, sku: discord.abc.Snowflake) -> Entitlement: """|coro| diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 853cd1683d..fb143e1bd9 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -820,9 +820,9 @@ def create_user(self, data): # state parameter is artificial return BaseUser(state=self, data=data) # type: ignore - def store_poll(self, poll: Poll, message_id: int): + async def store_poll(self, poll: Poll, message_id: int): if self._parent is not None: - return self._parent.store_poll(poll, message_id) + return await self._parent.store_poll(poll, message_id) # state parameter is artificial return None @@ -1027,7 +1027,7 @@ def __init__( ) self._update(data) - def _update(self, data: WebhookPayload | FollowerWebhookPayload): + async def _update(self, data: WebhookPayload | FollowerWebhookPayload): self.id = int(data["id"]) self.type = try_enum(WebhookType, int(data["type"])) self.channel_id = utils._get_as_snowflake(data, "channel_id") @@ -1068,13 +1068,12 @@ def is_authenticated(self) -> bool: """ return self.auth_token is not None - @property - def guild(self) -> Guild | None: + async def get_guild(self) -> Guild | None: """The guild this webhook belongs to. If this is a partial webhook, then this will always return ``None``. """ - return self._state and self._state._get_guild(self.guild_id) + return self._state and await self._state._get_guild(self.guild_id) @property def channel(self) -> TextChannel | None: diff --git a/discord/welcome_screen.py b/discord/welcome_screen.py index cb0d1eea0a..65625c768a 100644 --- a/discord/welcome_screen.py +++ b/discord/welcome_screen.py @@ -132,7 +132,7 @@ def __repr__(self): f" description={self.description} welcome_channels={self.welcome_channels}" ) - def _update(self, data: WelcomeScreenPayload): + async def _update(self, data: WelcomeScreenPayload): self.description: str = data.get("description") self.welcome_channels: list[WelcomeScreenChannel] = [ WelcomeScreenChannel._from_dict(channel, self._guild) diff --git a/discord/widget.py b/discord/widget.py index c60b1041f8..51bb4b8b1f 100644 --- a/discord/widget.py +++ b/discord/widget.py @@ -345,4 +345,4 @@ async def fetch_invite(self, *, with_counts: bool = True) -> Invite: """ invite_id = resolve_invite(self._invite) data = await self._state.http.get_invite(invite_id, with_counts=with_counts) - return Invite.from_incomplete(state=self._state, data=data) + return await Invite.from_incomplete(state=self._state, data=data) diff --git a/docs/api/enums.rst b/docs/api/enums.rst index 4d278e3758..4556f89274 100644 --- a/docs/api/enums.rst +++ b/docs/api/enums.rst @@ -821,7 +821,7 @@ of :class:`enum.Enum`. - Changing the guild moderation settings - Changing things related to the guild widget - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`. Possible attributes for :class:`AuditLogDiff`: @@ -844,7 +844,7 @@ of :class:`enum.Enum`. A new channel was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID. A more filled out object in the :class:`Object` case can be found @@ -863,7 +863,7 @@ of :class:`enum.Enum`. - The channel name or topic was changed - The channel bitrate was changed - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID. A more filled out object in the :class:`Object` case can be found @@ -885,7 +885,7 @@ of :class:`enum.Enum`. A channel was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID. A more filled out object can be found by using the @@ -901,7 +901,7 @@ of :class:`enum.Enum`. A channel permission overwrite was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID. When this is the action, the type of :attr:`~AuditLogEntry.extra` is @@ -923,7 +923,7 @@ of :class:`enum.Enum`. when the permission values change. See :attr:`overwrite_create` for more information on how the - :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields + :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set. Possible attributes for :class:`AuditLogDiff`: @@ -938,7 +938,7 @@ of :class:`enum.Enum`. A channel permission overwrite was deleted. See :attr:`overwrite_create` for more information on how the - :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields + :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set. Possible attributes for :class:`AuditLogDiff`: @@ -952,7 +952,7 @@ of :class:`enum.Enum`. A member was kicked. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked. When this is the action, :attr:`~AuditLogEntry.changes` is empty. @@ -961,7 +961,7 @@ of :class:`enum.Enum`. A member prune was triggered. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``. When this is the action, the type of :attr:`~AuditLogEntry.extra` is @@ -976,7 +976,7 @@ of :class:`enum.Enum`. A member was banned. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned. When this is the action, :attr:`~AuditLogEntry.changes` is empty. @@ -985,7 +985,7 @@ of :class:`enum.Enum`. A member was unbanned. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned. When this is the action, :attr:`~AuditLogEntry.changes` is empty. @@ -997,7 +997,7 @@ of :class:`enum.Enum`. - A nickname was changed - They were server muted or deafened (or it was undone) - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated. Possible attributes for :class:`AuditLogDiff`: @@ -1011,7 +1011,7 @@ of :class:`enum.Enum`. A member's role has been updated. This triggers when a member either gains a role or loses a role. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role. Possible attributes for :class:`AuditLogDiff`: @@ -1047,7 +1047,7 @@ of :class:`enum.Enum`. A bot was added to the guild. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild. .. versionadded:: 1.3 @@ -1056,7 +1056,7 @@ of :class:`enum.Enum`. A new role was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID. Possible attributes for :class:`AuditLogDiff`: @@ -1076,7 +1076,7 @@ of :class:`enum.Enum`. - The colour has changed - Its hoist/mentionable state has changed - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID. Possible attributes for :class:`AuditLogDiff`: @@ -1091,7 +1091,7 @@ of :class:`enum.Enum`. A role was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID. Possible attributes for :class:`AuditLogDiff`: @@ -1106,7 +1106,7 @@ of :class:`enum.Enum`. An invite was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created. Possible attributes for :class:`AuditLogDiff`: @@ -1123,14 +1123,14 @@ of :class:`enum.Enum`. An invite was updated. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated. .. attribute:: invite_delete An invite was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted. Possible attributes for :class:`AuditLogDiff`: @@ -1147,7 +1147,7 @@ of :class:`enum.Enum`. A webhook was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID. Possible attributes for :class:`AuditLogDiff`: @@ -1163,7 +1163,7 @@ of :class:`enum.Enum`. - The webhook name changed - The webhook channel changed - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID. Possible attributes for :class:`AuditLogDiff`: @@ -1176,7 +1176,7 @@ of :class:`enum.Enum`. A webhook was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID. Possible attributes for :class:`AuditLogDiff`: @@ -1189,7 +1189,7 @@ of :class:`enum.Enum`. An emoji was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID. Possible attributes for :class:`AuditLogDiff`: @@ -1200,7 +1200,7 @@ of :class:`enum.Enum`. An emoji was updated. This triggers when the name has changed. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID. Possible attributes for :class:`AuditLogDiff`: @@ -1211,7 +1211,7 @@ of :class:`enum.Enum`. An emoji was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID. Possible attributes for :class:`AuditLogDiff`: @@ -1223,7 +1223,7 @@ of :class:`enum.Enum`. A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted. When this is the action, the type of :attr:`~AuditLogEntry.extra` is @@ -1236,7 +1236,7 @@ of :class:`enum.Enum`. Messages were bulk deleted by a moderator. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged. When this is the action, the type of :attr:`~AuditLogEntry.extra` is @@ -1250,7 +1250,7 @@ of :class:`enum.Enum`. A message was pinned in a channel. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned. When this is the action, the type of :attr:`~AuditLogEntry.extra` is @@ -1265,7 +1265,7 @@ of :class:`enum.Enum`. A message was unpinned in a channel. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned. When this is the action, the type of :attr:`~AuditLogEntry.extra` is @@ -1280,7 +1280,7 @@ of :class:`enum.Enum`. A guild integration was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created. .. versionadded:: 1.3 @@ -1289,7 +1289,7 @@ of :class:`enum.Enum`. A guild integration was updated. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated. .. versionadded:: 1.3 @@ -1298,7 +1298,7 @@ of :class:`enum.Enum`. A guild integration was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted. .. versionadded:: 1.3 @@ -1307,7 +1307,7 @@ of :class:`enum.Enum`. A stage instance was started. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created. @@ -1322,7 +1322,7 @@ of :class:`enum.Enum`. A stage instance was updated. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated. @@ -1343,7 +1343,7 @@ of :class:`enum.Enum`. A sticker was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated. @@ -1362,7 +1362,7 @@ of :class:`enum.Enum`. A sticker was updated. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated. @@ -1381,7 +1381,7 @@ of :class:`enum.Enum`. A sticker was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated. @@ -1400,7 +1400,7 @@ of :class:`enum.Enum`. A scheduled event was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted. @@ -1421,7 +1421,7 @@ of :class:`enum.Enum`. A scheduled event was updated. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted. @@ -1442,7 +1442,7 @@ of :class:`enum.Enum`. A scheduled event was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted. @@ -1463,7 +1463,7 @@ of :class:`enum.Enum`. A thread was created. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created. @@ -1481,7 +1481,7 @@ of :class:`enum.Enum`. A thread was updated. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated. @@ -1499,7 +1499,7 @@ of :class:`enum.Enum`. A thread was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted. @@ -1517,7 +1517,7 @@ of :class:`enum.Enum`. An application command's permissions were updated. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited. @@ -1611,7 +1611,7 @@ of :class:`enum.Enum`. A voice channel status was updated. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated. @@ -1625,7 +1625,7 @@ of :class:`enum.Enum`. A voice channel status was deleted. - When this is the action, the type of :attr:`~AuditLogEntry.target` is + When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated. diff --git a/docs/api/events.rst b/docs/api/events.rst index e9bd0a4c0d..4f3982e2a4 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -328,7 +328,7 @@ Connection .. function:: on_ready() 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. + and the :func:`Client.get_guilds` and co. are filled up. .. warning:: @@ -434,7 +434,7 @@ Guilds - The client or the guild owner deleted the guild. 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`) + been part of the guild to begin with. (i.e. it is part of :func:`Client.get_guilds`) This requires :attr:`Intents.guilds` to be enabled. @@ -512,7 +512,7 @@ Guilds on_guild_unavailable(guild) Called when a guild becomes available or unavailable. The guild must have - existed in the :attr:`Client.guilds` cache. + existed in the :func:`Client.get_guilds` cache. This requires :attr:`Intents.guilds` to be enabled. diff --git a/docs/build/locales/api/enums.pot b/docs/build/locales/api/enums.pot index 3e4744f908..ccf0e3ac2b 100644 --- a/docs/build/locales/api/enums.pot +++ b/docs/build/locales/api/enums.pot @@ -1103,7 +1103,7 @@ msgstr "" #: ../../api/enums.rst:824 #: c890e719787243a5af28be7c6f82f77b -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgstr "" #: ../../api/enums.rst:827 @@ -1303,7 +1303,7 @@ msgstr "" #: ../../api/enums.rst:847 #: e00bbae1ecc54cd4aa7b2f97761a2857 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgstr "" #: ../../api/enums.rst:850 @@ -1360,7 +1360,7 @@ msgstr "" #: ../../api/enums.rst:904 #: 8f81efbdafac4745899e4ad861717ecd #: 5257b72e408a46d4a7b06498d32f7209 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgstr "" #: ../../api/enums.rst:869 @@ -1409,7 +1409,7 @@ msgstr "" #: ../../api/enums.rst:888 #: 5af5984da736481eae7b9ec37355167b -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgstr "" #: ../../api/enums.rst:891 @@ -1463,7 +1463,7 @@ msgstr "" #: ../../api/enums.rst:940 #: b74dea0f59eb486f98c229b259e001f9 #: 548a7a8ca0314acf9735eac331d866e9 -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgstr "" #: ../../api/enums.rst:938 @@ -1478,7 +1478,7 @@ msgstr "" #: ../../api/enums.rst:955 #: 9462b1de374b4d8696d91df0e11eaf2e -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgstr "" #: ../../api/enums.rst:958 @@ -1499,7 +1499,7 @@ msgstr "" #: ../../api/enums.rst:964 #: 4c83a0acb3cc4f5d9f86ee20117108e6 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgstr "" #: ../../api/enums.rst:967 @@ -1532,7 +1532,7 @@ msgstr "" #: ../../api/enums.rst:979 #: 770451079d8d4b5f8e33b6c52da5f3ff -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgstr "" #: ../../api/enums.rst:986 @@ -1542,7 +1542,7 @@ msgstr "" #: ../../api/enums.rst:988 #: 456609aa1df0460dbb3dcd6b5414aca8 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgstr "" #: ../../api/enums.rst:995 @@ -1562,7 +1562,7 @@ msgstr "" #: ../../api/enums.rst:1000 #: 3685711e4704496c8312ef6113636f20 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgstr "" #: ../../api/enums.rst:1005 @@ -1587,7 +1587,7 @@ msgstr "" #: ../../api/enums.rst:1014 #: a57b479bb72143a6b0e1fa6cff760c1a -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgstr "" #: ../../api/enums.rst:1019 @@ -1634,7 +1634,7 @@ msgstr "" #: ../../api/enums.rst:1050 #: aad1bbec2f2a487784929c43e432558a -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgstr "" #: ../../api/enums.rst:1057 @@ -1648,7 +1648,7 @@ msgstr "" #: 5547887ba3e64a6cadc87783c1bcecf7 #: 91ed6f1edab04e8aad95cd8d9aa069f7 #: f5e5f97f23e7451c8914aad7c3391dd4 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgstr "" #: ../../api/enums.rst:1064 @@ -1724,7 +1724,7 @@ msgstr "" #: ../../api/enums.rst:1109 #: ce279e2749bb47529031e1755e0740c2 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgstr "" #: ../../api/enums.rst:1114 @@ -1795,7 +1795,7 @@ msgstr "" #: ../../api/enums.rst:1126 #: c0adb6372c474dd9a6fa1b79ababb781 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgstr "" #: ../../api/enums.rst:1131 @@ -1805,7 +1805,7 @@ msgstr "" #: ../../api/enums.rst:1133 #: 951939c17ece4b73a544877c61858520 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgstr "" #: ../../api/enums.rst:1148 @@ -1819,7 +1819,7 @@ msgstr "" #: 2fe79b01b896449f8a2711781524a8df #: ef972dbe7ec941a5907d624c86a390d6 #: 59d99fea521c4d5f9ce1c97173c5a522 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgstr "" #: ../../api/enums.rst:1157 @@ -1863,7 +1863,7 @@ msgstr "" #: ../../api/enums.rst:1203 #: 41aeb9dc3c5840aeb56f92ec751f6cd0 #: df5de1fd8daa4ce2a481835fc9ae71f4 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgstr "" #: ../../api/enums.rst:1201 @@ -1878,7 +1878,7 @@ msgstr "" #: ../../api/enums.rst:1214 #: 6c4676bf1a414f30a39f7196f7d82d10 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." msgstr "" #: ../../api/enums.rst:1223 @@ -1888,7 +1888,7 @@ msgstr "" #: ../../api/enums.rst:1226 #: 8982961c175341f9a00e1d4196d2528a -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgstr "" #: ../../api/enums.rst:1232 @@ -1910,7 +1910,7 @@ msgstr "" #: ../../api/enums.rst:1239 #: b496e7abbf6641c6aa0ecb2f408fa88d -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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgstr "" #: ../../api/enums.rst:1251 @@ -1920,7 +1920,7 @@ msgstr "" #: ../../api/enums.rst:1253 #: d1e5158c3f894e9098ce7240dda29673 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgstr "" #: ../../api/enums.rst:1259 @@ -1940,7 +1940,7 @@ msgstr "" #: ../../api/enums.rst:1268 #: 9133756a00814e1591bec12ddd56edb6 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgstr "" #: ../../api/enums.rst:1274 @@ -1960,7 +1960,7 @@ msgstr "" #: ../../api/enums.rst:1283 #: 28a61562db5c42e58719d9abdd270252 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgstr "" #: ../../api/enums.rst:1290 @@ -1970,7 +1970,7 @@ msgstr "" #: ../../api/enums.rst:1292 #: cc4ffb3112c04683842b7de2775fb45d -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgstr "" #: ../../api/enums.rst:1299 @@ -1980,7 +1980,7 @@ msgstr "" #: ../../api/enums.rst:1301 #: f5bd9a45dda249209e96a6ee8751ca52 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgstr "" #: ../../api/enums.rst:1308 @@ -1990,7 +1990,7 @@ msgstr "" #: ../../api/enums.rst:1310 #: c2e9a68539184e2fa02ef00208b3a2aa -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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgstr "" #: ../../api/enums.rst:1317 @@ -2013,7 +2013,7 @@ msgstr "" #: ../../api/enums.rst:1325 #: 5f94e6a27480458483dd65b0a69b1037 -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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgstr "" #: ../../api/enums.rst:1338 @@ -2032,7 +2032,7 @@ msgstr "" #: a544a30d24fe44ce9cbd11c4d6bf9f7c #: 83e568d602594a06a3755e2645cea0e5 #: 3d62e77b4351433b96277edb798f3ce3 -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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgstr "" #: ../../api/enums.rst:1353 @@ -2098,7 +2098,7 @@ msgstr "" #: af920a7bbbc94e369a1d93f7a1cc290a #: 5a0b011e635a4d84895b73b59e7641c9 #: 3a98586e07cd4bd4b61d000b98c58dec -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgstr "" #: ../../api/enums.rst:1413 @@ -2154,7 +2154,7 @@ msgstr "" #: ../../api/enums.rst:1466 #: afb06852a1554bf69f47285f8ffc16d4 -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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgstr "" #: ../../api/enums.rst:1473 @@ -2200,7 +2200,7 @@ msgstr "" #: ../../api/enums.rst:1484 #: 43b8091416ca4f4987c31bc8136e00f5 -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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgstr "" #: ../../api/enums.rst:1500 @@ -2210,7 +2210,7 @@ msgstr "" #: ../../api/enums.rst:1502 #: 438ec881b324479aad7b59bb720a7b9c -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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgstr "" #: ../../api/enums.rst:1518 @@ -2220,7 +2220,7 @@ msgstr "" #: ../../api/enums.rst:1520 #: e7552f8804b04c728f78efa08726afe8 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgstr "" #: ../../api/enums.rst:1526 @@ -2338,7 +2338,7 @@ msgstr "" #: ../../api/enums.rst:1628 #: d09aa244691540dbaeff35ae7f802cd6 #: 21d7aa25fa2449bdbc086189b5743a39 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgstr "" #: ../../api/enums.rst:1620 diff --git a/docs/faq.rst b/docs/faq.rst index bf8bfe82d0..f90d69884e 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -201,7 +201,7 @@ to do anything special. You **cannot** send ``':thumbsup:'`` style shorthands. For custom emoji, you should pass an instance of :class:`GuildEmoji` or :class:`AppEmoji`. You can also pass a ``'<:name:id>'`` string, but if you can use said emoji, you should be able to use :meth:`Client.get_emoji` to get an emoji via ID or use :func:`utils.find`/ -:func:`utils.get` on :attr:`Client.emojis` or :attr:`Guild.emojis` collections. +:func:`utils.get` on :meth:`Client.get_emojis` or :attr:`Guild.emojis` collections. The name and ID of a custom emoji can be found with the client by prefixing ``:custom_emoji:`` with a backslash. For example, sending the message ``\:python3:`` with the client will result in ``<:python3:232720527448342530>``. @@ -285,7 +285,7 @@ specific models. Quick example: :: # find a guild by name - guild = discord.utils.get(client.guilds, name='My Server') + guild = discord.utils.get(await client.get_guilds(), name='My Server') # make sure to check if it's found if guild is not None: diff --git a/docs/locales/de/LC_MESSAGES/api/enums.po b/docs/locales/de/LC_MESSAGES/api/enums.po index 72896957cb..f2c595a41a 100644 --- a/docs/locales/de/LC_MESSAGES/api/enums.po +++ b/docs/locales/de/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/en/LC_MESSAGES/api/enums.po b/docs/locales/en/LC_MESSAGES/api/enums.po index 54180b4f76..9b4f494feb 100644 --- a/docs/locales/en/LC_MESSAGES/api/enums.po +++ b/docs/locales/en/LC_MESSAGES/api/enums.po @@ -960,7 +960,7 @@ msgstr "" #: ../../api/enums.rst:824 c890e719787243a5af28be7c6f82f77b msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Guild`." msgstr "" @@ -1076,7 +1076,7 @@ msgstr "" #: ../../api/enums.rst:847 e00bbae1ecc54cd4aa7b2f97761a2857 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is " +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is " "either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgstr "" @@ -1118,7 +1118,7 @@ msgstr "" #: ../../api/enums.rst:866 ../../api/enums.rst:904 #: 5257b72e408a46d4a7b06498d32f7209 8f81efbdafac4745899e4ad861717ecd msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`abc.GuildChannel` or :class:`Object` with an ID." msgstr "" @@ -1160,7 +1160,7 @@ msgstr "" #: ../../api/enums.rst:888 5af5984da736481eae7b9ec37355167b msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is an " +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is an " ":class:`Object` with an ID." msgstr "" @@ -1211,7 +1211,7 @@ msgstr "" #: 548a7a8ca0314acf9735eac331d866e9 b74dea0f59eb486f98c229b259e001f9 msgid "" "See :attr:`overwrite_create` for more information on how the " -":attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are" +":func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are" " set." msgstr "" @@ -1225,7 +1225,7 @@ msgstr "" #: ../../api/enums.rst:955 9462b1de374b4d8696d91df0e11eaf2e msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`User` who got kicked." msgstr "" @@ -1242,7 +1242,7 @@ msgstr "" #: ../../api/enums.rst:964 4c83a0acb3cc4f5d9f86ee20117108e6 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is set" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is set" " to ``None``." msgstr "" @@ -1270,7 +1270,7 @@ msgstr "" #: ../../api/enums.rst:979 770451079d8d4b5f8e33b6c52da5f3ff msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`User` who got banned." msgstr "" @@ -1280,7 +1280,7 @@ msgstr "" #: ../../api/enums.rst:988 456609aa1df0460dbb3dcd6b5414aca8 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`User` who got unbanned." msgstr "" @@ -1298,7 +1298,7 @@ msgstr "" #: ../../api/enums.rst:1000 3685711e4704496c8312ef6113636f20 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Member` or :class:`User` who got updated." msgstr "" @@ -1322,7 +1322,7 @@ msgstr "" #: ../../api/enums.rst:1014 a57b479bb72143a6b0e1fa6cff760c1a msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Member` or :class:`User` who got the role." msgstr "" @@ -1369,7 +1369,7 @@ msgstr "" #: ../../api/enums.rst:1050 aad1bbec2f2a487784929c43e432558a msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Member` or :class:`User` which was added to the guild." msgstr "" @@ -1381,7 +1381,7 @@ msgstr "" #: 5547887ba3e64a6cadc87783c1bcecf7 91ed6f1edab04e8aad95cd8d9aa069f7 #: f5e5f97f23e7451c8914aad7c3391dd4 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Role` or a :class:`Object` with the ID." msgstr "" @@ -1439,7 +1439,7 @@ msgstr "" #: ../../api/enums.rst:1109 ce279e2749bb47529031e1755e0740c2 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Invite` that was created." msgstr "" @@ -1489,7 +1489,7 @@ msgstr "" #: ../../api/enums.rst:1126 c0adb6372c474dd9a6fa1b79ababb781 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Invite` that was updated." msgstr "" @@ -1499,7 +1499,7 @@ msgstr "" #: ../../api/enums.rst:1133 951939c17ece4b73a544877c61858520 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Invite` that was deleted." msgstr "" @@ -1511,7 +1511,7 @@ msgstr "" #: 2fe79b01b896449f8a2711781524a8df 59d99fea521c4d5f9ce1c97173c5a522 #: ef972dbe7ec941a5907d624c86a390d6 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Object` with the webhook ID." msgstr "" @@ -1547,7 +1547,7 @@ msgstr "" #: ../../api/enums.rst:1192 ../../api/enums.rst:1203 #: 41aeb9dc3c5840aeb56f92ec751f6cd0 df5de1fd8daa4ce2a481835fc9ae71f4 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgstr "" @@ -1561,7 +1561,7 @@ msgstr "" #: ../../api/enums.rst:1214 6c4676bf1a414f30a39f7196f7d82d10 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Object` with the emoji ID." msgstr "" @@ -1573,7 +1573,7 @@ msgstr "" #: ../../api/enums.rst:1226 8982961c175341f9a00e1d4196d2528a msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Member` or :class:`User` who had their message deleted." msgstr "" @@ -1594,7 +1594,7 @@ msgstr "" #: ../../api/enums.rst:1239 b496e7abbf6641c6aa0ecb2f408fa88d msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`TextChannel` or :class:`Object` with the ID of the channel that " "was purged." msgstr "" @@ -1605,7 +1605,7 @@ msgstr "" #: ../../api/enums.rst:1253 d1e5158c3f894e9098ce7240dda29673 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Member` or :class:`User` who had their message pinned." msgstr "" @@ -1625,7 +1625,7 @@ msgstr "" #: ../../api/enums.rst:1268 9133756a00814e1591bec12ddd56edb6 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Member` or :class:`User` who had their message unpinned." msgstr "" @@ -1645,7 +1645,7 @@ msgstr "" #: ../../api/enums.rst:1283 28a61562db5c42e58719d9abdd270252 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Object` with the integration ID of the integration which was " "created." msgstr "" @@ -1656,7 +1656,7 @@ msgstr "" #: ../../api/enums.rst:1292 cc4ffb3112c04683842b7de2775fb45d msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Object` with the integration ID of the integration which was " "updated." msgstr "" @@ -1667,7 +1667,7 @@ msgstr "" #: ../../api/enums.rst:1301 f5bd9a45dda249209e96a6ee8751ca52 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Object` with the integration ID of the integration which was " "deleted." msgstr "" @@ -1678,7 +1678,7 @@ msgstr "" #: ../../api/enums.rst:1310 c2e9a68539184e2fa02ef00208b3a2aa msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`StageInstance` or :class:`Object` with the ID of the stage " "instance which was created." msgstr "" @@ -1697,7 +1697,7 @@ msgstr "" #: ../../api/enums.rst:1325 5f94e6a27480458483dd65b0a69b1037 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`StageInstance` or :class:`Object` with the ID of the stage " "instance which was updated." msgstr "" @@ -1714,7 +1714,7 @@ msgstr "" #: 3d62e77b4351433b96277edb798f3ce3 83e568d602594a06a3755e2645cea0e5 #: a544a30d24fe44ce9cbd11c4d6bf9f7c msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`GuildSticker` or :class:`Object` with the ID of the sticker " "which was updated." msgstr "" @@ -1761,7 +1761,7 @@ msgstr "" #: 3a98586e07cd4bd4b61d000b98c58dec 5a0b011e635a4d84895b73b59e7641c9 #: af920a7bbbc94e369a1d93f7a1cc290a msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`ScheduledEvent` or :class:`Object` with the ID of the thread " "which was deleted." msgstr "" @@ -1804,7 +1804,7 @@ msgstr "" #: ../../api/enums.rst:1466 afb06852a1554bf69f47285f8ffc16d4 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Thread` or :class:`Object` with the ID of the thread which was " "created." msgstr "" @@ -1839,7 +1839,7 @@ msgstr "" #: ../../api/enums.rst:1484 43b8091416ca4f4987c31bc8136e00f5 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Thread` or :class:`Object` with the ID of the thread which was " "updated." msgstr "" @@ -1850,7 +1850,7 @@ msgstr "" #: ../../api/enums.rst:1502 438ec881b324479aad7b59bb720a7b9c msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`Thread` or :class:`Object` with the ID of the thread which was " "deleted." msgstr "" @@ -1861,7 +1861,7 @@ msgstr "" #: ../../api/enums.rst:1520 e7552f8804b04c728f78efa08726afe8 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is an " +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is an " ":class:`Object` with the ID of the command that had it's permissions " "edited." msgstr "" @@ -1950,7 +1950,7 @@ msgstr "" #: ../../api/enums.rst:1614 ../../api/enums.rst:1628 #: 21d7aa25fa2449bdbc086189b5743a39 d09aa244691540dbaeff35ae7f802cd6 msgid "" -"When this is the action, the type of :attr:`~AuditLogEntry.target` is the" +"When this is the action, the type of :func:`~AuditLogEntry.get_target` is the" " :class:`VoiceChannel` or :class:`Object` with the ID of the voice " "channel which was updated." msgstr "" @@ -2817,7 +2817,7 @@ msgstr "" #~ msgid "" #~ "When this is the action, the type" -#~ " of :attr:`~AuditLogEntry.target` is the " +#~ " of :func:`~AuditLogEntry.get_target` is the " #~ ":class:`Emoji` or :class:`Object` with the " #~ "emoji ID." #~ msgstr "" diff --git a/docs/locales/es/LC_MESSAGES/api/enums.po b/docs/locales/es/LC_MESSAGES/api/enums.po index 72896957cb..f2c595a41a 100644 --- a/docs/locales/es/LC_MESSAGES/api/enums.po +++ b/docs/locales/es/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/fr/LC_MESSAGES/api/enums.po b/docs/locales/fr/LC_MESSAGES/api/enums.po index 8fc6f18aa2..1adf437a4e 100644 --- a/docs/locales/fr/LC_MESSAGES/api/enums.po +++ b/docs/locales/fr/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/hi/LC_MESSAGES/api/enums.po b/docs/locales/hi/LC_MESSAGES/api/enums.po index 72896957cb..f2c595a41a 100644 --- a/docs/locales/hi/LC_MESSAGES/api/enums.po +++ b/docs/locales/hi/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/it/LC_MESSAGES/api/enums.po b/docs/locales/it/LC_MESSAGES/api/enums.po index 72896957cb..f2c595a41a 100644 --- a/docs/locales/it/LC_MESSAGES/api/enums.po +++ b/docs/locales/it/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/ja/LC_MESSAGES/api/enums.po b/docs/locales/ja/LC_MESSAGES/api/enums.po index 691d149de4..bc6647464e 100644 --- a/docs/locales/ja/LC_MESSAGES/api/enums.po +++ b/docs/locales/ja/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/ja/LC_MESSAGES/build/locales/api/enums.po b/docs/locales/ja/LC_MESSAGES/build/locales/api/enums.po index f43361483c..f140fc4020 100644 --- a/docs/locales/ja/LC_MESSAGES/build/locales/api/enums.po +++ b/docs/locales/ja/LC_MESSAGES/build/locales/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Emoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Emoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Emoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Emoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/ko/LC_MESSAGES/api/enums.po b/docs/locales/ko/LC_MESSAGES/api/enums.po index 691d149de4..bc6647464e 100644 --- a/docs/locales/ko/LC_MESSAGES/api/enums.po +++ b/docs/locales/ko/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/pt_BR/LC_MESSAGES/api/enums.po b/docs/locales/pt_BR/LC_MESSAGES/api/enums.po index 72896957cb..f2c595a41a 100644 --- a/docs/locales/pt_BR/LC_MESSAGES/api/enums.po +++ b/docs/locales/pt_BR/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/ru/LC_MESSAGES/api/enums.po b/docs/locales/ru/LC_MESSAGES/api/enums.po index 1b6d5028bf..11b0c58e6a 100644 --- a/docs/locales/ru/LC_MESSAGES/api/enums.po +++ b/docs/locales/ru/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" diff --git a/docs/locales/zh_CN/LC_MESSAGES/api/enums.po b/docs/locales/zh_CN/LC_MESSAGES/api/enums.po index 691d149de4..bc6647464e 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/api/enums.po +++ b/docs/locales/zh_CN/LC_MESSAGES/api/enums.po @@ -656,8 +656,8 @@ msgstr "Changing the guild moderation settings" msgid "Changing things related to the guild widget" msgstr "Changing things related to the guild widget" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Guild`." msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr "Possible attributes for :class:`AuditLogDiff`:" @@ -704,8 +704,8 @@ msgstr ":attr:`~AuditLogDiff.vanity_url_code`" msgid "A new channel was created." msgstr "A new channel was created." -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 "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." @@ -725,8 +725,8 @@ msgstr "The channel name or topic was changed" msgid "The channel bitrate was changed" msgstr "The channel bitrate was changed" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." @@ -752,8 +752,8 @@ msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgid "A channel was deleted." msgstr "A channel was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with an ID." msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." @@ -776,8 +776,8 @@ msgstr ":attr:`~AuditLogDiff.id`" msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "A channel permission overwrite was changed, this is typically when the permission values change." -msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." -msgstr "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." +msgid "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." +msgstr "See :attr:`overwrite_create` for more information on how the :func:`~AuditLogEntry.get_target` and :attr:`~AuditLogEntry.extra` fields are set." msgid "A channel permission overwrite was deleted." msgstr "A channel permission overwrite was deleted." @@ -785,8 +785,8 @@ msgstr "A channel permission overwrite was deleted." msgid "A member was kicked." msgstr "A member was kicked." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got kicked." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got kicked." msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." @@ -794,8 +794,8 @@ msgstr "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgid "A member prune was triggered." msgstr "A member prune was triggered." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is set to ``None``." msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" @@ -809,14 +809,14 @@ msgstr "``members_removed``: An integer specifying how many members were removed msgid "A member was banned." msgstr "A member was banned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got banned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got banned." msgid "A member was unbanned." msgstr "A member was unbanned." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` who got unbanned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`User` who got unbanned." msgid "A member has updated. This triggers in the following situations:" msgstr "A member has updated. This triggers in the following situations:" @@ -827,8 +827,8 @@ msgstr "A nickname was changed" msgid "They were server muted or deafened (or it was undone)" msgstr "They were server muted or deafened (or it was undone)" -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got updated." msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" @@ -842,8 +842,8 @@ msgstr ":attr:`~AuditLogDiff.deaf`" msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "A member's role has been updated. This triggers when a member either gains a role or loses a role." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who got the role." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who got the role." msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" @@ -869,14 +869,14 @@ msgstr "``count``: An integer specifying how many members were disconnected." msgid "A bot was added to the guild." msgstr "A bot was added to the guild." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` which was added to the guild." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` which was added to the guild." msgid "A new role was created." msgstr "A new role was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Role` or a :class:`Object` with the ID." msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" @@ -911,8 +911,8 @@ msgstr "A role was deleted." msgid "An invite was created." msgstr "An invite was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was created." msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" @@ -938,20 +938,20 @@ msgstr ":attr:`~AuditLogDiff.max_uses`" msgid "An invite was updated." msgstr "An invite was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was updated." msgid "An invite was deleted." msgstr "An invite was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Invite` that was deleted." msgid "A webhook was created." msgstr "A webhook was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the webhook ID." msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" @@ -974,8 +974,8 @@ msgstr "A webhook was deleted." msgid "An emoji was created." msgstr "An emoji was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildEmoji` or :class:`Object` with the emoji ID." msgid "An emoji was updated. This triggers when the name has changed." msgstr "An emoji was updated. This triggers when the name has changed." @@ -983,14 +983,14 @@ msgstr "An emoji was updated. This triggers when the name has changed." msgid "An emoji was deleted." msgstr "An emoji was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the emoji ID." 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 "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message deleted." msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count``: An integer specifying how many messages were deleted." @@ -1001,14 +1001,14 @@ msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel msgid "Messages were bulk deleted by a moderator." msgstr "Messages were bulk deleted by a moderator." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgid "A message was pinned in a channel." msgstr "A message was pinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message pinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message pinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." @@ -1019,8 +1019,8 @@ msgstr "``message_id``: the ID of the message which was pinned." msgid "A message was unpinned in a channel." msgstr "A message was unpinned in a channel." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Member` or :class:`User` who had their message unpinned." msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." @@ -1031,26 +1031,26 @@ msgstr "``message_id``: the ID of the message which was unpinned." msgid "A guild integration was created." msgstr "A guild integration was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was created." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was created." msgid "A guild integration was updated." msgstr "A guild integration was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was updated." msgid "A guild integration was deleted." msgstr "A guild integration was deleted." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Object` with the integration ID of the integration which was deleted." msgid "A stage instance was started." msgstr "A stage instance was started." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" @@ -1058,8 +1058,8 @@ msgstr ":attr:`~AuditLogDiff.privacy_level`" msgid "A stage instance was updated." msgstr "A stage instance was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgid "A stage instance was ended." msgstr "A stage instance was ended." @@ -1067,8 +1067,8 @@ msgstr "A stage instance was ended." msgid "A sticker was created." msgstr "A sticker was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" @@ -1091,8 +1091,8 @@ msgstr "A sticker was deleted." msgid "A scheduled event was created." msgstr "A scheduled event was created." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the thread which was deleted." msgid ":attr:`~discord.ScheduledEvent.location`" msgstr ":attr:`~discord.ScheduledEvent.location`" @@ -1115,8 +1115,8 @@ msgstr "A scheduled event was deleted." msgid "A thread was created." msgstr "A thread was created." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" @@ -1133,20 +1133,20 @@ msgstr ":attr:`~AuditLogDiff.invitable`" msgid "A thread was updated." msgstr "A thread was updated." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgid "A thread was deleted." msgstr "A thread was deleted." -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 "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." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgid "An application command's permissions were updated." msgstr "An application command's permissions were updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is an :class:`Object` with the ID of the command that had it's permissions edited." msgid ":attr:`~AuditLogDiff.command_id`" msgstr ":attr:`~AuditLogDiff.command_id`" @@ -1199,8 +1199,8 @@ msgstr "The creator monetization terms were accepted." msgid "A voice channel status was updated." msgstr "A voice channel status was updated." -msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." -msgstr "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgid "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." +msgstr "When this is the action, the type of :func:`~AuditLogEntry.get_target` is the :class:`VoiceChannel` or :class:`Object` with the ID of the voice channel which was updated." msgid ":attr:`~AuditLogDiff.status`" msgstr ":attr:`~AuditLogDiff.status`" From 97dac811c359496359f83b543ceeb2cb22975fa6 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Thu, 15 May 2025 16:06:10 +0800 Subject: [PATCH 04/38] refactor!: another bulk of changes I need to make more commits --- discord/app/cache.py | 10 +- discord/app/{events.py => event_emitter.py} | 34 ++-- discord/app/state.py | 181 +------------------ discord/client.py | 5 +- discord/commands/core.py | 4 +- discord/enums.py | 9 + discord/events/automod.py | 75 ++++++++ discord/events/entitlement.py | 73 ++++++++ discord/events/gateway.py | 190 ++++++++++++++++++++ discord/events/message.py | 92 ++++++++++ discord/events/subscription.py | 71 ++++++++ discord/ext/commands/converter.py | 4 +- discord/gateway.py | 26 +-- discord/guild.py | 26 +-- discord/interactions.py | 4 +- discord/iterators.py | 12 +- discord/member.py | 11 +- discord/message.py | 6 +- discord/raw_models.py | 5 +- discord/shard.py | 12 +- discord/types/interactions.py | 1 + discord/ui/select.py | 9 +- 22 files changed, 601 insertions(+), 259 deletions(-) rename discord/app/{events.py => event_emitter.py} (77%) create mode 100644 discord/events/automod.py create mode 100644 discord/events/entitlement.py create mode 100644 discord/events/gateway.py create mode 100644 discord/events/message.py create mode 100644 discord/events/subscription.py diff --git a/discord/app/cache.py b/discord/app/cache.py index 05f2eb19b5..173f9fb515 100644 --- a/discord/app/cache.py +++ b/discord/app/cache.py @@ -211,7 +211,7 @@ async def get_user(self, user_id: int) -> User: async def get_all_stickers(self) -> list[GuildSticker]: return list(self._stickers.values()) - async def get_sticker(self, sticker_id: int) -> GuildSticker: + async def get_sticker(self, sticker_id: int) -> GuildSticker | None: return self._stickers.get(sticker_id) async def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: @@ -219,7 +219,7 @@ async def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildS try: self._stickers[guild.id].append(sticker) except KeyError: - self._stickers[guild.id] = sticker + self._stickers[guild.id] = [sticker] return sticker async def delete_sticker(self, sticker_id: int) -> None: @@ -228,10 +228,12 @@ async def delete_sticker(self, sticker_id: int) -> None: # interactions async def delete_view_on(self, message_id: int) -> View | None: - return self._views.pop(message_id, None) + for view in await self.get_all_views(): + if view.message and view.message.id == message_id: + return view async def store_view(self, view: View, message_id: int) -> None: - self._views[message_id or view.id] = view + self._views[str(message_id or view.id)] = view async def get_all_views(self) -> list[View]: return list(self._views.values()) diff --git a/discord/app/events.py b/discord/app/event_emitter.py similarity index 77% rename from discord/app/events.py rename to discord/app/event_emitter.py index bce0aaa3c3..561635352e 100644 --- a/discord/app/events.py +++ b/discord/app/event_emitter.py @@ -29,14 +29,14 @@ from .state import ConnectionState -T = TypeVar('T') +T = TypeVar('T', bound='Event') class Event(ABC): __event_name__: str @classmethod - async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self: + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: ... @@ -61,7 +61,7 @@ def add_listener(self, event: Type[Event], listener: Callable) -> None: self._listeners[event].append(listener) except KeyError: self.add_event(event) - self._listener[event] = [listener] + self._listeners[event] = [listener] def remove_listener(self, event: Type[Event], listener: Callable) -> None: self._listeners[event].remove(listener) @@ -79,21 +79,23 @@ def add_wait_for(self, event: Type[T]) -> Future[T]: def remove_wait_for(self, event: Type[Event], fut: Future) -> None: self._wait_fors[event].remove(fut) - async def publish(self, event_str: str, data: dict[str, Any]) -> None: - items = list(self.events.items()) + async def emit(self, event_str: str, data: Any) -> None: + events = self._events.get(event_str, []) - for event, funcs in items: - if event._name == event_str: - eve = event() + for event in events: + eve = await event.__load__(data=data, state=self._state) - await eve.__load__(data) + if eve is None: + continue - for func in funcs: - asyncio.create_task(func(eve)) + funcs = self._listeners.get(event, []) - wait_fors = self.wait_fors.get(event) + for func in funcs: + asyncio.create_task(func(eve)) - if wait_fors is not None: - for wait_for in wait_fors: - wait_for.set_result(eve) - self.wait_fors.pop(event) + wait_fors = self._wait_fors.get(event) + + if wait_fors is not None: + for wait_for in wait_fors: + wait_for.set_result(eve) + self._wait_fors.pop(event) diff --git a/discord/app/state.py b/discord/app/state.py index 00ec3c3253..38ef24183f 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -43,6 +43,8 @@ Union, ) +from discord.app.event_emitter import EventEmitter + from .cache import Cache from .. import utils @@ -186,6 +188,7 @@ def __init__( self.application_id: int | None = utils._get_as_snowflake( options, "application_id" ) + self.application_flags: ApplicationFlags | None = None self.heartbeat_timeout: float = options.get("heartbeat_timeout", 60.0) self.guild_ready_timeout: float = options.get("guild_ready_timeout", 2.0) if self.guild_ready_timeout < 0: @@ -256,10 +259,7 @@ def __init__( self.cache_app_emojis: bool = options.get("cache_app_emojis", False) - self.parsers = parsers = {} - for attr, func in inspect.getmembers(self): - if attr.startswith("parse_"): - parsers[attr[6:].upper()] = func + self.emitter = EventEmitter(self) self.cache: Cache = self.cache @@ -268,13 +268,13 @@ def clear(self, *, views: bool = True) -> None: self.cache.clear() self._voice_clients: dict[int, VoiceClient] = {} - def process_chunk_requests( + async def process_chunk_requests( self, guild_id: int, nonce: str | None, members: list[Member], complete: bool ) -> None: removed = [] for key, request in self._chunk_requests.items(): if request.guild_id == guild_id and request.nonce == nonce: - request.add_members(members) + await request.add_members(members) if complete: request.done() removed.append(key) @@ -524,175 +524,6 @@ async def query_members( ) raise - async def _delay_ready(self) -> None: - - if self.cache_app_emojis and self.application_id: - data = await self.http.get_all_application_emojis(self.application_id) - for e in data.get("items", []): - await self.maybe_store_app_emoji(self.application_id, e) - try: - states = [] - while True: - # this snippet of code is basically waiting N seconds - # until the last GUILD_CREATE was sent - try: - guild = await asyncio.wait_for( - self._ready_state.get(), timeout=self.guild_ready_timeout - ) - except asyncio.TimeoutError: - break - else: - if self._guild_needs_chunking(guild): - future = await self.chunk_guild(guild, wait=False) - states.append((guild, future)) - elif guild.unavailable is False: - self.dispatch("guild_available", guild) - else: - self.dispatch("guild_join", guild) - - for guild, future in states: - try: - await asyncio.wait_for(future, timeout=5.0) - except asyncio.TimeoutError: - _log.warning( - "Shard ID %s timed out waiting for chunks for guild_id %s.", - guild.shard_id, - guild.id, - ) - - if guild.unavailable is False: - self.dispatch("guild_available", guild) - else: - self.dispatch("guild_join", guild) - - # remove the state - try: - del self._ready_state - except AttributeError: - pass # already been deleted somehow - - except asyncio.CancelledError: - pass - else: - # dispatch the event - self.call_handlers("ready") - self.dispatch("ready") - finally: - self._ready_task = None - - def parse_ready(self, data) -> None: - if self._ready_task is not None: - self._ready_task.cancel() - - self._ready_state = asyncio.Queue() - self.clear(views=False) - self.user = ClientUser(state=self, data=data["user"]) - self.store_user(data["user"]) - - if self.application_id is None: - try: - application = data["application"] - except KeyError: - pass - else: - self.application_id = utils._get_as_snowflake(application, "id") - # flags will always be present here - self.application_flags = ApplicationFlags._from_value(application["flags"]) # type: ignore - - for guild_data in data["guilds"]: - self._add_guild_from_data(guild_data) - - self.dispatch("connect") - self._ready_task = asyncio.create_task(self._delay_ready()) - - def parse_resumed(self, data) -> None: - self.dispatch("resumed") - - def parse_application_command_permissions_update(self, data) -> None: - # unsure what the implementation would be like - pass - - def parse_auto_moderation_rule_create(self, data) -> None: - rule = AutoModRule(state=self, data=data) - self.dispatch("auto_moderation_rule_create", rule) - - def parse_auto_moderation_rule_update(self, data) -> None: - # somehow get a 'before' object? - rule = AutoModRule(state=self, data=data) - self.dispatch("auto_moderation_rule_update", rule) - - def parse_auto_moderation_rule_delete(self, data) -> None: - rule = AutoModRule(state=self, data=data) - self.dispatch("auto_moderation_rule_delete", rule) - - def parse_auto_moderation_action_execution(self, data) -> None: - event = AutoModActionExecutionEvent(self, data) - self.dispatch("auto_moderation_action_execution", event) - - def parse_entitlement_create(self, data) -> None: - event = Entitlement(data=data, state=self) - self.dispatch("entitlement_create", event) - - def parse_entitlement_update(self, data) -> None: - event = Entitlement(data=data, state=self) - self.dispatch("entitlement_update", event) - - def parse_entitlement_delete(self, data) -> None: - event = Entitlement(data=data, state=self) - self.dispatch("entitlement_delete", event) - - def parse_subscription_create(self, data) -> None: - event = Subscription(data=data, state=self) - self.dispatch("subscription_create", event) - - def parse_subscription_update(self, data) -> None: - event = Subscription(data=data, state=self) - self.dispatch("subscription_update", event) - - def parse_subscription_delete(self, data) -> None: - event = Subscription(data=data, state=self) - self.dispatch("subscription_delete", event) - - def parse_message_create(self, data) -> None: - channel, _ = self._get_guild_channel(data) - # channel would be the correct type here - message = Message(channel=channel, data=data, state=self) # type: ignore - self.dispatch("message", message) - if self._messages is not None: - self._messages.append(message) - # we ensure that the channel is either a TextChannel, VoiceChannel, StageChannel, or Thread - if channel and channel.__class__ in ( - TextChannel, - VoiceChannel, - StageChannel, - Thread, - ): - channel.last_message_id = message.id # type: ignore - - def parse_message_delete(self, data) -> None: - raw = RawMessageDeleteEvent(data) - found = self._get_message(raw.message_id) - raw.cached_message = found - self.dispatch("raw_message_delete", raw) - if self._messages is not None and found is not None: - self.dispatch("message_delete", found) - self._messages.remove(found) - - def parse_message_delete_bulk(self, data) -> None: - raw = RawBulkMessageDeleteEvent(data) - if self._messages: - found_messages = [ - message for message in self._messages if message.id in raw.message_ids - ] - else: - found_messages = [] - raw.cached_messages = found_messages - self.dispatch("raw_bulk_message_delete", raw) - if found_messages: - self.dispatch("bulk_message_delete", found_messages) - for msg in found_messages: - # self._messages won't be None here - self._messages.remove(msg) # type: ignore def parse_message_update(self, data) -> None: raw = RawMessageUpdateEvent(data) diff --git a/discord/client.py b/discord/client.py index 3a56fe8333..1765ff17f0 100644 --- a/discord/client.py +++ b/discord/client.py @@ -879,10 +879,9 @@ def intents(self) -> Intents: # helpers/getters - @property - def users(self) -> list[User]: + async def get_users(self) -> list[User]: """Returns a list of all the users the bot can see.""" - return list(self._connection._users.values()) + return await self._connection.cache.get_all_users() async def fetch_application(self, application_id: int, /) -> PartialAppInfo: """|coro| diff --git a/discord/commands/core.py b/discord/commands/core.py index 6afac89e8e..33cfe2e9b5 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -991,7 +991,7 @@ async def _invoke(self, ctx: ApplicationContext) -> None: # We resolved the user from the user id _data["user"] = _user_data cache_flag = ctx.interaction._state.member_cache_flags.interaction - arg = ctx.guild._get_and_update_member(_data, int(arg), cache_flag) + arg = await ctx.guild._get_and_update_member(_data, int(arg), cache_flag) elif op.input_type is SlashCommandOptionType.mentionable: if (_data := resolved.get("users", {}).get(arg)) is not None: arg = User(state=ctx.interaction._state, data=_data) @@ -1787,7 +1787,7 @@ async def _invoke(self, ctx: ApplicationContext) -> None: user = v member["user"] = user cache_flag = ctx.interaction._state.member_cache_flags.interaction - target = ctx.guild._get_and_update_member(member, user["id"], cache_flag) + target = await ctx.guild._get_and_update_member(member, user["id"], cache_flag) if self.cog is not None: await self.callback(self.cog, ctx, target) else: diff --git a/discord/enums.py b/discord/enums.py index e1086651e9..1de27dea9c 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -76,6 +76,7 @@ "EntitlementOwnerType", "IntegrationType", "InteractionContextType", + "ApplicationCommandPermissionType" ) @@ -1063,6 +1064,14 @@ class SubscriptionStatus(Enum): inactive = 2 +class ApplicationCommandPermissionType(Enum): + """The type of permission""" + + role = 1 + user = 2 + channel = 3 + + T = TypeVar("T") diff --git a/discord/events/automod.py b/discord/events/automod.py new file mode 100644 index 0000000000..252b3205b9 --- /dev/null +++ b/discord/events/automod.py @@ -0,0 +1,75 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 Any, Self +from discord.app.state import ConnectionState +from discord.automod import AutoModRule +from discord.raw_models import AutoModActionExecutionEvent +from ..app.event_emitter import Event + + +class AutoModRuleCreate(Event): + __event_name__ = "AUTO_MODERATION_RULE_CREATE" + + rule: AutoModRule + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + self.rule = AutoModRule(state=state, data=data) + return self + +class AutoModRuleUpdate(Event): + __event_name__ = "AUTO_MODERATION_RULE_UPDATE" + + rule: AutoModRule + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + self.rule = AutoModRule(state=state, data=data) + return self + +class AutoModRuleDelete(Event): + __event_name__ = "AUTO_MODERATION_RULE_DELETE" + + rule: AutoModRule + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + self.rule = AutoModRule(state=state, data=data) + return self + +class AutoModActionExecution(Event, AutoModActionExecutionEvent): + """Represents the `AUTO_MODERATION_ACTION_EXECUTION` event""" + + __event_name__ = "AUTO_MODERATION_ACTION_EXECUTION" + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + event = await AutoModActionExecutionEvent.from_data(state, data) + self.__dict__.update(event.__dict__) + return self diff --git a/discord/events/entitlement.py b/discord/events/entitlement.py new file mode 100644 index 0000000000..cdeb4dd7b2 --- /dev/null +++ b/discord/events/entitlement.py @@ -0,0 +1,73 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 Any, Self + +from discord.types.monetization import Entitlement as EntitlementPayload + +from ..monetization import Entitlement + +from ..app.state import ConnectionState +from ..app.event_emitter import Event + + +class EntitlementCreate(Event, Entitlement): + __event_name__ = "ENTITLEMENT_CREATE" + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + self.__dict__.update(Entitlement(data=data, state=state).__dict__) + return self + + +class EntitlementUpdate(Event, Entitlement): + __event_name__ = "ENTITLEMENT_UPDATE" + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + self.__dict__.update(Entitlement(data=data, state=state).__dict__) + return self + + +class EntitlementDelete(Event, Entitlement): + __event_name__ = "ENTITLEMENT_DELETE" + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + self.__dict__.update(Entitlement(data=data, state=state).__dict__) + return self + diff --git a/discord/events/gateway.py b/discord/events/gateway.py new file mode 100644 index 0000000000..763c0cb33b --- /dev/null +++ b/discord/events/gateway.py @@ -0,0 +1,190 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 Any, Self + +from discord import utils +from discord.emoji import Emoji +from discord.flags import ApplicationFlags +from discord.guild import Guild, GuildChannel +from discord.member import Member +from discord.role import Role +from discord.sticker import Sticker +from discord.user import ClientUser + +from ..app.state import ConnectionState +from ..app.event_emitter import Event +from ..types.interactions import ApplicationCommandPermissions as ApplicationCommandPermissionsPayload, GuildApplicationCommandPermissions +from ..types.guild import Guild as GuildPayload +from ..enums import ApplicationCommandPermissionType + + +class Resumed(Event): + __event_name__ = "RESUMED" + + +class Ready(Event): + __event_name__ = "READY" + + user: ClientUser + """An instance of :class:`.user.ClientUser` representing the application""" + application_id: int + """A snowflake of the application's id""" + application_flags: ApplicationFlags + """An instance of :class:`.flags.ApplicationFlags` representing the application flags""" + guilds: list[Guild] + """A list of guilds received in this event. Note it may have incomplete data as `GUILD_CREATE` fills up other parts of guild data.""" + + @classmethod + async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self: + self = cls() + self.user = ClientUser(state=state, data=data["user"]) + state.user = self.user + await state.store_user(data["user"]) + + if state.application_id is None: + try: + application = data["application"] + except KeyError: + pass + else: + self.application_id = utils._get_as_snowflake(application, "id") # type: ignore + # flags will always be present here + self.application_flags = ApplicationFlags._from_value(application["flags"]) # type: ignore + state.application_id = self.application_id + state.application_flags = self.application_flags + + self.guilds = [] + + for guild_data in data["guilds"]: + guild = await Guild(data=guild_data, state=state)._from_data(guild_data) + self.guilds.append(guild) + await state._add_guild(guild) + + await state.emitter.emit("CACHE_APP_EMOJIS", None) + + return self + +class _CacheAppEmojis(Event): + __event_name__ = "CACHE_APP_EMOJIS" + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + if state.cache_app_emojis and state.application_id: + data = await state.http.get_all_application_emojis(state.application_id) + for e in data.get("items", []): + await state.maybe_store_app_emoji(state.application_id, e) + +class GuildCreate(Event, Guild): + """An event which represents a guild becoming available via the gateway. Trickles down to the more distinct :class:`.GuildJoin` and :class:`.GuildAvailable` events.""" + + __event_name__ = "GUILD_CREATE" + + guild: Guild + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: GuildPayload, state: ConnectionState) -> Self: + self = cls() + guild = await state._get_guild(int(data["id"])) + if guild is None: + guild = await Guild(data=data, state=state)._from_data(data) + await state._add_guild(guild) + self.guild = guild + self.__dict__.update(self.guild.__dict__) + if state._guild_needs_chunking(guild): + await state.chunk_guild(guild) + if guild.unavailable: + await state.emitter.emit("GUILD_JOIN", guild) + else: + await state.emitter.emit("GUILD_AVAILABLE", guild) + return self + +class GuildJoin(Event, Guild): + """An event which represents joining a new guild.""" + + __event_name__ = "GUILD_JOIN" + + guild: Guild + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: Guild, _: ConnectionState) -> Self: + self = cls() + self.guild = data + self.__dict__.update(self.guild.__dict__) + return self + +class GuildAvailable(Event, Guild): + """An event which represents a guild previously joined becoming available.""" + + __event_name__ = "GUILD_AVAILABLE" + + guild: Guild + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: Guild, _: ConnectionState) -> Self: + self = cls() + self.guild = data + self.__dict__.update(self.guild.__dict__) + return self + +class ApplicationCommandPermission: + def __init__(self, data: ApplicationCommandPermissionsPayload) -> None: + self.id = int(data["id"]) + """The id of the user, role, or channel affected by this permission""" + self.type = ApplicationCommandPermissionType(data["type"]) + """Represents what this permission affects""" + self.permission = data["permission"] + """Represents whether the permission is allowed or denied""" + +class ApplicationCommandPermissionsUpdate(Event): + """Represents an Application Command having permissions updated in a guild""" + + __event_name__ = "APPLICATION_COMMAND_PERMISSIONS_UPDATE" + + id: int + """A snowflake of the application command's id""" + application_id: int + """A snowflake of the application's id""" + guild_id: int + """A snowflake of the guild's id where the permissions have been updated""" + permissions: list[ApplicationCommandPermission] + """A list of permissions this Application Command has""" + + @classmethod + async def __load__(cls, data: GuildApplicationCommandPermissions, state: ConnectionState) -> Self: + self = cls() + self.id = int(data["id"]) + self.application_id = int(data["application_id"]) + self.guild_id = int(data["guild_id"]) + self.permissions = [ApplicationCommandPermission(data) for data in data["permissions"]] + return self diff --git a/discord/events/message.py b/discord/events/message.py new file mode 100644 index 0000000000..8ccf67dce8 --- /dev/null +++ b/discord/events/message.py @@ -0,0 +1,92 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 Any, Self +from discord.app.state import ConnectionState +from discord.channel import StageChannel, TextChannel, VoiceChannel +from discord.raw_models import RawBulkMessageDeleteEvent, RawMessageDeleteEvent +from discord.threads import Thread +from ..message import Message +from ..app.event_emitter import Event + + +class MessageCreate(Event, Message): + __event_name__ = "MESSAGE_CREATE" + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + channel, _ = await state._get_guild_channel(data) + message = await Message._from_data(channel=channel, data=data, state=state) + self = cls() + self.__dict__.update(message.__dict__) + + await state.cache.store_message(data, channel) + # we ensure that the channel is either a TextChannel, VoiceChannel, StageChannel, or Thread + if channel and channel.__class__ in ( + TextChannel, + VoiceChannel, + StageChannel, + Thread, + ): + channel.last_message_id = message.id # type: ignore + + return self + + +class MessageDelete(Event, Message): + __event_name__ = "MESSAGE_DELETE" + + raw: RawMessageDeleteEvent + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + self = cls() + raw = RawMessageDeleteEvent(data) + msg = await state._get_message(raw.message_id) + raw.cached_message = msg + self.raw = raw + if msg is not None: + await state.cache.delete_message(raw.message_id) + self.__dict__.update(msg.__dict__) + + return self + + +class MessageDeleteBulk(Event): + __event_name__ = "MESSAGE_DELETE_BULK" + + raw: RawBulkMessageDeleteEvent + messages: list[Message] + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self : + self = cls() + raw = RawBulkMessageDeleteEvent(data) + messages = await state.cache.get_all_messages() + found_messages = [message for message in messages if message.id in raw.message_ids] + raw.cached_messages = found_messages + self.messages = found_messages + for message in messages: + await state.cache.delete_message(message.id) + return self diff --git a/discord/events/subscription.py b/discord/events/subscription.py new file mode 100644 index 0000000000..cad2038074 --- /dev/null +++ b/discord/events/subscription.py @@ -0,0 +1,71 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 Any, Self + +from discord.types.monetization import Entitlement as EntitlementPayload + +from ..monetization import Subscription + +from ..app.state import ConnectionState +from ..app.event_emitter import Event + + +class SubscriptionCreate(Event, Subscription): + __event_name__ = "SUBSCRIPTION_CREATE" + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + self.__dict__.update(Subscription(data=data, state=state).__dict__) + return self + + +class SubscriptionUpdate(Event, Subscription): + __event_name__ = "SUBSCRIPTION_UPDATE" + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + self.__dict__.update(Subscription(data=data, state=state).__dict__) + return self + + +class SubscriptionDelete(Event, Subscription): + __event_name__ = "SUBSCRIPTION_DELETE" + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + self.__dict__.update(Subscription(data=data, state=state).__dict__) + return self diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index 83d61c0353..52a4353576 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -316,12 +316,12 @@ async def convert(self, ctx: Context, argument: str) -> discord.User: discrim = arg[-4:] name = arg[:-5] predicate = lambda u: u.name == name and u.discriminator == discrim - result = discord.utils.find(predicate, state._users.values()) + result = discord.utils.find(predicate, await state.cache.get_all_users()) if result is not None: return result predicate = lambda u: arg in (u.name, u.global_name) - result = discord.utils.find(predicate, state._users.values()) + result = discord.utils.find(predicate, await state.cache.get_all_users()) if result is None: raise UserNotFound(argument) diff --git a/discord/gateway.py b/discord/gateway.py index 4af59f3864..f390a44561 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -289,10 +289,6 @@ def __init__(self, socket, *, loop): self.socket = socket self.loop = loop - # an empty dispatcher to prevent crashes - self._dispatch = lambda *args: None - # generic event listeners - self._dispatch_listeners = [] # the keep alive self._keep_alive = None self.thread_id = threading.get_ident() @@ -313,10 +309,10 @@ def open(self): def is_ratelimited(self): return self._rate_limiter.is_ratelimited() - def debug_log_receive(self, data, /): - self._dispatch("socket_raw_receive", data) + async def debug_log_receive(self, data, /): + await self._emitter.emit("socket_raw_receive", data) - def log_receive(self, _, /): + async def log_receive(self, _, /): pass @classmethod @@ -342,8 +338,7 @@ async def from_client( # dynamically add attributes needed ws.token = client.http.token ws._connection = client._connection - ws._discord_parsers = client._connection.parsers - ws._dispatch = client.dispatch + ws._emitter = client._connection.emitter ws.gateway = gateway ws.call_hooks = client._connection.call_hooks ws._initial_identify = initial @@ -461,13 +456,13 @@ async def received_message(self, msg, /): msg = msg.decode("utf-8") self._buffer = bytearray() - self.log_receive(msg) + await self.log_receive(msg) msg = utils._from_json(msg) _log.debug("For Shard ID %s: WebSocket Event: %s", self.shard_id, msg) event = msg.get("t") if event: - self._dispatch("socket_event_type", event) + await self._emitter.emit("socket_event_type", event) op = msg.get("op") data = msg.get("d") @@ -547,12 +542,7 @@ async def received_message(self, msg, /): ", ".join(trace), ) - try: - func = self._discord_parsers[event] - except KeyError: - _log.debug("Unknown event %s.", event) - else: - func(data) + await self._emitter.emit(event, data) # remove the dispatched listeners removed = [] @@ -644,7 +634,7 @@ async def poll_event(self): async def debug_send(self, data, /): await self._rate_limiter.block() - self._dispatch("socket_raw_send", data) + await self._emitter.emit("socket_raw_send", data) await self.socket.send_str(data) async def send(self, data, /): diff --git a/discord/guild.py b/discord/guild.py index 7214f51575..79ae438a22 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -35,6 +35,7 @@ List, NamedTuple, Optional, + Self, Sequence, Tuple, Union, @@ -309,7 +310,6 @@ def __init__(self, *, data: GuildPayload, state: ConnectionState): self._voice_states: dict[int, VoiceState] = {} self._threads: dict[int, Thread] = {} self._state: ConnectionState = state - asyncio.create_task(self._from_data(data)) def _add_channel(self, channel: GuildChannel, /) -> None: self._channels[channel.id] = channel @@ -323,20 +323,20 @@ def _voice_state_for(self, user_id: int, /) -> VoiceState | None: def _add_member(self, member: Member, /) -> None: self._members[member.id] = member - def _get_and_update_member( + async def _get_and_update_member( self, payload: MemberPayload, user_id: int, cache_flag: bool, / ) -> Member: # we always get the member, and we only update if the cache_flag (this cache # flag should always be MemberCacheFlag.interaction) is set to True if user_id in self._members: member = self.get_member(user_id) - member._update(payload) if cache_flag else None + await member._update(payload) if cache_flag else None else: # NOTE: # This is a fallback in case the member is not found in the guild's members. # If this fallback occurs, multiple aspects of the Member # class will be incorrect such as status and activities. - member = Member(guild=self, state=self._state, data=payload) # type: ignore + member = await Member._from_data(guild=self, state=self._state, data=payload) # type: ignore if cache_flag: self._members[user_id] = member return member @@ -396,7 +396,7 @@ def __repr__(self) -> str: inner = " ".join("%s=%r" % t for t in attrs) return f"" - def _update_voice_state( + async def _update_voice_state( self, data: GuildVoiceState, channel_id: int ) -> tuple[Member | None, VoiceState, VoiceState]: user_id = int(data["user_id"]) @@ -409,7 +409,7 @@ def _update_voice_state( after = self._voice_states[user_id] before = copy.copy(after) - after._update(data, channel) + await after._update(data, channel) except KeyError: # if we're here then we're getting added into the cache after = VoiceState(data=data, channel=channel) @@ -419,7 +419,7 @@ def _update_voice_state( member = self.get_member(user_id) if member is None: try: - member = Member(data=data["member"], state=self._state, guild=self) + member = await Member._from_data(data=data["member"], state=self._state, guild=self) except KeyError: member = None @@ -448,7 +448,7 @@ def _remove_role(self, role_id: int, /) -> Role: return role - async def _from_data(self, guild: GuildPayload) -> None: + async def _from_data(self, guild: GuildPayload) -> Self: member_count = guild.get("member_count") # Either the payload includes member_count, or it hasn't been set yet. # Prevents valid _member_count from suddenly changing to None @@ -522,7 +522,7 @@ async def _from_data(self, guild: GuildPayload) -> None: cache_joined = self._state.member_cache_flags.joined self_id = self._state.self_id for mdata in guild.get("members", []): - member = Member(data=mdata, guild=self, state=state) + member = await Member._from_data(data=mdata, guild=self, state=state) if cache_joined or member.id == self_id: self._add_member(member) @@ -551,7 +551,9 @@ async def _from_data(self, guild: GuildPayload) -> None: ) # type: ignore for obj in guild.get("voice_states", []): - self._update_voice_state(obj, int(obj["channel_id"])) + await self._update_voice_state(obj, int(obj["channel_id"])) + + return self # TODO: refactor/remove? def _sync(self, data: GuildPayload) -> None: @@ -2059,7 +2061,7 @@ async def search_members(self, query: str, *, limit: int = 1000) -> list[Member] """ data = await self._state.http.search_members(self.id, query, limit) - return [Member(data=m, guild=self, state=self._state) for m in data] + return [await Member._from_data(data=m, guild=self, state=self._state) for m in data] async def fetch_member(self, member_id: int, /) -> Member: """|coro| @@ -2089,7 +2091,7 @@ async def fetch_member(self, member_id: int, /) -> Member: Fetching the member failed. """ data = await self._state.http.get_member(self.id, member_id) - return Member(data=data, state=self._state, guild=self) + return await Member._from_data(data=data, state=self._state, guild=self) async def fetch_ban(self, user: Snowflake) -> BanEntry: """|coro| diff --git a/discord/interactions.py b/discord/interactions.py index 3cfb89bb7f..157f32025d 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -252,11 +252,11 @@ async def load_data(self): self._permissions = int(member.get("permissions", 0)) if not isinstance(guild, Object): cache_flag = self._state.member_cache_flags.interaction - self.user = guild._get_and_update_member( + self.user = await guild._get_and_update_member( member, int(member["user"]["id"]), cache_flag ) else: - self.user = Member(state=self._state, data=member, guild=guild) + self.user = await Member._from_data(state=self._state, data=member, guild=guild) else: try: self.user = User(state=self._state, data=data["user"]) diff --git a/discord/iterators.py b/discord/iterators.py index 193b2ac078..10bf437c7c 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -730,12 +730,12 @@ async def fill_members(self): self.after = Object(id=int(data[-1]["user"]["id"])) for element in reversed(data): - await self.members.put(self.create_member(element)) + await self.members.put(await self.create_member(element)) - def create_member(self, data): + async def create_member(self, data): from .member import Member - return Member(data=data, guild=self.guild, state=self.state) + return await Member._from_data(data=data, guild=self.guild, state=self.state) class BanIterator(_AsyncIterator["BanEntry"]): @@ -928,7 +928,7 @@ def _get_retrieve(self): self.retrieve = r return r > 0 - def member_from_payload(self, data): + async def member_from_payload(self, data): from .member import Member user = data.pop("user") @@ -936,7 +936,7 @@ def member_from_payload(self, data): member = data.pop("member") member["user"] = user - return Member(data=member, guild=self.event.guild, state=self.event._state) + return await Member._from_data(data=member, guild=self.event.guild, state=self.event._state) def user_from_payload(self, data): from .user import User @@ -970,7 +970,7 @@ async def fill_subs(self): for element in reversed(data): if "member" in element: - await self.subscribers.put(self.member_from_payload(element)) + await self.subscribers.put(await self.member_from_payload(element)) else: await self.subscribers.put(self.user_from_payload(element)) diff --git a/discord/member.py b/discord/member.py index 723424d171..f45d591204 100644 --- a/discord/member.py +++ b/discord/member.py @@ -30,7 +30,7 @@ import itertools import sys from operator import attrgetter -from typing import TYPE_CHECKING, Any, TypeVar, Union +from typing import TYPE_CHECKING, Any, Self, TypeVar, Union import discord.abc @@ -315,7 +315,6 @@ def __init__( self, *, data: MemberWithUserPayload, guild: Guild, state: ConnectionState ): self._state: ConnectionState = state - self._user: User = state.store_user(data["user"]) self.guild: Guild = guild self.joined_at: datetime.datetime | None = utils.parse_time( data.get("joined_at") @@ -335,6 +334,12 @@ def __init__( ) self.flags: MemberFlags = MemberFlags._from_value(data.get("flags", 0)) + @classmethod + async def _from_data(cls, data: MemberWithUserPayload, guild: Guild, state: ConnectionState) -> Self: + self = cls(data=data, guild=guild, state=state) + self._user = await state.store_user(data["user"]) + return self + def __str__(self) -> str: return str(self._user) @@ -912,7 +917,7 @@ async def edit( if payload: data = await http.edit_member(guild_id, self.id, reason=reason, **payload) - return Member(data=data, guild=self.guild, state=self._state) + return await Member._from_data(data=data, guild=self.guild, state=self._state) async def timeout( self, until: datetime.datetime | None, *, reason: str | None = None diff --git a/discord/message.py b/discord/message.py index 7688131342..891adc44ba 100644 --- a/discord/message.py +++ b/discord/message.py @@ -1027,7 +1027,7 @@ async def _update(self, data): else: r = handler(self, value) if isawaitable(r): - await r + await r # type: ignore # clear the cached properties for attr in self._CACHED_SLOTS: @@ -1099,12 +1099,12 @@ def _handle_member(self, member: MemberPayload) -> None: # TODO: consider adding to cache here self.author = Member._from_message(message=self, data=member) - def _handle_mentions(self, mentions: list[UserWithMemberPayload]) -> None: + async def _handle_mentions(self, mentions: list[UserWithMemberPayload]) -> None: self.mentions = r = [] guild = self.guild state = self._state if not isinstance(guild, Guild): - self.mentions = [state.store_user(m) for m in mentions] + self.mentions = [await state.store_user(m) for m in mentions] return for mention in filter(None, mentions): diff --git a/discord/raw_models.py b/discord/raw_models.py index 09c7ffdd78..43a8230837 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -26,7 +26,7 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Self from .automod import AutoModAction, AutoModTriggerType from .enums import AuditLogAction, ChannelType, ReactionType, try_enum @@ -655,7 +655,7 @@ class AutoModActionExecutionEvent: ) @classmethod - async def from_data(cls, state: ConnectionState, data: AutoModActionExecution) -> None: + async def from_data(cls, state: ConnectionState, data: AutoModActionExecution) -> Self: self = cls() self.action: AutoModAction = AutoModAction.from_dict(data["action"]) self.rule_id: int = int(data["rule_id"]) @@ -703,6 +703,7 @@ async def from_data(cls, state: ConnectionState, data: AutoModActionExecution) - self.alert_system_message_id: int | None = None self.alert_system_message: Message | None = None self.data: AutoModActionExecution = data + return self def __repr__(self) -> str: return ( diff --git a/discord/shard.py b/discord/shard.py index 4f57673263..b8fefd7231 100644 --- a/discord/shard.py +++ b/discord/shard.py @@ -99,7 +99,7 @@ def __init__( ) -> None: self.ws: DiscordWebSocket = ws self._client: Client = client - self._dispatch: Callable[..., None] = client.dispatch + self._dispatch: Callable[..., None] = client._connection.emitter.emit self._queue_put: Callable[[EventItem], None] = queue_put self.loop: asyncio.AbstractEventLoop = self._client.loop self._disconnect: bool = False @@ -133,11 +133,11 @@ async def close(self) -> None: async def disconnect(self) -> None: await self.close() - self._dispatch("shard_disconnect", self.id) + await self._dispatch("shard_disconnect", self.id) async def _handle_disconnect(self, e: Exception) -> None: - self._dispatch("disconnect") - self._dispatch("shard_disconnect", self.id) + await self._dispatch("disconnect") + await self._dispatch("shard_disconnect", self.id) if not self._reconnect: self._queue_put(EventItem(EventType.close, self, e)) return @@ -192,8 +192,8 @@ async def worker(self) -> None: async def reidentify(self, exc: ReconnectWebSocket) -> None: self._cancel_task() - self._dispatch("disconnect") - self._dispatch("shard_disconnect", self.id) + await self._dispatch("disconnect") + await self._dispatch("shard_disconnect", self.id) _log.info("Got a request to %s the websocket at Shard ID %s.", exc.op, self.id) try: coro = DiscordWebSocket.from_client( diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 37904a580a..5f4b9f1f69 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -25,6 +25,7 @@ from __future__ import annotations +from turtle import st from typing import TYPE_CHECKING, Dict, Literal, Union from ..permissions import Permissions diff --git a/discord/ui/select.py b/discord/ui/select.py index d9b07e0ece..09e6acaf6e 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -69,7 +69,7 @@ class Select(Item[V]): This is usually represented as a drop down menu. - In order to get the selected items that the user has chosen, use :attr:`Select.values`. + In order to get the selected items that the user has chosen, use :meth:`Select.get_values`. .. versionadded:: 2.0 @@ -325,8 +325,7 @@ def append_option(self, option: SelectOption): self._underlying.options.append(option) - @property - def values( + async def get_values( self, ) -> ( list[str] @@ -388,7 +387,7 @@ def values( member = dict(_member_data) member["user"] = _data _data = member - result = guild._get_and_update_member( + result = await guild._get_and_update_member( _data, int(_id), cache_flag ) else: @@ -466,7 +465,7 @@ def select( the :class:`discord.Interaction` you receive. In order to get the selected items that the user has chosen within the callback - use :attr:`Select.values`. + use :meth:`Select.get_values`. .. versionchanged:: 2.3 From d0c2eb563520a83ba0406d79247d6d7c7fd67995 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Fri, 16 May 2025 02:57:05 +0800 Subject: [PATCH 05/38] feat: message events --- discord/app/cache.py | 8 ++ discord/app/state.py | 172 +------------------------- discord/events/gateway.py | 4 + discord/events/message.py | 252 +++++++++++++++++++++++++++++++++++++- discord/message.py | 114 +---------------- 5 files changed, 270 insertions(+), 280 deletions(-) diff --git a/discord/app/cache.py b/discord/app/cache.py index 173f9fb515..d3ccfe4895 100644 --- a/discord/app/cache.py +++ b/discord/app/cache.py @@ -152,6 +152,9 @@ async def store_private_channel(self, channel: PrivateChannel) -> None: async def store_message(self, message: MessagePayload, channel: MessageableChannel) -> Message: ... + async def upsert_message(self, message: Message) -> None: + ... + async def delete_message(self, message_id: int) -> None: ... @@ -332,8 +335,13 @@ async def get_private_channel_by_user(self, user_id: int) -> PrivateChannel | No # messages + async def upsert_message(self, message: Message) -> None: + self._messages.append(message) + async def store_message(self, message: MessagePayload, channel: MessageableChannel) -> Message: msg = await Message._from_data(state=self._state, channel=channel, data=message) + self._messages.append(msg) + return msg async def get_message(self, message_id: int) -> Message | None: return utils.find(lambda m: m.id == message_id, reversed(self._messages)) diff --git a/discord/app/state.py b/discord/app/state.py index 38ef24183f..7a5b11a9b7 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -524,163 +524,6 @@ async def query_members( ) raise - - def parse_message_update(self, data) -> None: - raw = RawMessageUpdateEvent(data) - message = self._get_message(raw.message_id) - if message is not None: - older_message = copy.copy(message) - raw.cached_message = older_message - self.dispatch("raw_message_edit", raw) - 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) - else: - if poll_data := data.get("poll"): - self.store_raw_poll(poll_data, raw) - self.dispatch("raw_message_edit", raw) - - if "components" in data and self._view_store.is_message_tracked(raw.message_id): - self._view_store.update_from_message(raw.message_id, data["components"]) - - def parse_message_reaction_add(self, data) -> None: - emoji = data["emoji"] - emoji_id = utils._get_as_snowflake(emoji, "id") - emoji = PartialEmoji.with_state( - self, id=emoji_id, animated=emoji.get("animated", False), name=emoji["name"] - ) - raw = RawReactionActionEvent(data, emoji, "REACTION_ADD") - - member_data = data.get("member") - if member_data: - guild = self._get_guild(raw.guild_id) - if guild is not None: - raw.member = Member(data=member_data, guild=guild, state=self) - else: - raw.member = None - else: - raw.member = None - self.dispatch("raw_reaction_add", raw) - - # rich interface here - message = self._get_message(raw.message_id) - if message is not None: - emoji = self._upgrade_partial_emoji(emoji) - reaction = message._add_reaction(data, emoji, raw.user_id) - user = raw.member or self._get_reaction_user(message.channel, raw.user_id) - - if user: - self.dispatch("reaction_add", reaction, user) - - def parse_message_reaction_remove_all(self, data) -> None: - raw = RawReactionClearEvent(data) - self.dispatch("raw_reaction_clear", raw) - - message = self._get_message(raw.message_id) - if message is not None: - old_reactions = message.reactions.copy() - message.reactions.clear() - self.dispatch("reaction_clear", message, old_reactions) - - def parse_message_reaction_remove(self, data) -> None: - emoji = data["emoji"] - emoji_id = utils._get_as_snowflake(emoji, "id") - emoji = PartialEmoji.with_state(self, id=emoji_id, name=emoji["name"]) - raw = RawReactionActionEvent(data, emoji, "REACTION_REMOVE") - - member_data = data.get("member") - if member_data: - guild = self._get_guild(raw.guild_id) - if guild is not None: - raw.member = Member(data=member_data, guild=guild, state=self) - else: - raw.member = None - else: - raw.member = None - - self.dispatch("raw_reaction_remove", raw) - - message = self._get_message(raw.message_id) - if message is not None: - emoji = self._upgrade_partial_emoji(emoji) - try: - reaction = message._remove_reaction(data, emoji, raw.user_id) - except (AttributeError, ValueError): # eventual consistency lol - pass - else: - user = self._get_reaction_user(message.channel, raw.user_id) - if user: - self.dispatch("reaction_remove", reaction, user) - - def parse_message_reaction_remove_emoji(self, data) -> None: - emoji = data["emoji"] - emoji_id = utils._get_as_snowflake(emoji, "id") - emoji = PartialEmoji.with_state(self, id=emoji_id, name=emoji["name"]) - raw = RawReactionClearEmojiEvent(data, emoji) - self.dispatch("raw_reaction_clear_emoji", raw) - - message = self._get_message(raw.message_id) - if message is not None: - try: - reaction = message._clear_emoji(emoji) - except (AttributeError, ValueError): # eventual consistency lol - pass - else: - if reaction: - self.dispatch("reaction_clear_emoji", reaction) - - def parse_message_poll_vote_add(self, data) -> None: - raw = RawMessagePollVoteEvent(data, True) - guild = self._get_guild(raw.guild_id) - if guild: - user = guild.get_member(raw.user_id) - else: - user = self.get_user(raw.user_id) - self.dispatch("raw_poll_vote_add", raw) - - self._get_message(raw.message_id) - poll = self.get_poll(raw.message_id) - # if message was cached, poll has already updated but votes haven't - if poll and poll.results: - answer = poll.get_answer(raw.answer_id) - counts = poll.results._answer_counts - if answer is not None: - if answer.id in counts: - counts[answer.id].count += 1 - else: - counts[answer.id] = PollAnswerCount( - {"id": answer.id, "count": 1, "me_voted": False} - ) - if poll is not None and user is not None: - answer = poll.get_answer(raw.answer_id) - if answer is not None: - self.dispatch("poll_vote_add", poll, user, answer) - - def parse_message_poll_vote_remove(self, data) -> None: - raw = RawMessagePollVoteEvent(data, False) - guild = self._get_guild(raw.guild_id) - if guild: - user = guild.get_member(raw.user_id) - else: - user = self.get_user(raw.user_id) - self.dispatch("raw_poll_vote_remove", raw) - - self._get_message(raw.message_id) - poll = self.get_poll(raw.message_id) - # if message was cached, poll has already updated but votes haven't - if poll and poll.results: - answer = poll.get_answer(raw.answer_id) - counts = poll.results._answer_counts - if answer is not None: - if answer.id in counts: - counts[answer.id].count -= 1 - if poll is not None and user is not None: - answer = poll.get_answer(raw.answer_id) - if answer is not None: - self.dispatch("poll_vote_remove", poll, user, answer) - def parse_interaction_create(self, data) -> None: interaction = Interaction(data=data, state=self) if data["type"] == 3: # interaction component @@ -1711,21 +1554,21 @@ def _get_typing_user( return self.get_user(user_id) - def _get_reaction_user( + async def _get_reaction_user( self, channel: MessageableChannel, user_id: int ) -> User | Member | None: if isinstance(channel, TextChannel): return channel.guild.get_member(user_id) - return self.get_user(user_id) + return await self.get_user(user_id) - def get_reaction_emoji(self, data) -> GuildEmoji | AppEmoji | PartialEmoji: + async def get_reaction_emoji(self, data) -> GuildEmoji | AppEmoji | PartialEmoji: emoji_id = utils._get_as_snowflake(data, "id") if not emoji_id: return data["name"] try: - return self._emojis[emoji_id] + return await self.cache.get_emoji(emoji_id) except KeyError: return PartialEmoji.with_state( self, @@ -1734,16 +1577,13 @@ def get_reaction_emoji(self, data) -> GuildEmoji | AppEmoji | PartialEmoji: name=data["name"], ) - def _upgrade_partial_emoji( + async def _upgrade_partial_emoji( self, emoji: PartialEmoji ) -> GuildEmoji | AppEmoji | PartialEmoji | str: emoji_id = emoji.id if not emoji_id: return emoji.name - try: - return self._emojis[emoji_id] - except KeyError: - return emoji + return await self.cache.get_emoji(emoji_id) or emoji async def get_channel(self, id: int | None) -> Channel | Thread | None: if id is None: diff --git a/discord/events/gateway.py b/discord/events/gateway.py index 763c0cb33b..364259808c 100644 --- a/discord/events/gateway.py +++ b/discord/events/gateway.py @@ -43,6 +43,10 @@ class Resumed(Event): __event_name__ = "RESUMED" + @classmethod + async def __load__(cls, _data: Any, _state: ConnectionState) -> Self | None: + return cls() + class Ready(Event): __event_name__ = "READY" diff --git a/discord/events/message.py b/discord/events/message.py index 8ccf67dce8..d2b6cf7111 100644 --- a/discord/events/message.py +++ b/discord/events/message.py @@ -23,11 +23,22 @@ """ from typing import Any, Self +from discord import utils from discord.app.state import ConnectionState from discord.channel import StageChannel, TextChannel, VoiceChannel -from discord.raw_models import RawBulkMessageDeleteEvent, RawMessageDeleteEvent +from discord.emoji import AppEmoji, GuildEmoji +from discord.guild import Guild +from discord.member import Member +from discord.partial_emoji import PartialEmoji +from discord.poll import Poll, PollAnswer, PollAnswerCount +from discord.raw_models import RawBulkMessageDeleteEvent, RawMessageDeleteEvent, RawMessagePollVoteEvent, RawMessageUpdateEvent, RawReactionActionEvent, RawReactionClearEmojiEvent, RawReactionClearEvent +from discord.reaction import Reaction from discord.threads import Thread -from ..message import Message +from discord.types.message import Reaction as ReactionPayload +from discord.types.raw_models import ReactionActionEvent, ReactionClearEvent +from discord.user import User +from discord.utils import Undefined +from ..message import Message, PartialMessage from ..app.event_emitter import Event @@ -90,3 +101,240 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self : for message in messages: await state.cache.delete_message(message.id) return self + + +class MessageUpdate(Event, Message): + __event_name__ = "MESSAGE_UPDATE" + + raw: RawMessageUpdateEvent + old: Message | Undefined + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self: + self = cls() + raw = RawMessageUpdateEvent(data) + msg = await state._get_message(raw.message_id) + raw.cached_message = msg + self.raw = raw + if msg is not None: + new_msg = await state.cache.store_message(data, msg.channel) + self.old = msg + self.old.author = new_msg.author + self.__dict__.update(new_msg.__dict__) + else: + self.old = utils.MISSING + if poll_data := data.get("poll"): + channel = await state.get_channel(raw.channel_id) + await state.store_poll(Poll.from_dict(poll_data, PartialMessage(channel=channel, id=raw.message_id)), message_id=raw.message_id) + + return self + + +class ReactionAdd(Event): + __event_name__ = "MESSAGE_REACTION_ADD" + + raw: RawReactionActionEvent + user: Member | User | Undefined + reaction: Reaction + + @classmethod + async def __load__(cls, data: ReactionActionEvent, state: ConnectionState) -> Self: + self = cls() + emoji = data["emoji"] + emoji_id = utils._get_as_snowflake(emoji, "id") + emoji = PartialEmoji.with_state( + state, id=emoji_id, animated=emoji.get("animated", False), name=emoji["name"] + ) + raw = RawReactionActionEvent(data, emoji, "REACTION_ADD") + + member_data = data.get("member") + if member_data: + guild = await state._get_guild(raw.guild_id) + if guild is not None: + raw.member = await Member._from_data(data=member_data, guild=guild, state=state) + else: + raw.member = None + else: + raw.member = None + + message = await state._get_message(raw.message_id) + if message is not None: + emoji = await state._upgrade_partial_emoji(emoji) + self.reaction = message._add_reaction(data, emoji, raw.user_id) + await state.cache.upsert_message(message) + user = raw.member or await state._get_reaction_user(message.channel, raw.user_id) + + if user: + self.user = user + else: + self.user = utils.MISSING + + return self + +class ReactionClear(Event): + __event_name__ = "MESSAGE_REACTION_REMOVE_ALL" + + raw: RawReactionClearEvent + message: Message | Undefined + old_reactions: list[Reaction] | Undefined + + @classmethod + async def __load__(cls, data: ReactionClearEvent, state: ConnectionState) -> Self | None: + self = cls() + self.raw = RawReactionClearEvent(data) + message = await state._get_message(self.raw.message_id) + if message is not None: + old_reactions: list[Reaction] = message.reactions.copy() + message.reactions.clear() + self.message = message + self.old_reactions = old_reactions + else: + self.message = utils.MISSING + self.old_reactions = utils.MISSING + return self + +class ReactionRemove(Event): + __event_name__ = "MESSAGE_REACTION_REMOVE" + + raw: RawReactionActionEvent + user: Member | User | Undefined + reaction: Reaction + + @classmethod + async def __load__(cls, data: ReactionActionEvent, state: ConnectionState) -> Self: + self = cls() + emoji = data["emoji"] + emoji_id = utils._get_as_snowflake(emoji, "id") + emoji = PartialEmoji.with_state( + state, id=emoji_id, animated=emoji.get("animated", False), name=emoji["name"] + ) + raw = RawReactionActionEvent(data, emoji, "REACTION_ADD") + + member_data = data.get("member") + if member_data: + guild = await state._get_guild(raw.guild_id) + if guild is not None: + raw.member = await Member._from_data(data=member_data, guild=guild, state=state) + else: + raw.member = None + else: + raw.member = None + + message = await state._get_message(raw.message_id) + if message is not None: + emoji = await state._upgrade_partial_emoji(emoji) + try: + self.reaction = message._remove_reaction(data, emoji, raw.user_id) + await state.cache.upsert_message(message) + except (AttributeError, ValueError): # eventual consistency lol + pass + else: + user = await state._get_reaction_user(message.channel, raw.user_id) + if user: + self.user = user + else: + self.user = utils.MISSING + + return self + + +class ReactionRemoveEmoji(Event, Reaction): + __event_name__ = "MESSAGE_REACTION_REMOVE_EMOJI" + + def __init__(self): + pass + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + emoji = data["emoji"] + emoji_id = utils._get_as_snowflake(emoji, "id") + emoji = PartialEmoji.with_state(self, id=emoji_id, name=emoji["name"]) + raw = RawReactionClearEmojiEvent(data, emoji) + + message = await state._get_message(raw.message_id) + if message is not None: + try: + reaction = message._clear_emoji(emoji) + await state.cache.upsert_message(message) + except (AttributeError, ValueError): # evetnaul consistency + pass + else: + if reaction: + self = cls() + self.__dict__.update(reaction.__dict__) + return self + + +class PollVoteAdd(Event): + __event_name__ = "MESSAGE_POLL_VOTE_ADD" + + raw: RawMessagePollVoteEvent + guild: Guild | Undefined + user: User | Member | None + poll: Poll + answer: PollAnswer + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + self = cls() + raw = RawMessagePollVoteEvent(data, False) + self.raw = raw + guild = await state._get_guild(raw.guild_id) + if guild: + self.user = guild.get_member(raw.user_id) + else: + self.user = await state.get_user(raw.user_id) + poll = await state.get_poll(raw.message_id) + if poll and poll.results: + answer = poll.get_answer(raw.answer_id) + counts = poll.results._answer_counts + if answer is not None: + if answer.id in counts: + counts[answer.id].count += 1 + else: + counts[answer.id] = PollAnswerCount( + {"id": answer.id, "count": 1, "me_voted": False} + ) + if poll is not None and self.user is not None: + answer = poll.get_answer(raw.answer_id) + if answer is not None: + self.poll = poll + self.answer = answer + return self + +class PollVoteRemove(Event): + __event_name__ = "MESSAGE_POLL_VOTE_REMOVE" + + raw: RawMessagePollVoteEvent + guild: Guild | Undefined + user: User | Member | None + poll: Poll + answer: PollAnswer + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + self = cls() + raw = RawMessagePollVoteEvent(data, False) + self.raw = raw + guild = await state._get_guild(raw.guild_id) + if guild: + self.user = guild.get_member(raw.user_id) + else: + self.user = await state.get_user(raw.user_id) + poll = await state.get_poll(raw.message_id) + if poll and poll.results: + answer = poll.get_answer(raw.answer_id) + counts = poll.results._answer_counts + if answer is not None: + if answer.id in counts: + counts[answer.id].count += 1 + else: + counts[answer.id] = PollAnswerCount( + {"id": answer.id, "count": 1, "me_voted": False} + ) + if poll is not None and self.user is not None: + answer = poll.get_answer(raw.answer_id) + if answer is not None: + self.poll = poll + self.answer = answer + return self diff --git a/discord/message.py b/discord/message.py index 891adc44ba..e0e9872e74 100644 --- a/discord/message.py +++ b/discord/message.py @@ -949,6 +949,8 @@ async def _from_data( except KeyError: continue + return self + def __repr__(self) -> str: name = self.__class__.__name__ return ( @@ -1014,118 +1016,6 @@ def _clear_emoji(self, emoji) -> Reaction | None: del self.reactions[index] return reaction - async def _update(self, data): - # In an update scheme, 'author' key has to be handled before 'member' - # otherwise they overwrite each other which is undesirable. - # Since there's no good way to do this we have to iterate over every - # handler rather than iterating over the keys which is a little slower - for key, handler in self._HANDLERS: - try: - value = data[key] - except KeyError: - continue - else: - r = handler(self, value) - if isawaitable(r): - await r # type: ignore - - # clear the cached properties - for attr in self._CACHED_SLOTS: - try: - delattr(self, attr) - except AttributeError: - pass - - def _handle_edited_timestamp(self, value: str) -> None: - self._edited_timestamp = utils.parse_time(value) - - def _handle_pinned(self, value: bool) -> None: - self.pinned = value - - def _handle_flags(self, value: int) -> None: - self.flags = MessageFlags._from_value(value) - - def _handle_application(self, value: MessageApplicationPayload) -> None: - self.application = value - - def _handle_activity(self, value: MessageActivityPayload) -> None: - self.activity = value - - def _handle_mention_everyone(self, value: bool) -> None: - self.mention_everyone = value - - def _handle_tts(self, value: bool) -> None: - self.tts = value - - def _handle_type(self, value: int) -> None: - self.type = try_enum(MessageType, value) - - def _handle_content(self, value: str) -> None: - self.content = value - - def _handle_attachments(self, value: list[AttachmentPayload]) -> None: - self.attachments = [Attachment(data=a, state=self._state) for a in value] - - def _handle_embeds(self, value: list[EmbedPayload]) -> None: - self.embeds = [Embed.from_dict(data) for data in value] - - def _handle_nonce(self, value: str | int) -> None: - self.nonce = value - - async def _handle_poll(self, value: PollPayload) -> None: - self._poll = Poll.from_dict(value, self) - await self._state.store_poll(self._poll, self.id) - - def _handle_author(self, author: UserPayload) -> None: - self.author = self._state.store_user(author) - if isinstance(self.guild, Guild): - found = self.guild.get_member(self.author.id) - if found is not None: - self.author = found - - def _handle_member(self, member: MemberPayload) -> None: - # The gateway now gives us full Member objects sometimes with the following keys - # deaf, mute, joined_at, roles - # For the sake of performance I'm going to assume that the only - # field that needs *updating* would be the joined_at field. - # If there is no Member object (for some strange reason), then we can upgrade - # ourselves to a more "partial" member object. - author = self.author - try: - # Update member reference - 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) - - async def _handle_mentions(self, mentions: list[UserWithMemberPayload]) -> None: - self.mentions = r = [] - guild = self.guild - state = self._state - if not isinstance(guild, Guild): - self.mentions = [await state.store_user(m) for m in mentions] - return - - for mention in filter(None, mentions): - id_search = int(mention["id"]) - member = guild.get_member(id_search) - if member is not None: - r.append(member) - else: - r.append(Member._try_upgrade(data=mention, guild=guild, state=state)) - - def _handle_mention_roles(self, role_mentions: list[int]) -> None: - self.role_mentions = [] - if isinstance(self.guild, Guild): - for role_id in map(int, role_mentions): - role = self.guild.get_role(role_id) - if role is not None: - self.role_mentions.append(role) - - def _handle_components(self, components: list[ComponentPayload]): - self.components = [_component_factory(d) for d in components] - def _rebind_cached_references( self, new_guild: Guild, new_channel: TextChannel | Thread ) -> None: From a824153fc3faf8c18f71c6a6f42b6e89b4649eb4 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Fri, 16 May 2025 03:27:29 +0800 Subject: [PATCH 06/38] feat: InteractionCreate --- discord/app/cache.py | 8 +++- discord/app/state.py | 17 -------- discord/events/interaction.py | 75 +++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 discord/events/interaction.py diff --git a/discord/app/cache.py b/discord/app/cache.py index d3ccfe4895..41c5802a3c 100644 --- a/discord/app/cache.py +++ b/discord/app/cache.py @@ -83,7 +83,10 @@ async def delete_view_on(self, message_id: int) -> None: async def get_all_views(self) -> list[View]: ... - async def store_modal(self, modal: Modal) -> None: + async def store_modal(self, modal: Modal, user_id: int) -> None: + ... + + async def delete_modal(self, custom_id: str) -> None: ... async def get_all_modals(self) -> list[Modal]: @@ -348,3 +351,6 @@ async def get_message(self, message_id: int) -> Message | None: async def get_all_messages(self) -> list[Message]: return list(self._messages) + + async def delete_modal(self, custom_id: str) -> None: + self._modals.pop(custom_id, None) diff --git a/discord/app/state.py b/discord/app/state.py index 7a5b11a9b7..2affa6b9d9 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -524,23 +524,6 @@ async def query_members( ) raise - def parse_interaction_create(self, data) -> None: - interaction = Interaction(data=data, state=self) - if data["type"] == 3: # interaction component - custom_id = interaction.data["custom_id"] # type: ignore - component_type = interaction.data["component_type"] # type: ignore - self._view_store.dispatch(component_type, custom_id, interaction) - if interaction.type == InteractionType.modal_submit: - user_id, custom_id = ( - interaction.user.id, - interaction.data["custom_id"], - ) - asyncio.create_task( - self._modal_store.dispatch(user_id, custom_id, interaction) - ) - - self.dispatch("interaction", interaction) - def parse_presence_update(self, data) -> None: guild_id = utils._get_as_snowflake(data, "guild_id") # guild_id won't be None here diff --git a/discord/events/interaction.py b/discord/events/interaction.py new file mode 100644 index 0000000000..c71c52cdb2 --- /dev/null +++ b/discord/events/interaction.py @@ -0,0 +1,75 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 Any, Self + +from discord.enums import InteractionType +from discord.types.interactions import Interaction as InteractionPayload +from ..app.state import ConnectionState +from ..interactions import Interaction +from ..app.event_emitter import Event + + +class InteractionCreate(Event, Interaction): + __event_name__ = "INTERACTION_CREATE" + + def __init__(self) -> None: + pass + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + interaction = Interaction(data=data, state=state) + if data["type"] == 3: + custom_id = interaction.data["custom_id"] # type: ignore + component_type = interaction.data["component_type"] # type: ignore + views = await state.cache.get_all_views() + for view in views: + if view.id == custom_id: + for item in view.children: + if item.type == component_type: + item.refresh_state(interaction) + view._dispatch_item(item, interaction) + if interaction.type == InteractionType.modal_submit: + custom_id = interaction.data["custom_id"] + for modal in await state.cache.get_all_modals(): + if modal.custom_id != custom_id: + continue + try: + components = [ + component + for parent_component in interaction.data["components"] + for component in parent_component["components"] + ] + for component in components: + for child in modal.children: + if child.custom_id == component["custom_id"]: # type: ignore + child.refresh_state(component) + break + await modal.callback(interaction) + await state.cache.delete_modal(modal.custom_id) + except Exception as e: + return await modal.on_error(e, interaction) + self = cls() + self.__dict__.update(interaction.__dict__) + return self From 8a2bd5974797be49315d2fdd073123734fcced0f Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Fri, 16 May 2025 03:50:00 +0800 Subject: [PATCH 07/38] refactor!: remove View and Model Store & make `Guild._from_data` into classmethod --- discord/app/cache.py | 4 +- discord/app/state.py | 4 +- discord/client.py | 4 +- discord/events/gateway.py | 4 +- discord/guild.py | 30 +++++++------- discord/interactions.py | 2 +- discord/iterators.py | 6 +-- discord/template.py | 4 +- discord/ui/modal.py | 50 +---------------------- discord/ui/view.py | 83 --------------------------------------- 10 files changed, 30 insertions(+), 161 deletions(-) diff --git a/discord/app/cache.py b/discord/app/cache.py index 41c5802a3c..132fc1e882 100644 --- a/discord/app/cache.py +++ b/discord/app/cache.py @@ -35,8 +35,8 @@ from ..guild import Guild from ..poll import Poll from ..sticker import GuildSticker, Sticker -from ..ui.modal import Modal, ModalStore -from ..ui.view import View, ViewStore +from ..ui.modal import Modal +from ..ui.view import View from ..user import User from ..types.user import User as UserPayload from ..types.emoji import Emoji as EmojiPayload diff --git a/discord/app/state.py b/discord/app/state.py index 2affa6b9d9..aa229886d3 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -73,8 +73,8 @@ from ..stage_instance import StageInstance from ..sticker import GuildSticker from ..threads import Thread, ThreadMember -from ..ui.modal import Modal, ModalStore -from ..ui.view import View, ViewStore +from ..ui.modal import Modal +from ..ui.view import View from ..user import ClientUser, User if TYPE_CHECKING: diff --git a/discord/client.py b/discord/client.py index 1765ff17f0..9bc69c7a23 100644 --- a/discord/client.py +++ b/discord/client.py @@ -1584,7 +1584,7 @@ async def fetch_guild(self, guild_id: int, /, *, with_counts=True) -> Guild: Getting the guild failed. """ data = await self.http.get_guild(guild_id, with_counts=with_counts) - return Guild(data=data, state=self._connection) + return await Guild._from_data(data=data, state=self._connection) async def create_guild( self, @@ -1633,7 +1633,7 @@ async def create_guild( data = await self.http.create_from_template(code, name, icon_base64) else: data = await self.http.create_guild(name, icon_base64) - return Guild(data=data, state=self._connection) + return await Guild._from_data(data=data, state=self._connection) async def fetch_stage_instance(self, channel_id: int, /) -> StageInstance: """|coro| diff --git a/discord/events/gateway.py b/discord/events/gateway.py index 364259808c..e0bb74df55 100644 --- a/discord/events/gateway.py +++ b/discord/events/gateway.py @@ -82,7 +82,7 @@ async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self: self.guilds = [] for guild_data in data["guilds"]: - guild = await Guild(data=guild_data, state=state)._from_data(guild_data) + guild = await Guild._from_data(guild_data, state) self.guilds.append(guild) await state._add_guild(guild) @@ -115,7 +115,7 @@ async def __load__(cls, data: GuildPayload, state: ConnectionState) -> Self: self = cls() guild = await state._get_guild(int(data["id"])) if guild is None: - guild = await Guild(data=data, state=state)._from_data(data) + guild = await Guild._from_data(data, state) await state._add_guild(guild) self.guild = guild self.__dict__.update(self.guild.__dict__) diff --git a/discord/guild.py b/discord/guild.py index 79ae438a22..e3c19443c2 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -298,19 +298,6 @@ class Guild(Hashable): 3: _GuildLimit(emoji=250, stickers=60, bitrate=384e3, filesize=104_857_600), } - def __init__(self, *, data: GuildPayload, state: ConnectionState): - # NOTE: - # Adding an attribute here and getting an AttributeError saying - # the attr doesn't exist? it has something to do with the order - # of the attr in __slots__ - - self._channels: dict[int, GuildChannel] = {} - self._members: dict[int, Member] = {} - self._scheduled_events: dict[int, ScheduledEvent] = {} - self._voice_states: dict[int, VoiceState] = {} - self._threads: dict[int, Thread] = {} - self._state: ConnectionState = state - def _add_channel(self, channel: GuildChannel, /) -> None: self._channels[channel.id] = channel @@ -448,7 +435,20 @@ def _remove_role(self, role_id: int, /) -> Role: return role - async def _from_data(self, guild: GuildPayload) -> Self: + @classmethod + async def _from_data(cls, guild: GuildPayload, state: ConnectionState) -> Self: + self = cls() + # NOTE: + # Adding an attribute here and getting an AttributeError saying + # the attr doesn't exist? it has something to do with the order + # of the attr in __slots__ + + self._channels: dict[int, GuildChannel] = {} + self._members: dict[int, Member] = {} + self._scheduled_events: dict[int, ScheduledEvent] = {} + self._voice_states: dict[int, VoiceState] = {} + self._threads: dict[int, Thread] = {} + self._state: ConnectionState = state member_count = guild.get("member_count") # Either the payload includes member_count, or it hasn't been set yet. # Prevents valid _member_count from suddenly changing to None @@ -1906,7 +1906,7 @@ async def edit( fields["features"] = features data = await http.edit_guild(self.id, reason=reason, **fields) - return Guild(data=data, state=self._state) + return Guild._from_data(data=data, state=self._state) async def fetch_channels(self) -> Sequence[GuildChannel]: """|coro| diff --git a/discord/interactions.py b/discord/interactions.py index 157f32025d..3140284b25 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -235,7 +235,7 @@ async def load_data(self): self._guild: Guild | None = None self._guild_data = data.get("guild") if self.guild is None and self._guild_data: - self._guild = Guild(data=self._guild_data, state=self._state) + self._guild = await Guild._from_data(data=self._guild_data, state=self._state) # TODO: there's a potential data loss here if self.guild_id: diff --git a/discord/iterators.py b/discord/iterators.py index 10bf437c7c..5b8f2ea8f8 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -638,10 +638,10 @@ def _get_retrieve(self): self.retrieve = r return r > 0 - def create_guild(self, data): + async def create_guild(self, data): from .guild import Guild - return Guild(state=self.state, data=data) + return await Guild._from_data(state=self.state, data=data) async def fill_guilds(self): if self._get_retrieve(): @@ -653,7 +653,7 @@ async def fill_guilds(self): data = filter(self._filter, data) for element in data: - await self.guilds.put(self.create_guild(element)) + await self.guilds.put(await self.create_guild(element)) async def _retrieve_guilds(self, retrieve) -> list[Guild]: """Retrieve guilds and update next parameters.""" diff --git a/discord/template.py b/discord/template.py index 160f6eb45e..89339d54f2 100644 --- a/discord/template.py +++ b/discord/template.py @@ -156,7 +156,7 @@ async def from_data(cls, state: ConnectionState, data: TemplatePayload) -> None: 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 + self.source_guild = await Guild._from_data(data=source_serialised, state=state) # type: ignore else: self.source_guild = guild @@ -200,7 +200,7 @@ async def create_guild(self, name: str, icon: Any = None) -> Guild: icon = _bytes_to_base64_data(icon) data = await self._state.http.create_from_template(self.code, name, icon) - return Guild(data=data, state=self._state) + return await Guild._from_data(data=data, state=self._state) async def sync(self) -> Template: """|coro| diff --git a/discord/ui/modal.py b/discord/ui/modal.py index e30333dac3..8aa83f6087 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -12,8 +12,7 @@ from .input_text import InputText __all__ = ( - "Modal", - "ModalStore", + "Modal" ) @@ -69,16 +68,6 @@ def __init__( self.__timeout_task: asyncio.Task[None] | None = None self.loop = asyncio.get_event_loop() - def _start_listening_from_store(self, store: ModalStore) -> None: - self.__cancel_callback = partial(store.remove_modal) - if self.timeout: - loop = asyncio.get_running_loop() - if self.__timeout_task is not None: - self.__timeout_task.cancel() - - self.__timeout_expiry = time.monotonic() + self.timeout - self.__timeout_task = loop.create_task(self.__timeout_task_impl()) - async def __timeout_task_impl(self) -> None: while True: # Guard just in case someone changes the value of the timeout at runtime @@ -305,40 +294,3 @@ def remove_item(self, item: InputText) -> None: def clear(self) -> None: self.weights = [0, 0, 0, 0, 0] - - -class ModalStore: - def __init__(self, state: ConnectionState) -> None: - # (user_id, custom_id) : Modal - self._modals: dict[tuple[int, str], Modal] = {} - self._state: ConnectionState = state - - def add_modal(self, modal: Modal, user_id: int): - self._modals[(user_id, modal.custom_id)] = modal - modal._start_listening_from_store(self) - - def remove_modal(self, modal: Modal, user_id): - modal.stop() - self._modals.pop((user_id, modal.custom_id)) - - async def dispatch(self, user_id: int, custom_id: str, interaction: Interaction): - key = (user_id, custom_id) - value = self._modals.get(key) - if value is None: - return - - try: - components = [ - component - for parent_component in interaction.data["components"] - for component in parent_component["components"] - ] - for component in components: - for child in value.children: - if child.custom_id == component["custom_id"]: # type: ignore - child.refresh_state(component) - break - await value.callback(interaction) - self.remove_modal(value, user_id) - except Exception as e: - return await value.on_error(e, interaction) diff --git a/discord/ui/view.py b/discord/ui/view.py index e0d9e14541..f5bbd0eb7f 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -427,15 +427,6 @@ async def _scheduled_task(self, item: Item, interaction: Interaction): except Exception as e: return await self.on_error(e, item, interaction) - def _start_listening_from_store(self, store: ViewStore) -> None: - self.__cancel_callback = partial(store.remove_view) - if self.timeout: - loop = asyncio.get_running_loop() - if self.__timeout_task is not None: - self.__timeout_task.cancel() - - self.__timeout_expiry = time.monotonic() + self.timeout - self.__timeout_task = loop.create_task(self.__timeout_task_impl()) def _dispatch_timeout(self): if self.__stopped.done(): @@ -563,77 +554,3 @@ def message(self): def message(self, value): self._message = value - -class ViewStore: - def __init__(self, state: ConnectionState): - # (component_type, message_id, custom_id): (View, Item) - self._views: dict[tuple[int, int | None, str], tuple[View, Item]] = {} - # message_id: View - self._synced_message_views: dict[int, View] = {} - self._state: ConnectionState = state - - @property - def persistent_views(self) -> Sequence[View]: - views = { - view.id: view - for (_, (view, _)) in self._views.items() - if view.is_persistent() - } - return list(views.values()) - - def __verify_integrity(self): - to_remove: list[tuple[int, int | None, str]] = [] - for k, (view, _) in self._views.items(): - if view.is_finished(): - to_remove.append(k) - - for k in to_remove: - del self._views[k] - - def add_view(self, view: View, message_id: int | None = None): - self.__verify_integrity() - - view._start_listening_from_store(self) - for item in view.children: - if item.is_dispatchable(): - self._views[(item.type.value, message_id, item.custom_id)] = (view, item) # type: ignore - - if message_id is not None: - self._synced_message_views[message_id] = view - - def remove_view(self, view: View): - for item in view.children: - if item.is_dispatchable(): - self._views.pop((item.type.value, item.custom_id), None) # type: ignore - - for key, value in self._synced_message_views.items(): - if value.id == view.id: - del self._synced_message_views[key] - break - - def dispatch(self, component_type: int, custom_id: str, interaction: Interaction): - self.__verify_integrity() - message_id: int | None = interaction.message and interaction.message.id - key = (component_type, message_id, custom_id) - # Fallback to None message_id searches in case a persistent view - # was added without an associated message_id - value = self._views.get(key) or self._views.get( - (component_type, None, custom_id) - ) - if value is None: - return - - view, item = value - item.refresh_state(interaction) - view._dispatch_item(item, interaction) - - def is_message_tracked(self, message_id: int): - return message_id in self._synced_message_views - - def remove_message_tracking(self, message_id: int) -> View | None: - return self._synced_message_views.pop(message_id, None) - - def update_from_message(self, message_id: int, components: list[ComponentPayload]): - # pre-req: is_message_tracked == true - view = self._synced_message_views[message_id] - view.refresh([_component_factory(d) for d in components]) From f0554c3d5560dedec019f181b5d8c3631e08ec3a Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Fri, 16 May 2025 03:53:48 +0800 Subject: [PATCH 08/38] chore: Type -> type --- discord/app/event_emitter.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/discord/app/event_emitter.py b/discord/app/event_emitter.py index 561635352e..c21b2969ad 100644 --- a/discord/app/event_emitter.py +++ b/discord/app/event_emitter.py @@ -25,7 +25,7 @@ from abc import ABC from asyncio import Future import asyncio -from typing import Any, Callable, Self, Type, TypeVar +from typing import Any, Callable, Self, TypeVar from .state import ConnectionState @@ -42,31 +42,31 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: class EventEmitter: def __init__(self, state: ConnectionState) -> None: - self._listeners: dict[Type[Event], list[Callable]] = {} - self._events: dict[str, list[Type[Event]]] - self._wait_fors: dict[Type[Event], list[Future]] = {} + self._listeners: dict[type[Event], list[Callable]] = {} + self._events: dict[str, list[type[Event]]] + self._wait_fors: dict[type[Event], list[Future]] = {} self._state = state - def add_event(self, event: Type[Event]) -> None: + def add_event(self, event: type[Event]) -> None: try: self._events[event.__event_name__].append(event) except KeyError: self._events[event.__event_name__] = [event] - def remove_event(self, event: Type[Event]) -> list[Type[Event]] | None: + def remove_event(self, event: type[Event]) -> list[type[Event]] | None: return self._events.pop(event.__event_name__, None) - def add_listener(self, event: Type[Event], listener: Callable) -> None: + def add_listener(self, event: type[Event], listener: Callable) -> None: try: self._listeners[event].append(listener) except KeyError: self.add_event(event) self._listeners[event] = [listener] - def remove_listener(self, event: Type[Event], listener: Callable) -> None: + def remove_listener(self, event: type[Event], listener: Callable) -> None: self._listeners[event].remove(listener) - def add_wait_for(self, event: Type[T]) -> Future[T]: + def add_wait_for(self, event: type[T]) -> Future[T]: fut = Future() try: @@ -76,7 +76,7 @@ def add_wait_for(self, event: Type[T]) -> Future[T]: return fut - def remove_wait_for(self, event: Type[Event], fut: Future) -> None: + def remove_wait_for(self, event: type[Event], fut: Future) -> None: self._wait_fors[event].remove(fut) async def emit(self, event_str: str, data: Any) -> None: From b689c545ef0f4e936c71da137007d47671376c1a Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Sun, 18 May 2025 20:51:15 +0800 Subject: [PATCH 09/38] Apply suggestions from code review Co-authored-by: Paillat Signed-off-by: VincentRPS --- discord/app/event_emitter.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/discord/app/event_emitter.py b/discord/app/event_emitter.py index c21b2969ad..7aa3aa6ca4 100644 --- a/discord/app/event_emitter.py +++ b/discord/app/event_emitter.py @@ -44,7 +44,7 @@ class EventEmitter: def __init__(self, state: ConnectionState) -> None: self._listeners: dict[type[Event], list[Callable]] = {} self._events: dict[str, list[type[Event]]] - self._wait_fors: dict[type[Event], list[Future]] = {} + self._wait_fors: dict[type[Event], list[Future]] = defaultdict(list) self._state = state def add_event(self, event: type[Event]) -> None: @@ -69,10 +69,7 @@ def remove_listener(self, event: type[Event], listener: Callable) -> None: def add_wait_for(self, event: type[T]) -> Future[T]: fut = Future() - try: - self._wait_fors[event].append(fut) - except KeyError: - self._wait_fors[event] = [fut] + self._wait_fors[event].append(fut) return fut From 56fdd917cfb9909c51716e48a00c7a239dfccf5e Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Fri, 30 May 2025 05:09:52 +0800 Subject: [PATCH 10/38] feat: add more events --- discord/abc.py | 5 +- discord/app/cache.py | 72 +++++++++++-- discord/app/state.py | 174 +----------------------------- discord/audit_logs.py | 12 +-- discord/automod.py | 5 +- discord/channel.py | 12 ++- discord/events/channel.py | 162 ++++++++++++++++++++++++++++ discord/events/gateway.py | 51 ++++++++- discord/events/invite.py | 56 ++++++++++ discord/events/message.py | 9 +- discord/events/thread.py | 46 ++++++++ discord/ext/commands/converter.py | 4 +- discord/guild.py | 60 +++++------ discord/iterators.py | 4 +- discord/message.py | 53 +++++---- discord/raw_models.py | 2 +- discord/threads.py | 5 +- discord/user.py | 5 +- 18 files changed, 470 insertions(+), 267 deletions(-) create mode 100644 discord/events/channel.py create mode 100644 discord/events/invite.py create mode 100644 discord/events/thread.py diff --git a/discord/abc.py b/discord/abc.py index 1efbcc8fb9..1fd8fc48b5 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -629,8 +629,7 @@ def overwrites_for(self, obj: Role | User) -> PermissionOverwrite: return PermissionOverwrite() - @property - def overwrites(self) -> dict[Role | Member, PermissionOverwrite]: + async def get_overwrites(self) -> dict[Role | Member, PermissionOverwrite]: """Returns all of the channel's overwrites. This is returned as a dictionary where the key contains the target which @@ -652,7 +651,7 @@ def overwrites(self) -> dict[Role | Member, PermissionOverwrite]: if ow.is_role(): target = self.guild.get_role(ow.id) elif ow.is_member(): - target = self.guild.get_member(ow.id) + target = await self.guild.get_member(ow.id) # TODO: There is potential data loss here in the non-chunked # case, i.e. target is None because get_member returned nothing. diff --git a/discord/app/cache.py b/discord/app/cache.py index 132fc1e882..df2706a461 100644 --- a/discord/app/cache.py +++ b/discord/app/cache.py @@ -22,11 +22,12 @@ DEALINGS IN THE SOFTWARE. """ -from collections import OrderedDict, deque -from typing import Deque, Protocol +from collections import OrderedDict, defaultdict, deque +from typing import Deque, Protocol, TypeVar from discord import utils from discord.app.state import ConnectionState +from discord.member import Member from discord.message import Message from ..abc import MessageableChannel, PrivateChannel @@ -44,6 +45,8 @@ from ..types.channel import DMChannel as DMChannelPayload from ..types.message import Message as MessagePayload +T = TypeVar('T') + class Cache(Protocol): # users async def get_all_users(self) -> list[User]: @@ -167,6 +170,26 @@ async def get_message(self, message_id: int) -> Message | None: async def get_all_messages(self) -> list[Message]: ... + # guild members + + async def store_member(self, member: Member) -> None: + ... + + async def get_member(self, guild_id: int, user_id: int) -> Member | None: + ... + + async def delete_member(self, guild_id: int, user_id: int) -> None: + ... + + async def delete_guild_members(self, guild_id: int) -> None: + ... + + async def get_guild_members(self, guild_id: int) -> list[Member]: + ... + + async def get_all_members(self) -> list[Member]: + ... + def clear(self, views: bool = True) -> None: ... @@ -176,6 +199,9 @@ def __init__(self, max_messages: int | None = None, *, state: ConnectionState): self.max_messages = max_messages self.clear() + def _flatten(self, matrix: list[list[T]]) -> list[T]: + return [item for row in matrix for item in row] + def clear(self, views: bool = True) -> None: self._users: dict[int, User] = {} self._guilds: dict[int, Guild] = {} @@ -186,11 +212,13 @@ def clear(self, views: bool = True) -> None: self._modals: dict[str, Modal] = {} self._messages: Deque[Message] = deque(maxlen=self.max_messages) - self._emojis = dict[str, GuildEmoji | AppEmoji] = {} + self._emojis: dict[int, list[GuildEmoji | AppEmoji]] = {} self._private_channels: OrderedDict[int, PrivateChannel] = OrderedDict() self._private_channels_by_user: dict[int, DMChannel] = {} + self._guild_members: dict[int, dict[int, Member]] = defaultdict(dict) + # users async def get_all_users(self) -> list[User]: return list(self._users.values()) @@ -200,7 +228,7 @@ async def store_user(self, payload: UserPayload) -> User: try: return self._users[user_id] except KeyError: - user = User(state=self, data=payload) + user = User(state=self._state, data=payload) if user.discriminator != "0000": self._users[user_id] = user user._stored = True @@ -209,16 +237,19 @@ async def store_user(self, payload: UserPayload) -> User: async def delete_user(self, user_id: int) -> None: self._users.pop(user_id, None) - async def get_user(self, user_id: int) -> User: + async def get_user(self, user_id: int) -> User | None: return self._users.get(user_id) # stickers async def get_all_stickers(self) -> list[GuildSticker]: - return list(self._stickers.values()) + return self._flatten(list(self._stickers.values())) async def get_sticker(self, sticker_id: int) -> GuildSticker | None: - return self._stickers.get(sticker_id) + stickers = self._flatten(list(self._stickers.values())) + for sticker in stickers: + if sticker.id == sticker_id: + return sticker async def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: sticker = GuildSticker(state=self._state, data=data) @@ -285,10 +316,13 @@ async def store_app_emoji( return emoji async def get_all_emojis(self) -> list[GuildEmoji | AppEmoji]: - return list(self._emojis.values()) + return self._flatten(list(self._emojis.values())) async def get_emoji(self, emoji_id: int | None) -> GuildEmoji | AppEmoji | None: - return self._emojis.get(emoji_id) + emojis = self._flatten(list(self._emojis.values())) + for emoji in emojis: + if emoji.id == emoji_id: + return emoji async def delete_emoji(self, emoji: GuildEmoji | AppEmoji) -> None: if isinstance(emoji, AppEmoji): @@ -354,3 +388,23 @@ async def get_all_messages(self) -> list[Message]: async def delete_modal(self, custom_id: str) -> None: self._modals.pop(custom_id, None) + + # guild members + + async def store_member(self, member: Member) -> None: + self._guild_members[member.guild.id][member.id] = member + + async def get_member(self, guild_id: int, user_id: int) -> Member | None: + return self._guild_members[guild_id].get(user_id) + + async def delete_member(self, guild_id: int, user_id: int) -> None: + self._guild_members[guild_id].pop(user_id, None) + + async def delete_guild_members(self, guild_id: int) -> None: + self._guild_members.pop(guild_id, None) + + async def get_guild_members(self, guild_id: int) -> list[Member]: + return list(self._guild_members.get(guild_id, {}).values()) + + async def get_all_members(self) -> list[Member]: + return self._flatten([list(members.values()) for members in self._guild_members.values()]) diff --git a/discord/app/state.py b/discord/app/state.py index aa229886d3..1a99cdf99d 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -126,9 +126,9 @@ async def add_members(self, members: list[Member]) -> None: return for member in members: - existing = guild.get_member(member.id) + existing = await guild.get_member(member.id) if existing is None or existing.joined_at is None: - guild._add_member(member) + await guild._add_member(member) async def wait(self) -> list[Member]: future = self.loop.create_future() @@ -524,174 +524,6 @@ async def query_members( ) raise - def parse_presence_update(self, data) -> 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, - ) - return - - user = data["user"] - member_id = int(user["id"]) - member = guild.get_member(member_id) - if member is None: - _log.debug( - "PRESENCE_UPDATE referencing an unknown member ID: %s. Discarding", - member_id, - ) - return - - old_member = Member._copy(member) - user_update = member._presence_update(data=data, user=user) - if user_update: - self.dispatch("user_update", user_update[0], user_update[1]) - - self.dispatch("presence_update", old_member, member) - - def parse_user_update(self, data) -> None: - # self.user is *always* cached when this is called - user: ClientUser = self.user # type: ignore - user._update(data) - ref = self._users.get(user.id) - if ref: - ref._update(data) - - def parse_invite_create(self, data) -> None: - invite = Invite.from_gateway(state=self, data=data) - self.dispatch("invite_create", invite) - - def parse_invite_delete(self, data) -> None: - invite = Invite.from_gateway(state=self, data=data) - self.dispatch("invite_delete", invite) - - def parse_channel_delete(self, data) -> None: - guild = self._get_guild(utils._get_as_snowflake(data, "guild_id")) - channel_id = int(data["id"]) - if guild is not None: - channel = guild.get_channel(channel_id) - if channel is not None: - guild._remove_channel(channel) - self.dispatch("guild_channel_delete", channel) - - def parse_channel_update(self, data) -> None: - channel_type = try_enum(ChannelType, data.get("type")) - channel_id = int(data["id"]) - if channel_type is ChannelType.group: - channel = self._get_private_channel(channel_id) - old_channel = copy.copy(channel) - # the channel is a GroupChannel - channel._update_group(data) # type: ignore - self.dispatch("private_channel_update", old_channel, channel) - return - - guild_id = utils._get_as_snowflake(data, "guild_id") - guild = self._get_guild(guild_id) - if guild is not None: - channel = guild.get_channel(channel_id) - if channel is not None: - old_channel = copy.copy(channel) - channel._update(guild, data) - self.dispatch("guild_channel_update", old_channel, channel) - else: - _log.debug( - "CHANNEL_UPDATE referencing an unknown channel ID: %s. Discarding.", - channel_id, - ) - else: - _log.debug( - "CHANNEL_UPDATE referencing an unknown guild ID: %s. Discarding.", - guild_id, - ) - - def parse_channel_create(self, data) -> None: - factory, ch_type = _channel_factory(data["type"]) - if factory is None: - _log.debug( - "CHANNEL_CREATE referencing an unknown channel type %s. Discarding.", - data["type"], - ) - return - - guild_id = utils._get_as_snowflake(data, "guild_id") - guild = self._get_guild(guild_id) - if guild is not None: - # the factory can't be a DMChannel or GroupChannel here - channel = factory(guild=guild, state=self, data=data) # type: ignore - guild._add_channel(channel) # type: ignore - self.dispatch("guild_channel_create", channel) - else: - _log.debug( - "CHANNEL_CREATE referencing an unknown guild ID: %s. Discarding.", - guild_id, - ) - return - - def parse_channel_pins_update(self, data) -> None: - channel_id = int(data["channel_id"]) - try: - guild = self._get_guild(int(data["guild_id"])) - except KeyError: - guild = None - channel = self._get_private_channel(channel_id) - else: - channel = guild and guild._resolve_channel(channel_id) - - if channel is None: - _log.debug( - ( - "CHANNEL_PINS_UPDATE referencing an unknown channel ID: %s." - " Discarding." - ), - channel_id, - ) - return - - last_pin = ( - utils.parse_time(data["last_pin_timestamp"]) - if data["last_pin_timestamp"] - else None - ) - - if guild is None: - self.dispatch("private_channel_pins_update", channel, last_pin) - else: - self.dispatch("guild_channel_pins_update", channel, last_pin) - - def parse_thread_create(self, data) -> None: - guild_id = int(data["guild_id"]) - guild: Guild | None = self._get_guild(guild_id) - if guild is None: - _log.debug( - "THREAD_CREATE referencing an unknown guild ID: %s. Discarding", - guild_id, - ) - return - - cached_thread = guild.get_thread(int(data["id"])) - if not cached_thread: - thread = Thread(guild=guild, state=guild._state, data=data) - guild._add_thread(thread) - if data.get("newly_created"): - thread._add_member( - ThreadMember( - thread, - { - "id": thread.id, - "user_id": data["owner_id"], - "join_timestamp": data["thread_metadata"][ - "create_timestamp" - ], - "flags": utils.MISSING, - }, - ) - ) - self.dispatch("thread_create", thread) - else: - self.dispatch("thread_join", cached_thread) def parse_thread_update(self, data) -> None: guild_id = int(data["guild_id"]) @@ -1541,7 +1373,7 @@ async def _get_reaction_user( self, channel: MessageableChannel, user_id: int ) -> User | Member | None: if isinstance(channel, TextChannel): - return channel.guild.get_member(user_id) + return await channel.guild.get_member(user_id) return await self.get_user(user_id) async def get_reaction_emoji(self, data) -> GuildEmoji | AppEmoji | PartialEmoji: diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 9b405400e2..027a24f679 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -603,8 +603,8 @@ def _from_data(self, data: AuditLogEntryPayload) -> None: self.user = self._get_member(utils._get_as_snowflake(data, "user_id")) # type: ignore self._target_id = utils._get_as_snowflake(data, "target_id") - def _get_member(self, user_id: int) -> Member | User | None: - return self.guild.get_member(user_id) or self._users.get(user_id) + async def _get_member(self, user_id: int) -> Member | User | None: + return await self.guild.get_member(user_id) or self._users.get(user_id) def __repr__(self) -> str: return f"" @@ -667,8 +667,8 @@ def _convert_target_guild(self, target_id: int) -> Guild: def _convert_target_channel(self, target_id: int) -> abc.GuildChannel | Object: return self.guild.get_channel(target_id) or Object(id=target_id) - def _convert_target_user(self, target_id: int) -> Member | User | None: - return self._get_member(target_id) + async def _convert_target_user(self, target_id: int) -> Member | User | None: + return await self._get_member(target_id) def _convert_target_role(self, target_id: int) -> Role | Object: return self.guild.get_role(target_id) or Object(id=target_id) @@ -700,8 +700,8 @@ def _convert_target_invite(self, target_id: int) -> Invite: async def _convert_target_emoji(self, target_id: int) -> GuildEmoji | Object: return (await self._state.get_emoji(target_id)) or Object(id=target_id) - def _convert_target_message(self, target_id: int) -> Member | User | None: - return self._get_member(target_id) + async def _convert_target_message(self, target_id: int) -> Member | User | None: + return await self._get_member(target_id) def _convert_target_stage_instance(self, target_id: int) -> StageInstance | Object: return self.guild.get_stage_instance(target_id) or Object(id=target_id) diff --git a/discord/automod.py b/discord/automod.py index 60519872fe..9b3dcc0f66 100644 --- a/discord/automod.py +++ b/discord/automod.py @@ -420,12 +420,11 @@ async def get_guild(self) -> Guild | None: """The guild this rule belongs to.""" return await self._state._get_guild(self.guild_id) - @cached_property - def creator(self) -> Member | None: + async def get_creator(self) -> Member | None: """The member who created this rule.""" if self.guild is None: return None - return self.guild.get_member(self.creator_id) + return await self.guild.get_member(self.creator_id) @cached_property def exempt_roles(self) -> list[Role | Object]: diff --git a/discord/channel.py b/discord/channel.py index 5b0b4c5bd4..83a059ae01 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1611,13 +1611,12 @@ async def _update( def _sorting_bucket(self) -> int: return ChannelType.voice.value - @property - def members(self) -> list[Member]: + async def get_members(self) -> list[Member]: """Returns all members that are currently inside this voice channel.""" ret = [] for user_id, state in self.guild._voice_states.items(): if state.channel and state.channel.id == self.id: - member = self.guild.get_member(user_id) + member = await self.guild.get_member(user_id) if member is not None: ret.append(member) return ret @@ -3235,6 +3234,7 @@ class GroupChannel(discord.abc.Messageable, Hashable): "name", "me", "_state", + "_data", ) def __init__( @@ -3256,11 +3256,13 @@ async def _load(self) -> None: else: self.owner = utils.find(lambda u: u.id == self.owner_id, self.recipients) - def _update_group(self) -> None: + async def _update_group(self, data: dict[str, Any] | None = None) -> None: + if data: + self._data = data self.owner_id: int | None = utils._get_as_snowflake(self._data, "owner_id") self._icon: str | None = self._data.get("icon") self.name: str | None = self._data.get("name") - asyncio.create_task(self._load()) + await self._load() async def _get_channel(self): return self diff --git a/discord/events/channel.py b/discord/events/channel.py new file mode 100644 index 0000000000..e1246c7c90 --- /dev/null +++ b/discord/events/channel.py @@ -0,0 +1,162 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 datetime import datetime +from copy import copy +from typing import Any, Self, TypeVar, cast +from discord import utils +from discord.abc import GuildChannel, PrivateChannel +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.channel import GroupChannel, _channel_factory +from discord.enums import ChannelType, try_enum +from discord.threads import Thread + +T = TypeVar('T') + +class ChannelCreate(Event, GuildChannel): + __event_name__ = "CHANNEL_CREATE" + + def __init__(self) -> None: + ... + + @classmethod + async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | None: + factory, _ = _channel_factory(data["type"]) + if factory is None: + return + + guild_id = utils._get_as_snowflake(data, "guild_id") + guild = await state._get_guild(guild_id) + if guild is not None: + # the factory can't be a DMChannel or GroupChannel here + channel = factory(guild=guild, state=self, data=data) # type: ignore + guild._add_channel(channel) # type: ignore + self = cls() + self.__dict__.update(channel.__dict__) + return self + else: + return + +class PrivateChannelUpdate(Event, PrivateChannel): + __event_name__ = "PRIVATE_CHANNEL_UPDATE" + + old: PrivateChannel | None + + def __init__(self) -> None: + ... + + @classmethod + async def __load__(cls, data: tuple[PrivateChannel | None, PrivateChannel], _: ConnectionState) -> Self | None: + self = cls() + self.old = data[0] + self.__dict__.update(data[1].__dict__) + return self + +class GuildChannelUpdate(Event, PrivateChannel): + __event_name__ = "GUILD_CHANNEL_UPDATE" + + old: GuildChannel | None + + def __init__(self) -> None: + ... + + @classmethod + async def __load__(cls, data: tuple[GuildChannel | None, GuildChannel], _: ConnectionState) -> Self | None: + self = cls() + self.old = data[0] + self.__dict__.update(data[1].__dict__) + return self + +class ChannelUpdate(Event, GuildChannel): + __event_name__ = "CHANNEL_UPDATE" + + def __init__(self) -> None: + ... + + @classmethod + async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | None: + channel_type = try_enum(ChannelType, data.get("type")) + channel_id = int(data["id"]) + if channel_type is ChannelType.group: + channel = await state._get_private_channel(channel_id) + old_channel = copy(channel) + # the channel is a GroupChannel + await cast(GroupChannel, channel)._update_group(data) + await state.emitter.emit("PRIVATE_CHANNEL_UPDATE", (old_channel, channel)) + return + + guild_id = utils._get_as_snowflake(data, "guild_id") + guild = await state._get_guild(guild_id) + if guild is not None: + channel = guild.get_channel(channel_id) + if channel is not None: + old_channel = copy.copy(channel) + await channel._update(data) # type: ignore + await state.emitter.emit("GUILD_CHANNEL_UPDATE", (old_channel, channel)) + +class ChannelDelete(Event, GuildChannel): + __event_name__ = "CHANNEL_DELETE" + + def __init__(self) -> None: + ... + + @classmethod + async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | None: + guild = await state._get_guild(utils._get_as_snowflake(data, "guild_id")) + channel_id = int(data["id"]) + if guild is not None: + channel = guild.get_channel(channel_id) + if channel is not None: + guild._remove_channel(channel) + self = cls() + self.__dict__.update(channel.__dict__) + return self + +class ChannelPinsUpdate(Event): + channel: PrivateChannel | GuildChannel | Thread + last_pin: datetime | None + + @classmethod + async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | None: + channel_id = int(data["channel_id"]) + try: + guild = await state._get_guild(int(data["guild_id"])) + except KeyError: + guild = None + channel = await state._get_private_channel(channel_id) + else: + channel = guild and guild._resolve_channel(channel_id) + + if channel is None: + return + + self = cls() + self.channel = channel + self.last_pin = ( + utils.parse_time(data["last_pin_timestamp"]) + if data["last_pin_timestamp"] + else None + ) + return self diff --git a/discord/events/gateway.py b/discord/events/gateway.py index e0bb74df55..8402f2940a 100644 --- a/discord/events/gateway.py +++ b/discord/events/gateway.py @@ -22,7 +22,7 @@ DEALINGS IN THE SOFTWARE. """ -from typing import Any, Self +from typing import Any, Self, cast from discord import utils from discord.emoji import Emoji @@ -31,7 +31,8 @@ from discord.member import Member from discord.role import Role from discord.sticker import Sticker -from discord.user import ClientUser +from discord.types.user import User as UserPayload +from discord.user import ClientUser, User from ..app.state import ConnectionState from ..app.event_emitter import Event @@ -192,3 +193,49 @@ async def __load__(cls, data: GuildApplicationCommandPermissions, state: Connect self.guild_id = int(data["guild_id"]) self.permissions = [ApplicationCommandPermission(data) for data in data["permissions"]] return self + +class PresenceUpdate(Event): + __event_name__ = "PRESENCE_UPDATE" + + old: Member + new: Member + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + self = cls() + guild_id = utils._get_as_snowflake(data, "guild_id") + guild = await state._get_guild(guild_id) + if guild is None: + return + + user = data["user"] + member_id = int(user["id"]) + member = await guild.get_member(member_id) + if member is None: + return + + self.old = Member._copy(member) + self.new = member + user_update = member._presence_update(data=data, user=user) + +class UserUpdate(Event, User): + __event_name__ = "USER_UPDATE" + + old: User + + def __init__(self) -> None: + ... + + @classmethod + async def __load__(cls, data: tuple[User, User] | Any, state: ConnectionState) -> Self | None: + self = cls() + if isinstance(data, tuple): + self.old = data[0] + self.__dict__.update(data[1].__dict__) + return self + else: + user = cast(ClientUser, state.user) + await user._update(data) # type: ignore + ref = await state.cache.get_user(user.id) + if ref is not None: + await ref._update(data) diff --git a/discord/events/invite.py b/discord/events/invite.py new file mode 100644 index 0000000000..8de54846ee --- /dev/null +++ b/discord/events/invite.py @@ -0,0 +1,56 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 Any, Self +from discord.abc import GuildChannel +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.guild import Guild +from discord.invite import Invite, PartialInviteChannel, PartialInviteGuild +from discord.types.invite import GatewayInvite, Invite as InvitePayload, VanityInvite + + +class InviteCreate(Event, Invite): + __event_name__ = "INVITE_CREATE" + + def __init__(self) -> None: + ... + + @classmethod + async def __load__(cls, data: GatewayInvite, state: ConnectionState) -> Self | None: + invite = await Invite.from_gateway(state=state, data=data) + self = cls() + self.__dict__.update(invite.__dict__) + +class InviteDelete(Event, Invite): + __event_name__ = "INVITE_DELETE" + + def __init__(self) -> None: + ... + + @classmethod + async def __load__(cls, data: GatewayInvite, state: ConnectionState) -> Self | None: + invite = await Invite.from_gateway(state=state, data=data) + self = cls() + self.__dict__.update(invite.__dict__) diff --git a/discord/events/message.py b/discord/events/message.py index d2b6cf7111..97f3a498e3 100644 --- a/discord/events/message.py +++ b/discord/events/message.py @@ -69,6 +69,7 @@ class MessageDelete(Event, Message): __event_name__ = "MESSAGE_DELETE" raw: RawMessageDeleteEvent + is_cached: bool @classmethod async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: @@ -77,9 +78,13 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: msg = await state._get_message(raw.message_id) raw.cached_message = msg self.raw = raw + self.id = raw.message_id if msg is not None: + self.is_cached = True await state.cache.delete_message(raw.message_id) self.__dict__.update(msg.__dict__) + else: + self.is_cached = False return self @@ -281,7 +286,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: self.raw = raw guild = await state._get_guild(raw.guild_id) if guild: - self.user = guild.get_member(raw.user_id) + self.user = await guild.get_member(raw.user_id) else: self.user = await state.get_user(raw.user_id) poll = await state.get_poll(raw.message_id) @@ -318,7 +323,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: self.raw = raw guild = await state._get_guild(raw.guild_id) if guild: - self.user = guild.get_member(raw.user_id) + self.user = await guild.get_member(raw.user_id) else: self.user = await state.get_user(raw.user_id) poll = await state.get_poll(raw.message_id) diff --git a/discord/events/thread.py b/discord/events/thread.py new file mode 100644 index 0000000000..cdd2480f43 --- /dev/null +++ b/discord/events/thread.py @@ -0,0 +1,46 @@ +from typing import Any, Self +from discord import utils +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.threads import Thread, ThreadMember + + +class ThreadCreate(Event, Thread): + __event_name__ = "THREAD_CREATE" + + def __init__(self) -> None: + ... + + just_joined: bool + + @classmethod + async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | None: + guild_id = int(data["guild_id"]) + guild = await state._get_guild(guild_id) + if guild is None: + return + + cached_thread = guild.get_thread(int(data["id"])) + self = cls() + if not cached_thread: + thread = Thread(guild=guild, state=guild._state, data=data) # type: ignore + guild._add_thread(thread) + if data.get("newly_created"): + thread._add_member( + ThreadMember( + thread, + { + "id": thread.id, + "user_id": data["owner_id"], + "join_timestamp": data["thread_metadata"][ + "create_timestamp" + ], + "flags": utils.MISSING, + }, + ) + ) + self.just_joined = False + else: + self.just_joined = True + + return self diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index 52a4353576..42b390e94f 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -217,7 +217,7 @@ async def query_member_by_id(self, bot, guild, user_id): return None if cache: - guild._add_member(member) + await guild._add_member(member) return member # If we're not being rate limited then we can use the websocket to actually query @@ -243,7 +243,7 @@ async def convert(self, ctx: Context, argument: str) -> discord.Member: else: user_id = int(match.group(1)) if guild: - result = guild.get_member(user_id) + result = await guild.get_member(user_id) if ctx.message is not None and result is None: result = _utils_get(ctx.message.mentions, id=user_id) else: diff --git a/discord/guild.py b/discord/guild.py index e3c19443c2..5aa959d84b 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -39,6 +39,7 @@ Sequence, Tuple, Union, + cast, overload, ) @@ -269,7 +270,6 @@ class Guild(Hashable): "preferred_locale", "nsfw_level", "_scheduled_events", - "_members", "_channels", "_icon", "_banner", @@ -307,16 +307,17 @@ def _remove_channel(self, channel: Snowflake, /) -> None: def _voice_state_for(self, user_id: int, /) -> VoiceState | None: return self._voice_states.get(user_id) - def _add_member(self, member: Member, /) -> None: - self._members[member.id] = member + async def _add_member(self, member: Member, /) -> None: + await cast(ConnectionState, self._state).cache.store_member(member) async def _get_and_update_member( self, payload: MemberPayload, user_id: int, cache_flag: bool, / ) -> Member: + members = await cast(ConnectionState, self._state).cache.get_guild_members(self.id) # we always get the member, and we only update if the cache_flag (this cache # flag should always be MemberCacheFlag.interaction) is set to True - if user_id in self._members: - member = self.get_member(user_id) + if user_id in members: + member = cast(Member, await self.get_member(user_id)) await member._update(payload) if cache_flag else None else: # NOTE: @@ -325,7 +326,7 @@ async def _get_and_update_member( # class will be incorrect such as status and activities. member = await Member._from_data(guild=self, state=self._state, data=payload) # type: ignore if cache_flag: - self._members[user_id] = member + await cast(ConnectionState, self._state).cache.store_member(member) return member def _store_thread(self, payload: ThreadPayload, /) -> Thread: @@ -333,9 +334,6 @@ def _store_thread(self, payload: ThreadPayload, /) -> Thread: self._threads[thread.id] = thread return thread - def _remove_member(self, member: Snowflake, /) -> None: - self._members.pop(member.id, None) - def _add_scheduled_event(self, event: ScheduledEvent, /) -> None: self._scheduled_events[event.id] = event @@ -403,7 +401,7 @@ async def _update_voice_state( before = VoiceState(data=data, channel=None) self._voice_states[user_id] = after - member = self.get_member(user_id) + member = await self.get_member(user_id) if member is None: try: member = await Member._from_data(data=data["member"], state=self._state, guild=self) @@ -444,11 +442,10 @@ async def _from_data(cls, guild: GuildPayload, state: ConnectionState) -> Self: # of the attr in __slots__ self._channels: dict[int, GuildChannel] = {} - self._members: dict[int, Member] = {} self._scheduled_events: dict[int, ScheduledEvent] = {} self._voice_states: dict[int, VoiceState] = {} self._threads: dict[int, Thread] = {} - self._state: ConnectionState = state + self._state = state member_count = guild.get("member_count") # Either the payload includes member_count, or it hasn't been set yet. # Prevents valid _member_count from suddenly changing to None @@ -524,14 +521,14 @@ async def _from_data(cls, guild: GuildPayload, state: ConnectionState) -> Self: for mdata in guild.get("members", []): member = await Member._from_data(data=mdata, guild=self, state=state) if cache_joined or member.id == self_id: - self._add_member(member) + await self._add_member(member) events = [] for event in guild.get("guild_scheduled_events", []): creator = ( None if not event.get("creator", None) - else self.get_member(event.get("creator_id")) + else await self.get_member(event.get("creator_id")) ) events.append( ScheduledEvent( @@ -565,7 +562,7 @@ def _sync(self, data: GuildPayload) -> None: empty_tuple = () for presence in data.get("presences", []): user_id = int(presence["user"]["id"]) - member = self.get_member(user_id) + member = await self.get_member(user_id) if member is not None: member._presence_update(presence, empty_tuple) # type: ignore @@ -602,15 +599,14 @@ def jump_url(self) -> str: """ return f"https://discord.com/channels/{self.id}" - @property - def large(self) -> bool: + async def is_large(self) -> bool: """Indicates if the guild is a 'large' guild. A large guild is defined as having more than ``large_threshold`` count members, which for this library is set to the maximum of 250. """ if self._large is None: - return (self._member_count or len(self._members)) >= 250 + return (self._member_count or len(await cast(ConnectionState, self._state).cache.get_guild_members(self.id))) >= 250 return self._large @property @@ -647,14 +643,13 @@ def forum_channels(self) -> list[ForumChannel]: r.sort(key=lambda c: (c.position or -1, c.id)) return r - @property - def me(self) -> Member: + async def get_me(self) -> Member: """Similar to :attr:`Client.user` except an instance of :class:`Member`. This is essentially used to get the member version of yourself. """ self_id = self._state.user.id # The self member is *always* cached - return self.get_member(self_id) # type: ignore + return await self.get_member(self_id) # type: ignore @property def voice_client(self) -> VoiceClient | None: @@ -848,12 +843,11 @@ def filesize_limit(self) -> int: """The maximum number of bytes files can have when uploaded to this guild.""" return self._PREMIUM_GUILD_LIMITS[self.premium_tier].filesize - @property - def members(self) -> list[Member]: + async def get_members(self) -> list[Member]: """A list of members that belong to this guild.""" - return list(self._members.values()) + return await cast(ConnectionState, self._state).cache.get_guild_members(self.id) - def get_member(self, user_id: int, /) -> Member | None: + async def get_member(self, user_id: int, /) -> Member | None: """Returns a member with the given ID. Parameters @@ -866,7 +860,7 @@ def get_member(self, user_id: int, /) -> Member | None: Optional[:class:`Member`] The member or ``None`` if not found. """ - return self._members.get(user_id) + return await cast(ConnectionState, self._state).cache.get_member(self.id, user_id) @property def premium_subscribers(self) -> list[Member]: @@ -953,10 +947,9 @@ def get_stage_instance(self, stage_instance_id: int, /) -> StageInstance | None: """ return self._stage_instances.get(stage_instance_id) - @property - def owner(self) -> Member | None: + async def get_owner(self) -> Member | None: """The member that owns the guild.""" - return self.get_member(self.owner_id) # type: ignore + return await self.get_member(self.owner_id) # type: ignore @property def icon(self) -> Asset | None: @@ -1003,8 +996,7 @@ def member_count(self) -> int: """ return self._member_count - @property - def chunked(self) -> bool: + async def is_chunked(self) -> bool: """Returns a boolean indicating if the guild is "chunked". A chunked guild means that :attr:`member_count` is equal to the @@ -1015,7 +1007,7 @@ def chunked(self) -> bool: """ if self._member_count is None: return False - return self._member_count == len(self._members) + return self._member_count == len(await cast(ConnectionState, self._state).cache.get_guild_members(self.id)) @property def shard_id(self) -> int: @@ -3692,7 +3684,7 @@ async def fetch_scheduled_events( creator = ( None if not event.get("creator", None) - else self.get_member(event.get("creator_id")) + else await self.get_member(event.get("creator_id")) ) result.append( ScheduledEvent( @@ -3742,7 +3734,7 @@ async def fetch_scheduled_event( creator = ( None if not data.get("creator", None) - else self.get_member(data.get("creator_id")) + else await self.get_member(data.get("creator_id")) ) event = ScheduledEvent( state=self._state, guild=self, creator=creator, data=data diff --git a/discord/iterators.py b/discord/iterators.py index 5b8f2ea8f8..db0e1b41d8 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -235,7 +235,7 @@ async def fill_users(self): await self.users.put(User(state=self.state, data=element)) else: member_id = int(element["id"]) - member = self.guild.get_member(member_id) + member = await self.guild.get_member(member_id) if member is not None: await self.users.put(member) else: @@ -290,7 +290,7 @@ async def fill_users(self): await self.users.put(User(state=self.state, data=element)) else: member_id = int(element["id"]) - member = self.guild.get_member(member_id) + member = await self.guild.get_member(member_id) if member is not None: await self.users.put(member) else: diff --git a/discord/message.py b/discord/message.py index 1818c6b68e..7d057be4c2 100644 --- a/discord/message.py +++ b/discord/message.py @@ -776,22 +776,6 @@ def __repr__(self) -> str: return f"" -def flatten_handlers(cls): - prefix = len("_handle_") - handlers = [ - (key[prefix:], value) - for key, value in cls.__dict__.items() - if key.startswith("_handle_") and key != "_handle_member" - ] - - # store _handle_member last - handlers.append(("member", cls._handle_member)) - cls._HANDLERS = handlers - cls._CACHED_SLOTS = [attr for attr in cls.__slots__ if attr.startswith("_cs_")] - return cls - - -@flatten_handlers class Message(Hashable): r"""Represents a message from Discord. @@ -1104,11 +1088,38 @@ async def _from_data( except KeyError: self.call = None - for handler in ("author", "member", "mentions", "mention_roles"): - try: - getattr(self, f"_handle_{handler}")(data[handler]) - except KeyError: - continue + self.author = await self._state.store_user(data["author"]) + if isinstance(self.guild, Guild): + found = await self.guild.get_member(self.author.id) + if found is not None: + self.author = found + + try: + # Update member reference + self.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=data["member"]) + + self.mentions = r = [] + if not isinstance(self.guild, Guild): + self.mentions = [await state.store_user(m) for m in data["mentions"]] + else: + for mention in filter(None, data["mentions"]): + id_search = int(mention["id"]) + member = await self.guild.get_member(id_search) + if member is not None: + r.append(member) + else: + r.append(Member._try_upgrade(data=mention, guild=self.guild, state=state)) + + self.role_mentions = [] + if isinstance(self.guild, Guild): + for role_id in map(int, data["mention_roles"]): + role = self.guild.get_role(role_id) + if role is not None: + self.role_mentions.append(role) return self diff --git a/discord/raw_models.py b/discord/raw_models.py index 43a8230837..dbe4a3d157 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -670,7 +670,7 @@ async def from_data(cls, state: ConnectionState, data: AutoModActionExecution) - self.matched_content: str | None = data.get("matched_content", None) if self.guild: - self.member: Member | None = self.guild.get_member(self.user_id) + self.member: Member | None = await self.guild.get_member(self.user_id) else: self.member: Member | None = None diff --git a/discord/threads.py b/discord/threads.py index e316c3ad7f..49f044a7b6 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -269,10 +269,9 @@ def parent(self) -> TextChannel | ForumChannel | None: """The parent channel this thread belongs to.""" return self.guild.get_channel(self.parent_id) # type: ignore - @property - def owner(self) -> Member | None: + async def get_owner(self) -> Member | None: """The member this thread belongs to.""" - return self.guild.get_member(self.owner_id) + return await self.guild.get_member(self.owner_id) @property def mention(self) -> str: diff --git a/discord/user.py b/discord/user.py index cbdab71b10..d397365b1d 100644 --- a/discord/user.py +++ b/discord/user.py @@ -581,8 +581,7 @@ async def get_dm_channel(self) -> DMChannel | None: """ return await self._state._get_private_channel_by_user(self.id) - @property - def mutual_guilds(self) -> list[Guild]: + async def get_mutual_guilds(self) -> list[Guild]: """The guilds that the user shares with the client. .. note:: @@ -592,7 +591,7 @@ def mutual_guilds(self) -> list[Guild]: .. versionadded:: 1.7 """ return [ - guild for guild in self._state._guilds.values() if guild.get_member(self.id) + guild for guild in await self._state.cache.get_all_guilds() if await guild.get_member(self.id) ] async def create_dm(self) -> DMChannel: From a2015f91ad9bd448336e42a691a8ce6d9f4ef565 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Fri, 30 May 2025 05:36:29 +0800 Subject: [PATCH 11/38] chore: temporary fix --- discord/guild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/guild.py b/discord/guild.py index 3cb21f69ef..c8c4a94133 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -540,7 +540,7 @@ def _sync(self, data: GuildPayload) -> None: empty_tuple = () for presence in data.get("presences", []): user_id = int(presence["user"]["id"]) - member = await self.get_member(user_id) + member = self.get_member(user_id) if member is not None: member._presence_update(presence, empty_tuple) # type: ignore From 79c6bf4d1b2bfd1f54e0bc049863fbe2d0276699 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Sat, 28 Jun 2025 22:10:48 +0800 Subject: [PATCH 12/38] Update discord/app/event_emitter.py Co-authored-by: Paillat Signed-off-by: VincentRPS --- discord/app/event_emitter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/app/event_emitter.py b/discord/app/event_emitter.py index 7aa3aa6ca4..380f3be2d2 100644 --- a/discord/app/event_emitter.py +++ b/discord/app/event_emitter.py @@ -36,6 +36,7 @@ class Event(ABC): __event_name__: str @classmethod + @abstractmethod async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: ... From 79883a0f3cb6fbb5ea2eb42996f056dac441f595 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Sat, 28 Jun 2025 22:24:24 +0800 Subject: [PATCH 13/38] refactor!: miscellaneous --- discord/app/cache.py | 21 +++++++++++++++++---- discord/app/event_emitter.py | 3 ++- discord/app/state.py | 31 +++++++++++++------------------ discord/interactions.py | 3 ++- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/discord/app/cache.py b/discord/app/cache.py index df2706a461..74a8569d45 100644 --- a/discord/app/cache.py +++ b/discord/app/cache.py @@ -147,7 +147,7 @@ async def get_private_channels(self) -> list[PrivateChannel]: async def get_private_channel(self, channel_id: int) -> PrivateChannel: ... - async def get_private_channel_by_user(self, user_id: int) -> PrivateChannel: + async def get_private_channel_by_user(self, user_id: int) -> PrivateChannel | None: ... async def store_private_channel(self, channel: PrivateChannel) -> None: @@ -190,19 +190,32 @@ async def get_guild_members(self, guild_id: int) -> list[Member]: async def get_all_members(self) -> list[Member]: ... - def clear(self, views: bool = True) -> None: + async def clear(self, views: bool = True) -> None: ... class MemoryCache(Cache): def __init__(self, max_messages: int | None = None, *, state: ConnectionState): self._state = state self.max_messages = max_messages - self.clear() + self._users: dict[int, User] = {} + self._guilds: dict[int, Guild] = {} + self._polls: dict[int, Poll] = {} + self._stickers: dict[int, list[GuildSticker]] = {} + self._views: dict[str, View] = {} + self._modals: dict[str, Modal] = {} + self._messages: Deque[Message] = deque(maxlen=self.max_messages) + + self._emojis: dict[int, list[GuildEmoji | AppEmoji]] = {} + + self._private_channels: OrderedDict[int, PrivateChannel] = OrderedDict() + self._private_channels_by_user: dict[int, DMChannel] = {} + + self._guild_members: dict[int, dict[int, Member]] = defaultdict(dict) def _flatten(self, matrix: list[list[T]]) -> list[T]: return [item for row in matrix for item in row] - def clear(self, views: bool = True) -> None: + async def clear(self, views: bool = True) -> None: self._users: dict[int, User] = {} self._guilds: dict[int, Guild] = {} self._polls: dict[int, Poll] = {} diff --git a/discord/app/event_emitter.py b/discord/app/event_emitter.py index 380f3be2d2..9bc38b8b2a 100644 --- a/discord/app/event_emitter.py +++ b/discord/app/event_emitter.py @@ -22,9 +22,10 @@ DEALINGS IN THE SOFTWARE. """ -from abc import ABC +from abc import ABC, abstractmethod from asyncio import Future import asyncio +from collections import defaultdict from typing import Any, Callable, Self, TypeVar from .state import ConnectionState diff --git a/discord/app/state.py b/discord/app/state.py index 1a99cdf99d..85854df43e 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -41,6 +41,7 @@ Sequence, TypeVar, Union, + cast, ) from discord.app.event_emitter import EventEmitter @@ -263,9 +264,9 @@ def __init__( self.cache: Cache = self.cache - def clear(self, *, views: bool = True) -> None: + async def clear(self, *, views: bool = True) -> None: self.user: ClientUser | None = None - self.cache.clear() + await self.cache.clear() self._voice_clients: dict[int, VoiceClient] = {} async def process_chunk_requests( @@ -340,7 +341,7 @@ def deref_user_no_intents(self, user_id: int) -> None: return async def get_user(self, id: int | None) -> User | None: - return await self.cache.get_user(id) + return await self.cache.get_user(cast(int, id)) async def store_emoji(self, guild: Guild, data: EmojiPayload) -> GuildEmoji: return await self.cache.store_guild_emoji(guild, data) @@ -360,8 +361,8 @@ async def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildS async def store_view(self, view: View, message_id: int | None = None) -> None: await self.cache.store_view(view, message_id) - async def store_modal(self, modal: Modal) -> None: - await self.cache.store_modal(modal) + async def store_modal(self, modal: Modal, user_id: int) -> None: + await self.cache.store_modal(modal, user_id) async def prevent_view_updates_for(self, message_id: int) -> View | None: return await self.cache.delete_view_on(message_id) @@ -379,7 +380,7 @@ async def get_guilds(self) -> list[Guild]: return await self.cache.get_all_guilds() async def _get_guild(self, guild_id: int | None) -> Guild | None: - return await self.cache.get_guild(guild_id) + return await self.cache.get_guild(cast(int, guild_id)) async def _add_guild(self, guild: Guild) -> None: await self.cache.add_guild(guild) @@ -408,18 +409,11 @@ async def _remove_emoji(self, emoji: GuildEmoji | AppEmoji) -> None: await self.cache.delete_emoji(emoji) async def get_sticker(self, sticker_id: int | None) -> GuildSticker | None: - return await self.cache.get_sticker(sticker_id) + return await self.cache.get_sticker(cast(int, sticker_id)) async def get_polls(self) -> list[Poll]: return await self.cache.get_all_polls() - def create_poll(self, poll: PollPayload, raw) -> Poll: - channel = self.get_channel(raw.channel_id) or PartialMessageable( - state=self, id=raw.channel_id - ) - message = channel.get_partial_message(raw.message_id) - return Poll.from_dict(poll, message) - async def store_poll(self, poll: Poll, message_id: int): await self.cache.store_poll(poll, message_id) @@ -430,10 +424,10 @@ async def get_private_channels(self) -> list[PrivateChannel]: return await self.cache.get_private_channels() async def _get_private_channel(self, channel_id: int | None) -> PrivateChannel | None: - return await self.cache.get_private_channel(channel_id) + return await self.cache.get_private_channel(cast(int, channel_id)) async def _get_private_channel_by_user(self, user_id: int | None) -> DMChannel | None: - return await self.cache.get_private_channel_by_user(user_id) + return cast(DMChannel | None, await self.cache.get_private_channel_by_user(cast(int, user_id))) async def _add_private_channel(self, channel: PrivateChannel) -> None: await self.cache.store_private_channel(channel) @@ -446,7 +440,7 @@ async def add_dm_channel(self, data: DMChannelPayload) -> DMChannel: return channel async def _get_message(self, msg_id: int | None) -> Message | None: - return await self.cache.get_message(msg_id) + return await self.cache.get_message(cast(int, msg_id)) def _guild_needs_chunking(self, guild: Guild) -> bool: # If presences are enabled then we get back the old guild.large behaviour @@ -461,7 +455,8 @@ async def _get_guild_channel( ) -> tuple[Channel | Thread, Guild | None]: channel_id = int(data["channel_id"]) try: - guild = await self._get_guild(int(guild_id or data["guild_id"])) + # guild_id is in data + guild = await self._get_guild(int(guild_id or data["guild_id"])) # type: ignore except KeyError: channel = DMChannel._from_message(self, channel_id) guild = None diff --git a/discord/interactions.py b/discord/interactions.py index 95c821fdd1..3355248288 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -1243,7 +1243,8 @@ async def send_modal(self, modal: Modal) -> Interaction: ) ) self._responded = True - await self._parent._state.store_modal(modal) + # _data should be present + await self._parent._state.store_modal(modal, int(self._parent._data["user"]["id"])) # type: ignore return self._parent @utils.deprecated("a button with type ButtonType.premium", "2.6") From 160830ee81660c4f5e0d909a18d98ea9a3770260 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Sat, 28 Jun 2025 22:41:27 +0800 Subject: [PATCH 14/38] feat: more thread events --- discord/app/state.py | 51 --------------------------------- discord/events/thread.py | 62 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 52 deletions(-) diff --git a/discord/app/state.py b/discord/app/state.py index 85854df43e..c0627276f1 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -519,57 +519,6 @@ async def query_members( ) raise - - def parse_thread_update(self, data) -> None: - guild_id = int(data["guild_id"]) - guild = self._get_guild(guild_id) - raw = RawThreadUpdateEvent(data) - if guild is None: - _log.debug( - "THREAD_UPDATE referencing an unknown guild ID: %s. Discarding", - guild_id, - ) - return - else: - thread = guild.get_thread(raw.thread_id) - if thread is not None: - old = copy.copy(thread) - thread._update(data) - if thread.archived: - guild._remove_thread(thread) - self.dispatch("thread_update", old, thread) - else: - thread = Thread(guild=guild, state=guild._state, data=data) - if not thread.archived: - guild._add_thread(thread) - self.dispatch("thread_join", thread) - raw.thread = thread - self.dispatch("raw_thread_update", raw) - - def parse_thread_delete(self, data) -> None: - guild_id = int(data["guild_id"]) - guild = self._get_guild(guild_id) - - if guild is None: - _log.debug( - "THREAD_DELETE referencing an unknown guild ID: %s. Discarding", - guild_id, - ) - return - - raw = RawThreadDeleteEvent(data) - thread = guild.get_thread(raw.thread_id) - raw.thread = thread - - self.dispatch("raw_thread_delete", raw) - - if thread is not None: - guild._remove_thread(thread) # type: ignore - self.dispatch("thread_delete", thread) - - if (msg := thread.starting_message) is not None: - msg.thread = None - def parse_thread_list_sync(self, data) -> None: guild_id = int(data["guild_id"]) guild: Guild | None = self._get_guild(guild_id) diff --git a/discord/events/thread.py b/discord/events/thread.py index cdd2480f43..918c5af3cc 100644 --- a/discord/events/thread.py +++ b/discord/events/thread.py @@ -1,8 +1,11 @@ -from typing import Any, Self +from typing import Any, Self, cast from discord import utils +from discord.abc import Snowflake from discord.app.event_emitter import Event from discord.app.state import ConnectionState +from discord.raw_models import RawThreadDeleteEvent, RawThreadUpdateEvent from discord.threads import Thread, ThreadMember +from discord.types.raw_models import ThreadDeleteEvent, ThreadUpdateEvent class ThreadCreate(Event, Thread): @@ -40,7 +43,64 @@ async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | ) ) self.just_joined = False + self.__dict__.update(thread.__dict__) else: + self.__dict__.update(cached_thread.__dict__) self.just_joined = True return self + +class ThreadUpdate(Event, Thread): + __event_name__ = "THREAD_UPDATE" + + def __init__(self) -> None: + ... + + old: Thread + + @classmethod + async def __load__(cls, data: ThreadUpdateEvent, state: ConnectionState) -> Self | None: + guild_id = int(data["guild_id"]) + guild = await state._get_guild(guild_id) + raw = RawThreadUpdateEvent(data) + if guild is None: + return + + self = cls() + + thread = guild.get_thread(raw.thread_id) + if thread: + self.old = thread + await thread._update(thread) + if thread.archived: + guild._remove_thread(cast(Snowflake, raw.thread_id)) + else: + thread = Thread(guild=guild, state=guild._state, data=data) # type: ignore + if not thread.archived: + guild._add_thread(thread) + + self.__dict__.update(thread.__dict__) + return self + +class ThreadDelete(Event, Thread): + __event_name__ = "THREAD_DELETE" + + def __init__(self) -> None: + ... + + @classmethod + async def __load__(cls, data: ThreadDeleteEvent, state: ConnectionState) -> Self | None: + raw = RawThreadDeleteEvent(data) + guild = await state._get_guild(raw.guild_id) + if guild is None: + return + + self = cls() + + thread = guild.get_thread(raw.thread_id) + if thread: + guild._remove_thread(cast(Snowflake, thread.id)) + if (msg := await thread.get_starting_message()) is not None: + msg.thread = None # type: ignore + + return cast(Self, thread) From b27c61be495ff81e46fa121c94973a5dc4d36e48 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Fri, 5 Sep 2025 06:24:46 +0800 Subject: [PATCH 15/38] refactor: thread events --- discord/app/state.py | 102 ---------------------- discord/events/channel.py | 10 +-- discord/events/gateway.py | 6 +- discord/events/message.py | 19 ++--- discord/events/thread.py | 172 +++++++++++++++++++++++++++++++++++++- 5 files changed, 187 insertions(+), 122 deletions(-) diff --git a/discord/app/state.py b/discord/app/state.py index 2e407e9ad1..ad50f534d0 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -491,108 +491,6 @@ async def query_members( ) raise - def parse_thread_list_sync(self, data) -> None: - guild_id = int(data["guild_id"]) - guild: Guild | None = self._get_guild(guild_id) - if guild is None: - _log.debug( - "THREAD_LIST_SYNC referencing an unknown guild ID: %s. Discarding", - guild_id, - ) - return - - try: - channel_ids = set(data["channel_ids"]) - except KeyError: - # If not provided, then the entire guild is being synced - # So all previous thread data should be overwritten - previous_threads = guild._threads.copy() - guild._clear_threads() - else: - previous_threads = guild._filter_threads(channel_ids) - - threads = {d["id"]: guild._store_thread(d) for d in data.get("threads", [])} - - for member in data.get("members", []): - try: - # note: member['id'] is the thread_id - thread = threads[member["id"]] - except KeyError: - continue - else: - thread._add_member(ThreadMember(thread, member)) - - for thread in threads.values(): - old = previous_threads.pop(thread.id, None) - if old is None: - self.dispatch("thread_join", thread) - - for thread in previous_threads.values(): - self.dispatch("thread_remove", thread) - - def parse_thread_member_update(self, data) -> None: - guild_id = int(data["guild_id"]) - guild: Guild | None = self._get_guild(guild_id) - if guild is None: - _log.debug( - "THREAD_MEMBER_UPDATE referencing an unknown guild ID: %s. Discarding", - guild_id, - ) - return - - thread_id = int(data["id"]) - thread: Thread | None = guild.get_thread(thread_id) - if thread is None: - _log.debug( - "THREAD_MEMBER_UPDATE referencing an unknown thread ID: %s. Discarding", - thread_id, - ) - return - - member = ThreadMember(thread, data) - thread.me = member - thread._add_member(member) - - def parse_thread_members_update(self, data) -> None: - guild_id = int(data["guild_id"]) - guild: Guild | None = self._get_guild(guild_id) - if guild is None: - _log.debug( - "THREAD_MEMBERS_UPDATE referencing an unknown guild ID: %s. Discarding", - guild_id, - ) - return - - thread_id = int(data["id"]) - thread: Thread | None = guild.get_thread(thread_id) - raw = RawThreadMembersUpdateEvent(data) - if thread is None: - _log.debug( - ("THREAD_MEMBERS_UPDATE referencing an unknown thread ID: %s. Discarding"), - thread_id, - ) - return - - added_members = [ThreadMember(thread, d) for d in data.get("added_members", [])] - removed_member_ids = [int(x) for x in data.get("removed_member_ids", [])] - self_id = self.self_id - for member in added_members: - thread._add_member(member) - if member.id != self_id: - self.dispatch("thread_member_join", member) - else: - thread.me = member - self.dispatch("thread_join", thread) - - for member_id in removed_member_ids: - member = thread._pop_member(member_id) - if member_id != self_id: - self.dispatch("raw_thread_member_remove", raw) - if member is not None: - self.dispatch("thread_member_remove", member) - else: - thread.me = None - self.dispatch("thread_remove", thread) def parse_guild_member_add(self, data) -> None: guild = self._get_guild(int(data["guild_id"])) diff --git a/discord/events/channel.py b/discord/events/channel.py index 77b4c9e006..b843e1c91f 100644 --- a/discord/events/channel.py +++ b/discord/events/channel.py @@ -25,7 +25,7 @@ from datetime import datetime from copy import copy from typing import Any, Self, TypeVar, cast -from discord import utils +from discord.utils.private import get_as_snowflake, parse_time from discord.abc import GuildChannel, PrivateChannel from discord.app.event_emitter import Event from discord.app.state import ConnectionState @@ -47,7 +47,7 @@ async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | if factory is None: return - guild_id = utils._get_as_snowflake(data, "guild_id") + guild_id = get_as_snowflake(data, "guild_id") guild = await state._get_guild(guild_id) if guild is not None: # the factory can't be a DMChannel or GroupChannel here @@ -107,7 +107,7 @@ async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | await state.emitter.emit("PRIVATE_CHANNEL_UPDATE", (old_channel, channel)) return - guild_id = utils._get_as_snowflake(data, "guild_id") + guild_id = get_as_snowflake(data, "guild_id") guild = await state._get_guild(guild_id) if guild is not None: channel = guild.get_channel(channel_id) @@ -124,7 +124,7 @@ def __init__(self) -> None: ... @classmethod async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | None: - guild = await state._get_guild(utils._get_as_snowflake(data, "guild_id")) + guild = await state._get_guild(get_as_snowflake(data, "guild_id")) channel_id = int(data["id"]) if guild is not None: channel = guild.get_channel(channel_id) @@ -155,5 +155,5 @@ async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | self = cls() self.channel = channel - self.last_pin = utils.parse_time(data["last_pin_timestamp"]) if data["last_pin_timestamp"] else None + self.last_pin = parse_time(data["last_pin_timestamp"]) if data["last_pin_timestamp"] else None return self diff --git a/discord/events/gateway.py b/discord/events/gateway.py index 4b93859d5d..d4c4898404 100644 --- a/discord/events/gateway.py +++ b/discord/events/gateway.py @@ -24,7 +24,7 @@ from typing import Any, Self, cast -from discord import utils +from discord.utils.private import get_as_snowflake from discord.emoji import Emoji from discord.flags import ApplicationFlags from discord.guild import Guild, GuildChannel @@ -77,7 +77,7 @@ async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self: except KeyError: pass else: - self.application_id = utils._get_as_snowflake(application, "id") # type: ignore + self.application_id = get_as_snowflake(application, "id") # type: ignore # flags will always be present here self.application_flags = ApplicationFlags._from_value(application["flags"]) # type: ignore state.application_id = self.application_id @@ -213,7 +213,7 @@ class PresenceUpdate(Event): @classmethod async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: self = cls() - guild_id = utils._get_as_snowflake(data, "guild_id") + guild_id = get_as_snowflake(data, "guild_id") guild = await state._get_guild(guild_id) if guild is None: return diff --git a/discord/events/message.py b/discord/events/message.py index 99dc0f346d..58384f85c6 100644 --- a/discord/events/message.py +++ b/discord/events/message.py @@ -23,10 +23,9 @@ """ from typing import Any, Self -from discord import utils +from discord.utils import private as utils, MISSING from discord.app.state import ConnectionState from discord.channel import StageChannel, TextChannel, VoiceChannel -from discord.emoji import AppEmoji, GuildEmoji from discord.guild import Guild from discord.member import Member from discord.partial_emoji import PartialEmoji @@ -135,7 +134,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self: self.old.author = new_msg.author self.__dict__.update(new_msg.__dict__) else: - self.old = utils.MISSING + self.old = MISSING if poll_data := data.get("poll"): channel = await state.get_channel(raw.channel_id) await state.store_poll( @@ -157,7 +156,7 @@ class ReactionAdd(Event): async def __load__(cls, data: ReactionActionEvent, state: ConnectionState) -> Self: self = cls() emoji = data["emoji"] - emoji_id = utils._get_as_snowflake(emoji, "id") + emoji_id = utils.get_as_snowflake(emoji, "id") emoji = PartialEmoji.with_state(state, id=emoji_id, animated=emoji.get("animated", False), name=emoji["name"]) raw = RawReactionActionEvent(data, emoji, "REACTION_ADD") @@ -181,7 +180,7 @@ async def __load__(cls, data: ReactionActionEvent, state: ConnectionState) -> Se if user: self.user = user else: - self.user = utils.MISSING + self.user = MISSING return self @@ -204,8 +203,8 @@ async def __load__(cls, data: ReactionClearEvent, state: ConnectionState) -> Sel self.message = message self.old_reactions = old_reactions else: - self.message = utils.MISSING - self.old_reactions = utils.MISSING + self.message = MISSING + self.old_reactions = MISSING return self @@ -220,7 +219,7 @@ class ReactionRemove(Event): async def __load__(cls, data: ReactionActionEvent, state: ConnectionState) -> Self: self = cls() emoji = data["emoji"] - emoji_id = utils._get_as_snowflake(emoji, "id") + emoji_id = utils.get_as_snowflake(emoji, "id") emoji = PartialEmoji.with_state(state, id=emoji_id, animated=emoji.get("animated", False), name=emoji["name"]) raw = RawReactionActionEvent(data, emoji, "REACTION_ADD") @@ -247,7 +246,7 @@ async def __load__(cls, data: ReactionActionEvent, state: ConnectionState) -> Se if user: self.user = user else: - self.user = utils.MISSING + self.user = MISSING return self @@ -261,7 +260,7 @@ def __init__(self): @classmethod async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: emoji = data["emoji"] - emoji_id = utils._get_as_snowflake(emoji, "id") + emoji_id = utils.get_as_snowflake(emoji, "id") emoji = PartialEmoji.with_state(self, id=emoji_id, name=emoji["name"]) raw = RawReactionClearEmojiEvent(data, emoji) diff --git a/discord/events/thread.py b/discord/events/thread.py index 8d7a2b50f5..48f9ab6575 100644 --- a/discord/events/thread.py +++ b/discord/events/thread.py @@ -1,12 +1,59 @@ +import logging from typing import Any, Self, cast from discord import utils from discord.abc import Snowflake from discord.app.event_emitter import Event from discord.app.state import ConnectionState -from discord.raw_models import RawThreadDeleteEvent, RawThreadUpdateEvent +from discord.raw_models import RawThreadDeleteEvent, RawThreadMembersUpdateEvent, RawThreadUpdateEvent from discord.threads import Thread, ThreadMember from discord.types.raw_models import ThreadDeleteEvent, ThreadUpdateEvent +from discord.types.threads import ThreadMember as ThreadMemberPayload +_log = logging.getLogger(__name__) + +class ThreadMemberJoin(Event, ThreadMember): + __event_name__ = "THREAD_MEMBER_JOIN" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: ThreadMember, _: ConnectionState) -> Self: + self = cls() + self.__dict__.update(data.__dict__) + return self + +class ThreadJoin(Event, Thread): + __event_name__ = "THREAD_JOIN" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Thread, _: ConnectionState) -> Self: + self = cls() + self.__dict__.update(data.__dict__) + return self + +class ThreadMemberRemove(Event, ThreadMember): + __event_name__ = "THREAD_MEMBER_REMOVE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: ThreadMember, _: ConnectionState) -> Self: + self = cls() + self.__dict__.update(data.__dict__) + return self + +class ThreadRemove(Event, Thread): + __event_name__ = "THREAD_REMOVE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Thread, _: ConnectionState) -> Self: + self = cls() + self.__dict__.update(data.__dict__) + return self class ThreadCreate(Event, Thread): __event_name__ = "THREAD_CREATE" @@ -45,7 +92,10 @@ async def __load__(cls, data: dict[str, Any], state: ConnectionState) -> Self | self.__dict__.update(cached_thread.__dict__) self.just_joined = True - return self + if self.just_joined: + await state.emitter.emit("THREAD_JOIN", self) + else: + return self class ThreadUpdate(Event, Thread): @@ -101,3 +151,121 @@ async def __load__(cls, data: ThreadDeleteEvent, state: ConnectionState) -> Self msg.thread = None # type: ignore return cast(Self, thread) + +class ThreadListSync(Event): + __event_name__ = "THREAD_LIST_SYNC" + + @classmethod + async def __load__(cls, data: dict[str, Any], state) -> Self | None: + guild_id = int(data["guild_id"]) + guild = await state._get_guild(guild_id) + if guild is None: + _log.debug( + "THREAD_LIST_SYNC referencing an unknown guild ID: %s. Discarding", + guild_id, + ) + return + + try: + channel_ids = set(data["channel_ids"]) + except KeyError: + # If not provided, then the entire guild is being synced + # So all previous thread data should be overwritten + previous_threads = guild._threads.copy() + guild._clear_threads() + else: + previous_threads = guild._filter_threads(channel_ids) + + threads = {d["id"]: guild._store_thread(d) for d in data.get("threads", [])} + + for member in data.get("members", []): + try: + # note: member['id'] is the thread_id + thread = threads[member["id"]] + except KeyError: + continue + else: + thread._add_member(ThreadMember(thread, member)) + + for thread in threads.values(): + old = previous_threads.pop(thread.id, None) + if old is None: + await state.emitter.emit("THREAD_JOIN", thread) + + for thread in previous_threads.values(): + await state.emitter.emit("THREAD_REMOVE", thread) + +class ThreadMemberUpdate(Event, ThreadMember): + __event_name__ = "THREAD_MEMBER_UPDATE" + def __init__(self): ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild_id = int(data["guild_id"]) + guild = await state._get_guild(guild_id) + if guild is None: + _log.debug( + "THREAD_MEMBER_UPDATE referencing an unknown guild ID: %s. Discarding", + guild_id, + ) + return + + thread_id = int(data["id"]) + thread: Thread | None = guild.get_thread(thread_id) + if thread is None: + _log.debug( + "THREAD_MEMBER_UPDATE referencing an unknown thread ID: %s. Discarding", + thread_id, + ) + return + + member = ThreadMember(thread, data) + thread.me = member + thread._add_member(member) + self = cls() + self.__dict__.update(member.__dict__) + + return self + +class BulkThreadMemberUpdate(Event): + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild_id = int(data["guild_id"]) + guild = await state._get_guild(guild_id) + if guild is None: + _log.debug( + "THREAD_MEMBERS_UPDATE referencing an unknown guild ID: %s. Discarding", + guild_id, + ) + return + + thread_id = int(data["id"]) + thread: Thread | None = guild.get_thread(thread_id) + raw = RawThreadMembersUpdateEvent(data) + if thread is None: + _log.debug( + ("THREAD_MEMBERS_UPDATE referencing an unknown thread ID: %s. Discarding"), + thread_id, + ) + return + + added_members = [ThreadMember(thread, d) for d in data.get("added_members", [])] + removed_member_ids = [int(x) for x in data.get("removed_member_ids", [])] + self_id = state.self_id + for member in added_members: + thread._add_member(member) + if member.id != self_id: + await state.emitter.emit("THREAD_MEMBER_JOIN", member) + else: + thread.me = member + await state.emitter.emit("THREAD_JOIN", thread) + + for member_id in removed_member_ids: + member = thread._pop_member(member_id) + if member_id != self_id: + if member is not None: + await state.emitter.emit("thread_member_remove", member) + else: + thread.me = None + await state.emitter.emit("thread_remove", thread) + From c5dea903c920cc1a782544c68dbcdbd5debf15ac Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Fri, 5 Sep 2025 20:26:37 +0800 Subject: [PATCH 16/38] refactor!: move some guild events --- discord/app/state.py | 173 -------------------- discord/audit_logs.py | 1 + discord/events/automod.py | 3 + discord/events/guild.py | 323 ++++++++++++++++++++++++++++++++++++++ discord/events/thread.py | 24 +++ 5 files changed, 351 insertions(+), 173 deletions(-) create mode 100644 discord/events/guild.py diff --git a/discord/app/state.py b/discord/app/state.py index ad50f534d0..d3351552d3 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -491,113 +491,6 @@ async def query_members( ) raise - - def parse_guild_member_add(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - "GUILD_MEMBER_ADD referencing an unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - return - - member = Member(guild=guild, data=data, state=self) - if self.member_cache_flags.joined: - guild._add_member(member) - - if guild._member_count is not None: - guild._member_count += 1 - - self.dispatch("member_join", member) - - def parse_guild_member_remove(self, data) -> None: - user = self.store_user(data["user"]) - raw = RawMemberRemoveEvent(data, user) - - guild = self._get_guild(int(data["guild_id"])) - if guild is not None: - if guild._member_count is not None: - guild._member_count -= 1 - - member = guild.get_member(user.id) - if member is not None: - raw.user = member - guild._remove_member(member) # type: ignore - self.dispatch("member_remove", member) - else: - _log.debug( - "GUILD_MEMBER_REMOVE referencing an unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - self.dispatch("raw_member_remove", raw) - - def parse_guild_member_update(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - user = data["user"] - user_id = int(user["id"]) - if guild is None: - _log.debug( - "GUILD_MEMBER_UPDATE referencing an unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - return - - member = guild.get_member(user_id) - if member is not None: - old_member = Member._copy(member) - member._update(data) - user_update = member._update_inner_user(user) - if user_update: - self.dispatch("user_update", user_update[0], user_update[1]) - - self.dispatch("member_update", old_member, member) - else: - if self.member_cache_flags.joined: - member = Member(data=data, guild=guild, state=self) - - # Force an update on the inner user if necessary - user_update = member._update_inner_user(user) - if user_update: - self.dispatch("user_update", user_update[0], user_update[1]) - - guild._add_member(member) - _log.debug( - "GUILD_MEMBER_UPDATE referencing an unknown member ID: %s. Discarding.", - user_id, - ) - - def parse_guild_emojis_update(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - "GUILD_EMOJIS_UPDATE referencing an unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - return - - before_emojis = guild.emojis - for emoji in before_emojis: - self._emojis.pop(emoji.id, None) - # guild won't be None here - guild.emojis = tuple(map(lambda d: self.store_emoji(guild, d), data["emojis"])) # type: ignore - self.dispatch("guild_emojis_update", guild, before_emojis, guild.emojis) - - def parse_guild_stickers_update(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - ("GUILD_STICKERS_UPDATE referencing an unknown guild ID: %s. Discarding."), - data["guild_id"], - ) - return - - before_stickers = guild.stickers - for emoji in before_stickers: - self._stickers.pop(emoji.id, None) - # guild won't be None here - guild.stickers = tuple(map(lambda d: self.store_sticker(guild, d), data["stickers"])) # type: ignore - self.dispatch("guild_stickers_update", guild, before_stickers, guild.stickers) - def _get_create_guild(self, data): if data.get("unavailable") is False: # GUILD_CREATE with unavailable in the response @@ -638,72 +531,6 @@ async def _chunk_and_dispatch(self, guild, unavailable): else: self.dispatch("guild_join", guild) - def parse_guild_create(self, data) -> None: - unavailable = data.get("unavailable") - if unavailable is True: - # joined a guild with unavailable == True so.. - return - - guild = self._get_create_guild(data) - - try: - # Notify the on_ready state, if any, that this guild is complete. - self._ready_state.put_nowait(guild) - except AttributeError: - pass - else: - # If we're waiting for the event, put the rest on hold - return - - # check if it requires chunking - if self._guild_needs_chunking(guild): - asyncio.create_task(self._chunk_and_dispatch(guild, unavailable)) - return - - # Dispatch available if newly available - if unavailable is False: - self.dispatch("guild_available", guild) - else: - self.dispatch("guild_join", guild) - - def parse_guild_update(self, data) -> None: - guild = self._get_guild(int(data["id"])) - if guild is not None: - old_guild = copy.copy(guild) - guild._from_data(data) - self.dispatch("guild_update", old_guild, guild) - else: - _log.debug( - "GUILD_UPDATE referencing an unknown guild ID: %s. Discarding.", - data["id"], - ) - - def parse_guild_delete(self, data) -> None: - guild = self._get_guild(int(data["id"])) - if guild is None: - _log.debug( - "GUILD_DELETE referencing an unknown guild ID: %s. Discarding.", - data["id"], - ) - return - - if data.get("unavailable", False): - # GUILD_DELETE with unavailable being True means that the - # guild that was available is now currently unavailable - guild.unavailable = True - self.dispatch("guild_unavailable", guild) - return - - # do a cleanup of the messages cache - if self._messages is not None: - self._messages: Deque[Message] | None = deque( - (msg for msg in self._messages if msg.guild != guild), - maxlen=self.max_messages, - ) - - self._remove_guild(guild) - self.dispatch("guild_remove", guild) - def parse_guild_audit_log_entry_create(self, data) -> None: guild = self._get_guild(int(data["guild_id"])) if guild is None: diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 5b1488c769..dac0b70988 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -272,6 +272,7 @@ class AuditLogChanges: "exempt_channels": (None, _transform_channels), } + @staticmethod async def _maybe_await(func: Any) -> Any: if isawaitable(func): return await func diff --git a/discord/events/automod.py b/discord/events/automod.py index 8ab50e2da1..21e6bbc74f 100644 --- a/discord/events/automod.py +++ b/discord/events/automod.py @@ -31,6 +31,7 @@ class AutoModRuleCreate(Event): __event_name__ = "AUTO_MODERATION_RULE_CREATE" + __slots__ = ("rule") rule: AutoModRule @@ -43,6 +44,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self: class AutoModRuleUpdate(Event): __event_name__ = "AUTO_MODERATION_RULE_UPDATE" + __slots__ = ("rule") rule: AutoModRule @@ -55,6 +57,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self: class AutoModRuleDelete(Event): __event_name__ = "AUTO_MODERATION_RULE_DELETE" + __slots__ = ("rule") rule: AutoModRule diff --git a/discord/events/guild.py b/discord/events/guild.py new file mode 100644 index 0000000000..73490d34fe --- /dev/null +++ b/discord/events/guild.py @@ -0,0 +1,323 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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. +""" + +import asyncio +import copy +import logging +from typing import Any, Self +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.emoji import Emoji +from discord.guild import Guild +from discord.member import Member +from discord.raw_models import RawMemberRemoveEvent +from discord.sticker import Sticker + +_log = logging.getLogger(__name__) + +class GuildMemberJoin(Event, Member): + __event_name__ = "GUILD_MEMBER_JOIN" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_MEMBER_ADD referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + member = Member(guild=guild, data=data, state=state) + if state.member_cache_flags.joined: + await guild._add_member(member) + + if guild._member_count is not None: + guild._member_count += 1 + + self = cls() + self.__dict__.update(member.__dict__) + return self + +class GuildMemberRemove(Event, Member): + __event_name__ = "GUILD_MEMBER_REMOVE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + user = await state.store_user(data["user"]) + raw = RawMemberRemoveEvent(data, user) + + guild = await state._get_guild(int(data["guild_id"])) + if guild is not None: + if guild._member_count is not None: + guild._member_count -= 1 + + member = await guild.get_member(user.id) + if member is not None: + raw.user = member + guild._remove_member(member) # type: ignore + self = cls() + self.__dict__.update(member.__dict__) + return self + else: + _log.debug( + "GUILD_MEMBER_REMOVE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + +class GuildMemberUpdate(Event, Member): + __event_name__ = "GUILD_MEMBER_UPDATE" + + old: Member + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + user = data["user"] + user_id = int(user["id"]) + if guild is None: + _log.debug( + "GUILD_MEMBER_UPDATE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + member = await guild.get_member(user_id) + if member is not None: + old_member = Member._copy(member) + await member._update(data) + user_update = member._update_inner_user(user) + if user_update: + await state.emitter.emit("USER_UPDATE", user_update) + + self = cls() + self.__dict__.update(member.__dict__) + self.old = old_member + return self + else: + if state.member_cache_flags.joined: + member = Member(data=data, guild=guild, state=state) + + # Force an update on the inner user if necessary + user_update = member._update_inner_user(user) + if user_update: + await state.emitter.emit("USER_UPDATE", user_update) + + await guild._add_member(member) + _log.debug( + "GUILD_MEMBER_UPDATE referencing an unknown member ID: %s. Discarding.", + user_id, + ) + +class GuildEmojisUpdate(Event): + __event_name__ = "GUILD_EMOJIS_UPDATE" + guild: Guild + emojis: list[Emoji] + old_emojis: list[Emoji] + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_EMOJIS_UPDATE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + before_emojis = guild.emojis + for emoji in before_emojis: + await state.cache.delete_emoji(emoji) + # guild won't be None here + emojis = [] + for emoji in data["emojis"]: + emojis.append(await state.store_emoji(guild, emoji)) + guild.emojis = emojis + self = cls() + self.guild = guild + self.old_emojis = guild.emojis + self.emojis = emojis + +class GuildStickersUpdate(Event): + __event_name__ = "GUILD_STICKERS_UPDATE" + + guild: Guild + stickers: list[Sticker] + old_stickers: list[Sticker] + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + ("GUILD_STICKERS_UPDATE referencing an unknown guild ID: %s. Discarding."), + data["guild_id"], + ) + return + + before_stickers = guild.stickers + for emoji in before_stickers: + await state.cache.delete_sticker(emoji.id) + stickers = [] + for sticker in data["stickers"]: + stickers.append(await state.store_sticker(guild, sticker)) + # guild won't be None here + guild.stickers = stickers + self = cls() + self.old_stickers = stickers + self.stickers = stickers + self.guild = guild + +class GuildAvailable(Event, Guild): + __event_name__ = "GUILD_AVAILABLE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Guild, _: ConnectionState) -> Self: + self = cls() + self.__dict__.update(data.__dict__) + return self + +class GuildUnavailable(Event, Guild): + __event_name__ = "GUILD_UNAVAILABLE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Guild, _: ConnectionState) -> Self: + self = cls() + self.__dict__.update(data.__dict__) + return self + +class GuildJoin(Event, Guild): + __event_name__ = "GUILD_JOIN" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Guild, _: ConnectionState) -> Self: + self = cls() + self.__dict__.update(data.__dict__) + return self + +class GuildCreate(Event, Guild): + __event_name__ = "GUILD_CREATE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + unavailable = data.get("unavailable") + if unavailable is True: + # joined a guild with unavailable == True so.. + return + + guild = await state._get_create_guild(data) + + try: + # Notify the on_ready state, if any, that this guild is complete. + state._ready_state.put_nowait(guild) # type: ignore + except AttributeError: + pass + else: + # If we're waiting for the event, put the rest on hold + return + + # check if it requires chunking + if state._guild_needs_chunking(guild): + asyncio.create_task(state._chunk_and_dispatch(guild, unavailable)) + return + + # Dispatch available if newly available + if unavailable is False: + await state.emitter.emit("GUILD_AVAILABLE", guild) + else: + await state.emitter.emit("GUILD_JOIN", guild) + + self = cls() + self.__dict__.update(data.__dict__) + return self + +class GuildUpdate(Event, Guild): + __event_name__ = "GUILD_UPDATE" + + old: Guild + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["id"])) + if guild is not None: + old_guild = copy.copy(guild) + guild = await guild._from_data(data, state) + self = cls() + self.__dict__.update(guild.__dict__) + self.old = old_guild + return self + else: + _log.debug( + "GUILD_UPDATE referencing an unknown guild ID: %s. Discarding.", + data["id"], + ) + +class GuildDelete(Event, Guild): + __event_name__ = "GUILD_DELETE" + + old: Guild + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["id"])) + if guild is None: + _log.debug( + "GUILD_DELETE referencing an unknown guild ID: %s. Discarding.", + data["id"], + ) + return + + if data.get("unavailable", False): + # GUILD_DELETE with unavailable being True means that the + # guild that was available is now currently unavailable + guild.unavailable = True + await state.emitter.emit("GUILD_UNAVAILABLE", guild) + return + + # do a cleanup of the messages cache + messages = await state.cache.get_all_messages() + await asyncio.gather(*[state.cache.delete_message(message.id) for message in messages]) + + await state._remove_guild(guild) + self = cls() + self.__dict__.update(guild.__dict__) + return self \ No newline at end of file diff --git a/discord/events/thread.py b/discord/events/thread.py index 48f9ab6575..8b80639c62 100644 --- a/discord/events/thread.py +++ b/discord/events/thread.py @@ -1,3 +1,27 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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. +""" + import logging from typing import Any, Self, cast from discord import utils From 93bd39f0e0e8352924169bbe9800ad06ae991de5 Mon Sep 17 00:00:00 2001 From: VincentRPS Date: Fri, 5 Sep 2025 20:50:35 +0800 Subject: [PATCH 17/38] fix: make `PRESENCE_UPDATE` actually emit events --- discord/events/gateway.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discord/events/gateway.py b/discord/events/gateway.py index d4c4898404..e1fb8561d5 100644 --- a/discord/events/gateway.py +++ b/discord/events/gateway.py @@ -227,6 +227,8 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: self.old = Member._copy(member) self.new = member user_update = member._presence_update(data=data, user=user) + await state.emitter.emit("USER_UPDATE", user_update) + return self class UserUpdate(Event, User): From 22a8232332002cc61452b665c8f743d056b9f6b7 Mon Sep 17 00:00:00 2001 From: Soheab <33902984+Soheab@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:05:11 +0200 Subject: [PATCH 18/38] refactor: events guild_ban_add, guild_ban_remove, guild_role_create, guild_role_update, guild_role_delete --- discord/app/state.py | 67 --------------- discord/events/guild.py | 180 ++++++++++++++++++++++++++++++++++++++-- discord/types/member.py | 2 +- 3 files changed, 176 insertions(+), 73 deletions(-) diff --git a/discord/app/state.py b/discord/app/state.py index d3351552d3..5b3f2efc84 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -548,73 +548,6 @@ def parse_guild_audit_log_entry_create(self, data) -> None: entry = AuditLogEntry(users={data["user_id"]: user}, data=data, guild=guild) self.dispatch("audit_log_entry", entry) - def parse_guild_ban_add(self, data) -> None: - # we make the assumption that GUILD_BAN_ADD is done - # before GUILD_MEMBER_REMOVE is called - # hence we don't remove it from cache or do anything - # strange with it, the main purpose of this event - # is mainly to dispatch to another event worth listening to for logging - guild = self._get_guild(int(data["guild_id"])) - if guild is not None: - try: - user = User(data=data["user"], state=self) - except KeyError: - pass - else: - member = guild.get_member(user.id) or user - self.dispatch("member_ban", guild, member) - - def parse_guild_ban_remove(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is not None and "user" in data: - user = self.store_user(data["user"]) - self.dispatch("member_unban", guild, user) - - def parse_guild_role_create(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - "GUILD_ROLE_CREATE referencing an unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - return - - role_data = data["role"] - role = Role(guild=guild, data=role_data, state=self) - guild._add_role(role) - self.dispatch("guild_role_create", role) - - def parse_guild_role_delete(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is not None: - role_id = int(data["role_id"]) - try: - role = guild._remove_role(role_id) - except KeyError: - return - else: - self.dispatch("guild_role_delete", role) - else: - _log.debug( - "GUILD_ROLE_DELETE referencing an unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - - def parse_guild_role_update(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is not None: - role_data = data["role"] - role_id = int(role_data["id"]) - role = guild.get_role(role_id) - if role is not None: - old_role = copy.copy(role) - role._update(role_data) - self.dispatch("guild_role_update", old_role, role) - else: - _log.debug( - "GUILD_ROLE_UPDATE referencing an unknown guild ID: %s. Discarding.", - data["guild_id"], - ) def parse_guild_members_chunk(self, data) -> None: guild_id = int(data["guild_id"]) diff --git a/discord/events/guild.py b/discord/events/guild.py index 73490d34fe..1c94eae8d2 100644 --- a/discord/events/guild.py +++ b/discord/events/guild.py @@ -25,7 +25,8 @@ import asyncio import copy import logging -from typing import Any, Self +from typing import TYPE_CHECKING, Any, Self +from discord import Role from discord.app.event_emitter import Event from discord.app.state import ConnectionState from discord.emoji import Emoji @@ -34,8 +35,12 @@ from discord.raw_models import RawMemberRemoveEvent from discord.sticker import Sticker +if TYPE_CHECKING: + from ..types.member import MemberWithUser + _log = logging.getLogger(__name__) + class GuildMemberJoin(Event, Member): __event_name__ = "GUILD_MEMBER_JOIN" @@ -62,6 +67,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: self.__dict__.update(member.__dict__) return self + class GuildMemberRemove(Event, Member): __event_name__ = "GUILD_MEMBER_REMOVE" @@ -90,6 +96,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: data["guild_id"], ) + class GuildMemberUpdate(Event, Member): __event_name__ = "GUILD_MEMBER_UPDATE" @@ -136,6 +143,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: user_id, ) + class GuildEmojisUpdate(Event): __event_name__ = "GUILD_EMOJIS_UPDATE" guild: Guild @@ -165,6 +173,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: self.old_emojis = guild.emojis self.emojis = emojis + class GuildStickersUpdate(Event): __event_name__ = "GUILD_STICKERS_UPDATE" @@ -177,7 +186,9 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: guild = await state._get_guild(int(data["guild_id"])) if guild is None: _log.debug( - ("GUILD_STICKERS_UPDATE referencing an unknown guild ID: %s. Discarding."), + ( + "GUILD_STICKERS_UPDATE referencing an unknown guild ID: %s. Discarding." + ), data["guild_id"], ) return @@ -195,6 +206,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: self.stickers = stickers self.guild = guild + class GuildAvailable(Event, Guild): __event_name__ = "GUILD_AVAILABLE" @@ -206,6 +218,7 @@ async def __load__(cls, data: Guild, _: ConnectionState) -> Self: self.__dict__.update(data.__dict__) return self + class GuildUnavailable(Event, Guild): __event_name__ = "GUILD_UNAVAILABLE" @@ -217,6 +230,7 @@ async def __load__(cls, data: Guild, _: ConnectionState) -> Self: self.__dict__.update(data.__dict__) return self + class GuildJoin(Event, Guild): __event_name__ = "GUILD_JOIN" @@ -228,6 +242,7 @@ async def __load__(cls, data: Guild, _: ConnectionState) -> Self: self.__dict__.update(data.__dict__) return self + class GuildCreate(Event, Guild): __event_name__ = "GUILD_CREATE" @@ -244,7 +259,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: try: # Notify the on_ready state, if any, that this guild is complete. - state._ready_state.put_nowait(guild) # type: ignore + state._ready_state.put_nowait(guild) # type: ignore except AttributeError: pass else: @@ -266,6 +281,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: self.__dict__.update(data.__dict__) return self + class GuildUpdate(Event, Guild): __event_name__ = "GUILD_UPDATE" @@ -289,6 +305,7 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: data["id"], ) + class GuildDelete(Event, Guild): __event_name__ = "GUILD_DELETE" @@ -315,9 +332,162 @@ async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: # do a cleanup of the messages cache messages = await state.cache.get_all_messages() - await asyncio.gather(*[state.cache.delete_message(message.id) for message in messages]) + await asyncio.gather( + *[state.cache.delete_message(message.id) for message in messages] + ) await state._remove_guild(guild) self = cls() self.__dict__.update(guild.__dict__) - return self \ No newline at end of file + return self + + +class GuildBanAdd(Event, Member): + __event_name__ = "GUILD_BAN_ADD" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_BAN_ADD referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + member = await guild.get_member(int(data["user"]["id"])) + if member is None: + fake_data: MemberWithUser = { + "user": data["user"], + "roles": [], + "joined_at": None, + "deaf": False, + "mute": False, + } + member = Member(guild=guild, data=fake_data, state=state) + + self = cls() + self.__dict__.update(member.__dict__) + return self + + +class GuildBanRemove(Event, Member): + __event_name__ = "GUILD_BAN_REMOVE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_BAN_ADD referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + member = await guild.get_member(int(data["user"]["id"])) + if member is None: + fake_data: MemberWithUser = { + "user": data["user"], + "roles": [], + "joined_at": None, + "deaf": False, + "mute": False, + } + member = Member(guild=guild, data=fake_data, state=state) + + self = cls() + self.__dict__.update(member.__dict__) + return self + + +class GuildRoleCreate(Event, Role): + __event_name__ = "GUILD_ROLE_CREATE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_ROLE_CREATE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + role = Role(guild=guild, data=data["role"], state=state) + guild._add_role(role) + + self = cls() + self.__dict__.update(role.__dict__) + return self + + +class GuildRoleUpdate(Event, Role): + __event_name__ = "GUILD_ROLE_UPDATE" + + old: Role + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_ROLE_UPDATE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + role_id: int = int(data["role"]["id"]) + role = guild.get_role(role_id) + if role is None: + _log.debug( + "GUILD_ROLE_UPDATE referencing an unknown role ID: %s. Discarding.", + data["role"]["id"], + ) + return + + old_role = copy.copy(role) + await role._update(data["role"]) + + self = cls() + self.__dict__.update(role.__dict__) + self.old = old_role + return self + + +class GuildRoleDelete(Event, Role): + __event_name__ = "GUILD_ROLE_DELETE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_ROLE_DELETE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + role_id: int = int(data["role_id"]) + role = guild.get_role(role_id) + if role is None: + _log.debug( + "GUILD_ROLE_DELETE referencing an unknown role ID: %s. Discarding.", + data["role_id"], + ) + return + + guild._remove_role(role_id) + + self = cls() + self.__dict__.update(role.__dict__) + return self diff --git a/discord/types/member.py b/discord/types/member.py index 618bb13efe..e2796dd88e 100644 --- a/discord/types/member.py +++ b/discord/types/member.py @@ -35,7 +35,7 @@ class Nickname(TypedDict): class PartialMember(TypedDict): roles: SnowflakeList - joined_at: str + joined_at: str | None deaf: bool mute: bool From 8555732c0a18b18d90cd36c2c9e0207edfcb383c Mon Sep 17 00:00:00 2001 From: Paillat Date: Sun, 2 Nov 2025 18:54:46 +0100 Subject: [PATCH 19/38] :bug: Fix ruff --- discord/enums.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index e2e220895f..be7efc48d8 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -1052,12 +1052,6 @@ class ApplicationCommandPermissionType(Enum): channel = 3 -def create_unknown_value(cls: type[T], val: Any) -> T: - value_cls = cls._enum_value_cls_ # type: ignore - name = f"unknown_{val}" - return value_cls(name=name, value=val) - - def try_enum(cls: type[E], val: Any) -> E: """A function that tries to turn the value into enum ``cls``. From b1a350f153c0e203de7265c043a65e6a7ddb8de8 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sun, 2 Nov 2025 19:19:32 +0100 Subject: [PATCH 20/38] :sparkles: Make it run --- discord/app/cache.py | 46 +++++++++++++++++++++++------------ discord/app/event_emitter.py | 11 ++++++--- discord/app/state.py | 6 +++-- discord/client.py | 4 ++- discord/events/guild.py | 4 ++- discord/guild.py | 9 ++++--- discord/member.py | 4 ++- discord/message.py | 3 ++- discord/raw_models.py | 4 ++- discord/shard.py | 2 +- discord/types/interactions.py | 1 - 11 files changed, 62 insertions(+), 32 deletions(-) diff --git a/discord/app/cache.py b/discord/app/cache.py index 246454ea7b..545e1198ee 100644 --- a/discord/app/cache.py +++ b/discord/app/cache.py @@ -23,14 +23,12 @@ """ from collections import OrderedDict, defaultdict, deque -from typing import Deque, Protocol, TypeVar +from typing import TYPE_CHECKING, Deque, Protocol, TypeVar from discord import utils -from discord.app.state import ConnectionState from discord.member import Member from discord.message import Message -from ..abc import MessageableChannel, PrivateChannel from ..channel import DMChannel from ..emoji import AppEmoji, GuildEmoji from ..guild import Guild @@ -45,10 +43,28 @@ from ..ui.view import View from ..user import User +if TYPE_CHECKING: + from discord.app.state import ConnectionState + + from ..abc import MessageableChannel, PrivateChannel + T = TypeVar("T") class Cache(Protocol): + def __init__(self): + self.__state: ConnectionState | None = None + + @property + def _state(self) -> "ConnectionState": + if self.__state is None: + raise RuntimeError("Cache state has not been initialized.") + return self.__state + + @_state.setter + def _state(self, state: "ConnectionState") -> None: + self.__state = state + # users async def get_all_users(self) -> list[User]: ... @@ -114,17 +130,17 @@ async def store_poll(self, poll: Poll, message_id: int) -> None: ... # private channels - async def get_private_channels(self) -> list[PrivateChannel]: ... + async def get_private_channels(self) -> "list[PrivateChannel]": ... - async def get_private_channel(self, channel_id: int) -> PrivateChannel: ... + async def get_private_channel(self, channel_id: int) -> "PrivateChannel": ... - async def get_private_channel_by_user(self, user_id: int) -> PrivateChannel | None: ... + async def get_private_channel_by_user(self, user_id: int) -> "PrivateChannel | None": ... - async def store_private_channel(self, channel: PrivateChannel) -> None: ... + async def store_private_channel(self, channel: "PrivateChannel") -> None: ... # messages - async def store_message(self, message: MessagePayload, channel: MessageableChannel) -> Message: ... + async def store_message(self, message: MessagePayload, channel: "MessageableChannel") -> Message: ... async def upsert_message(self, message: Message) -> None: ... @@ -152,8 +168,8 @@ async def clear(self, views: bool = True) -> None: ... class MemoryCache(Cache): - def __init__(self, max_messages: int | None = None, *, state: ConnectionState): - self._state = state + def __init__(self, max_messages: int | None = None) -> None: + self.__state: ConnectionState | None = None self.max_messages = max_messages self._users: dict[int, User] = {} self._guilds: dict[int, Guild] = {} @@ -312,10 +328,10 @@ async def store_poll(self, poll: Poll, message_id: int) -> None: # private channels - async def get_private_channels(self) -> list[PrivateChannel]: + async def get_private_channels(self) -> "list[PrivateChannel]": return list(self._private_channels.values()) - async def get_private_channel(self, channel_id: int) -> PrivateChannel | None: + async def get_private_channel(self, channel_id: int) -> "PrivateChannel | None": try: channel = self._private_channels[channel_id] except KeyError: @@ -324,7 +340,7 @@ async def get_private_channel(self, channel_id: int) -> PrivateChannel | None: self._private_channels.move_to_end(channel_id) return channel - async def store_private_channel(self, channel: PrivateChannel) -> None: + async def store_private_channel(self, channel: "PrivateChannel") -> None: channel_id = channel.id self._private_channels[channel_id] = channel @@ -336,7 +352,7 @@ async def store_private_channel(self, channel: PrivateChannel) -> None: if isinstance(channel, DMChannel) and channel.recipient: self._private_channels_by_user[channel.recipient.id] = channel - async def get_private_channel_by_user(self, user_id: int) -> PrivateChannel | None: + async def get_private_channel_by_user(self, user_id: int) -> "PrivateChannel | None": return self._private_channels_by_user.get(user_id) # messages @@ -344,7 +360,7 @@ async def get_private_channel_by_user(self, user_id: int) -> PrivateChannel | No async def upsert_message(self, message: Message) -> None: self._messages.append(message) - async def store_message(self, message: MessagePayload, channel: MessageableChannel) -> Message: + async def store_message(self, message: MessagePayload, channel: "MessageableChannel") -> Message: msg = await Message._from_data(state=self._state, channel=channel, data=message) self._messages.append(msg) return msg diff --git a/discord/app/event_emitter.py b/discord/app/event_emitter.py index 3cf51dde82..bdcfb25f43 100644 --- a/discord/app/event_emitter.py +++ b/discord/app/event_emitter.py @@ -26,9 +26,12 @@ from abc import ABC, abstractmethod from asyncio import Future from collections import defaultdict -from typing import Any, Callable, Self, TypeVar +from typing import TYPE_CHECKING, Any, Callable, TypeVar -from .state import ConnectionState +from typing_extensions import Self + +if TYPE_CHECKING: + from .state import ConnectionState T = TypeVar("T", bound="Event") @@ -38,11 +41,11 @@ class Event(ABC): @classmethod @abstractmethod - async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: ... + async def __load__(cls, data: Any, state: "ConnectionState") -> Self | None: ... class EventEmitter: - def __init__(self, state: ConnectionState) -> None: + def __init__(self, state: "ConnectionState") -> None: self._listeners: dict[type[Event], list[Callable]] = {} self._events: dict[str, list[type[Event]]] self._wait_fors: dict[type[Event], list[Future]] = defaultdict(list) diff --git a/discord/app/state.py b/discord/app/state.py index 22fd18159c..0259f6c047 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -238,6 +238,7 @@ def __init__( self._activity: ActivityPayload | None = activity self._status: str | None = status self._intents: Intents = intents + self._voice_clients: dict[int, VoiceClient] = {} if not intents.members or cache_flags._empty: self.store_user = self.create_user # type: ignore @@ -247,12 +248,13 @@ def __init__( self.emitter = EventEmitter(self) - self.cache: Cache = self.cache + self.cache: Cache = cache + self.cache._state = self async def clear(self, *, views: bool = True) -> None: self.user: ClientUser | None = None await self.cache.clear() - self._voice_clients: dict[int, VoiceClient] = {} + self._voice_clients = {} async def process_chunk_requests( self, guild_id: int, nonce: str | None, members: list[Member], complete: bool diff --git a/discord/client.py b/discord/client.py index b0a428d850..71bd01751e 100644 --- a/discord/client.py +++ b/discord/client.py @@ -39,6 +39,7 @@ from . import utils from .activity import ActivityTypes, BaseActivity, create_activity +from .app.cache import Cache, MemoryCache from .app.state import ConnectionState from .appinfo import AppInfo, PartialAppInfo from .application_role_connection import ApplicationRoleConnectionMetadata @@ -309,6 +310,7 @@ def _get_state(self, **options: Any) -> ConnectionState: hooks=self._hooks, http=self.http, loop=self.loop, + cache=MemoryCache(), **options, ) @@ -1022,7 +1024,7 @@ async def get_stage_instance(self, id: int, /) -> StageInstance | None: Optional[:class:`.StageInstance`] The stage instance or ``None`` if not found. """ - from .channel import StageChannel # noqa: PLC0415 + from .channel import StageChannel channel = await self._connection.get_channel(id) diff --git a/discord/events/guild.py b/discord/events/guild.py index ba57041fda..3c4d369faf 100644 --- a/discord/events/guild.py +++ b/discord/events/guild.py @@ -25,7 +25,9 @@ import asyncio import copy import logging -from typing import TYPE_CHECKING, Any, Self +from typing import TYPE_CHECKING, Any + +from typing_extensions import Self from discord import Role from discord.app.event_emitter import Event diff --git a/discord/guild.py b/discord/guild.py index f52570bbbc..9b390a3586 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -36,7 +36,6 @@ List, NamedTuple, Optional, - Self, Sequence, Tuple, Union, @@ -44,6 +43,8 @@ overload, ) +from typing_extensions import Self + from . import abc, utils from .asset import Asset from .automod import AutoModAction, AutoModRule, AutoModTriggerMetadata @@ -2565,7 +2566,7 @@ async def templates(self) -> list[Template]: Forbidden You don't have permissions to get the templates. """ - from .template import Template # noqa: PLC0415 + from .template import Template data = await self._state.http.guild_templates(self.id) return [await Template.from_data(data=d, state=self._state) for d in data] @@ -2588,7 +2589,7 @@ async def webhooks(self) -> list[Webhook]: You don't have permissions to get the webhooks. """ - from .webhook import Webhook # noqa: PLC0415 + from .webhook import Webhook # circular import data = await self._state.http.guild_webhooks(self.id) return [Webhook.from_state(d, state=self._state) for d in data] @@ -2678,7 +2679,7 @@ async def create_template(self, *, name: str, description: str | utils.Undefined description: :class:`str` The description of the template. """ - from .template import Template # noqa: PLC0415 + from .template import Template # circular import payload = {"name": name} diff --git a/discord/member.py b/discord/member.py index 1ae9ad1e5e..7e411884b7 100644 --- a/discord/member.py +++ b/discord/member.py @@ -30,7 +30,9 @@ import itertools import sys from operator import attrgetter -from typing import TYPE_CHECKING, Any, Self, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from typing_extensions import Self import discord.abc diff --git a/discord/message.py b/discord/message.py index 6ea9a34259..e6aa2e15ad 100644 --- a/discord/message.py +++ b/discord/message.py @@ -35,7 +35,6 @@ Any, Callable, ClassVar, - Self, Sequence, TypeVar, Union, @@ -43,6 +42,8 @@ ) from urllib.parse import parse_qs, urlparse +from typing_extensions import Self + from . import utils from .channel import PartialMessageable from .components import _component_factory diff --git a/discord/raw_models.py b/discord/raw_models.py index a5783aeaf3..9a91c3ce35 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -26,7 +26,9 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING + +from typing_extensions import Self from .automod import AutoModAction, AutoModTriggerType from .enums import ( diff --git a/discord/shard.py b/discord/shard.py index 355a5ee198..dc03ee4859 100644 --- a/discord/shard.py +++ b/discord/shard.py @@ -33,6 +33,7 @@ from .app.state import AutoShardedConnectionState from .backoff import ExponentialBackoff +from .client import Client from .enums import Status from .errors import ( ClientException, @@ -48,7 +49,6 @@ from .gateway import DiscordWebSocket EI = TypeVar("EI", bound="EventItem") - __all__ = ( "AutoShardedClient", "ShardInfo", diff --git a/discord/types/interactions.py b/discord/types/interactions.py index a2cf9dade9..235d9722c9 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -25,7 +25,6 @@ from __future__ import annotations -from turtle import st from typing import TYPE_CHECKING, Dict, Literal, Union from ..permissions import Permissions From 2e2cfd50c7800c98c99907dbd3744c88d5241cb6 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sun, 2 Nov 2025 19:39:10 +0100 Subject: [PATCH 21/38] feat(events): migrate parse_x events to Event subclasses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate 18 remaining parse_x methods from ConnectionState to new Event subclasses following the established event migration pattern. Created event files: - audit_log.py: GuildAuditLogEntryCreate - scheduled_event.py: GuildScheduledEvent{Create,Update,Delete,UserAdd,UserRemove} - integration.py: GuildIntegrationsUpdate, Integration{Create,Update,Delete} - stage_instance.py: StageInstance{Create,Update,Delete} - voice.py: VoiceStateUpdate, VoiceServerUpdate, VoiceChannelStatusUpdate - typing.py: TypingStart - webhook.py: WebhooksUpdate Changes to ConnectionState: - Removed 18 parse_x methods - Removed _get_typing_user helper (moved to typing.py) - Cleaned up unused imports (AuditLogEntry, ScheduledEventStatus, _integration_factory, ScheduledEvent, StageInstance) - Kept parse_guild_members_chunk (internal infrastructure) Bug fixes: - Fixed subscriber count decrement in GuildScheduledEventUserRemove 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- discord/app/state.py | 323 +----------------------------- discord/events/audit_log.py | 64 ++++++ discord/events/integration.py | 127 ++++++++++++ discord/events/scheduled_event.py | 178 ++++++++++++++++ discord/events/stage_instance.py | 115 +++++++++++ discord/events/typing.py | 92 +++++++++ discord/events/voice.py | 152 ++++++++++++++ discord/events/webhook.py | 70 +++++++ 8 files changed, 799 insertions(+), 322 deletions(-) create mode 100644 discord/events/audit_log.py create mode 100644 discord/events/integration.py create mode 100644 discord/events/scheduled_event.py create mode 100644 discord/events/stage_instance.py create mode 100644 discord/events/typing.py create mode 100644 discord/events/voice.py create mode 100644 discord/events/webhook.py diff --git a/discord/app/state.py b/discord/app/state.py index 0259f6c047..b57ad5e776 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -46,15 +46,13 @@ from .. import utils from ..activity import BaseActivity -from ..audit_logs import AuditLogEntry from ..automod import AutoModRule from ..channel import * from ..channel import _channel_factory from ..emoji import AppEmoji, GuildEmoji -from ..enums import ChannelType, InteractionType, ScheduledEventStatus, Status, try_enum +from ..enums import ChannelType, InteractionType, Status, try_enum from ..flags import ApplicationFlags, Intents, MemberCacheFlags from ..guild import Guild -from ..integrations import _integration_factory from ..interactions import Interaction from ..invite import Invite from ..member import Member @@ -66,8 +64,6 @@ from ..poll import Poll, PollAnswerCount from ..raw_models import * from ..role import Role -from ..scheduled_events import ScheduledEvent -from ..stage_instance import StageInstance from ..sticker import GuildSticker from ..threads import Thread, ThreadMember from ..ui.modal import Modal @@ -531,23 +527,6 @@ async def _chunk_and_dispatch(self, guild, unavailable): else: self.dispatch("guild_join", guild) - def parse_guild_audit_log_entry_create(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - ("GUILD_AUDIT_LOG_ENTRY_CREATE referencing an unknown guild ID: %s. Discarding."), - data["guild_id"], - ) - return - payload = RawAuditLogEntryEvent(data) - payload.guild = guild - self.dispatch("raw_audit_log_entry", payload) - user = self.get_user(payload.user_id) - if user is not None: - data.pop("guild_id") - entry = AuditLogEntry(users={data["user_id"]: user}, data=data, guild=guild) - self.dispatch("audit_log_entry", entry) - def parse_guild_members_chunk(self, data) -> None: guild_id = int(data["guild_id"]) guild = self._get_guild(guild_id) @@ -569,306 +548,6 @@ def parse_guild_members_chunk(self, data) -> None: complete = data.get("chunk_index", 0) + 1 == data.get("chunk_count") self.process_chunk_requests(guild_id, data.get("nonce"), members, complete) - def parse_guild_scheduled_event_create(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - ("GUILD_SCHEDULED_EVENT_CREATE referencing an unknown guild ID: %s. Discarding."), - data["guild_id"], - ) - return - - creator = None if not data.get("creator", None) else guild.get_member(data.get("creator_id")) - scheduled_event = ScheduledEvent(state=self, guild=guild, creator=creator, data=data) - guild._add_scheduled_event(scheduled_event) - self.dispatch("scheduled_event_create", scheduled_event) - - def parse_guild_scheduled_event_update(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - ("GUILD_SCHEDULED_EVENT_UPDATE referencing an unknown guild ID: %s. Discarding."), - data["guild_id"], - ) - return - - creator = None if not data.get("creator", None) else guild.get_member(data.get("creator_id")) - scheduled_event = ScheduledEvent(state=self, guild=guild, creator=creator, data=data) - old_event = guild.get_scheduled_event(int(data["id"])) - guild._add_scheduled_event(scheduled_event) - self.dispatch("scheduled_event_update", old_event, scheduled_event) - - def parse_guild_scheduled_event_delete(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - ("GUILD_SCHEDULED_EVENT_DELETE referencing an unknown guild ID: %s. Discarding."), - data["guild_id"], - ) - return - - creator = None if not data.get("creator", None) else guild.get_member(data.get("creator_id")) - scheduled_event = ScheduledEvent(state=self, guild=guild, creator=creator, data=data) - scheduled_event.status = ScheduledEventStatus.canceled - guild._remove_scheduled_event(scheduled_event) - self.dispatch("scheduled_event_delete", scheduled_event) - - def parse_guild_scheduled_event_user_add(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - ("GUILD_SCHEDULED_EVENT_USER_ADD referencing an unknown guild ID: %s. Discarding."), - data["guild_id"], - ) - return - - payload = RawScheduledEventSubscription(data, "USER_ADD") - payload.guild = guild - self.dispatch("raw_scheduled_event_user_add", payload) - - member = guild.get_member(data["user_id"]) - if member is not None: - event = guild.get_scheduled_event(data["guild_scheduled_event_id"]) - if event: - event.subscriber_count += 1 - guild._add_scheduled_event(event) - self.dispatch("scheduled_event_user_add", event, member) - - def parse_guild_scheduled_event_user_remove(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - ("GUILD_SCHEDULED_EVENT_USER_REMOVE referencing an unknown guild ID: %s. Discarding."), - data["guild_id"], - ) - return - - payload = RawScheduledEventSubscription(data, "USER_REMOVE") - payload.guild = guild - self.dispatch("raw_scheduled_event_user_remove", payload) - - member = guild.get_member(data["user_id"]) - if member is not None: - event = guild.get_scheduled_event(data["guild_scheduled_event_id"]) - if event: - event.subscriber_count += 1 - guild._add_scheduled_event(event) - self.dispatch("scheduled_event_user_remove", event, member) - - def parse_guild_integrations_update(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is not None: - self.dispatch("guild_integrations_update", guild) - else: - _log.debug( - ("GUILD_INTEGRATIONS_UPDATE referencing an unknown guild ID: %s. Discarding."), - data["guild_id"], - ) - - def parse_integration_create(self, data) -> None: - guild_id = int(data.pop("guild_id")) - guild = self._get_guild(guild_id) - if guild is not None: - cls, _ = _integration_factory(data["type"]) - integration = cls(data=data, guild=guild) - self.dispatch("integration_create", integration) - else: - _log.debug( - "INTEGRATION_CREATE referencing an unknown guild ID: %s. Discarding.", - guild_id, - ) - - def parse_integration_update(self, data) -> None: - guild_id = int(data.pop("guild_id")) - guild = self._get_guild(guild_id) - if guild is not None: - cls, _ = _integration_factory(data["type"]) - integration = cls(data=data, guild=guild) - self.dispatch("integration_update", integration) - else: - _log.debug( - "INTEGRATION_UPDATE referencing an unknown guild ID: %s. Discarding.", - guild_id, - ) - - def parse_integration_delete(self, data) -> None: - guild_id = int(data["guild_id"]) - guild = self._get_guild(guild_id) - if guild is not None: - raw = RawIntegrationDeleteEvent(data) - self.dispatch("raw_integration_delete", raw) - else: - _log.debug( - "INTEGRATION_DELETE referencing an unknown guild ID: %s. Discarding.", - guild_id, - ) - - def parse_webhooks_update(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is None: - _log.debug( - "WEBHOOKS_UPDATE referencing an unknown guild ID: %s. Discarding", - data["guild_id"], - ) - return - - channel_id = data["channel_id"] - if channel_id is not None: - channel = guild.get_channel(int(channel_id)) - if channel is not None: - self.dispatch("webhooks_update", channel) - else: - _log.debug( - "WEBHOOKS_UPDATE referencing an unknown channel ID: %s. Discarding.", - data["channel_id"], - ) - else: - _log.debug( - "WEBHOOKS_UPDATE channel ID was null for guild: %s. Discarding.", - data["guild_id"], - ) - - def parse_stage_instance_create(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is not None: - stage_instance = StageInstance(guild=guild, state=self, data=data) - guild._stage_instances[stage_instance.id] = stage_instance - self.dispatch("stage_instance_create", stage_instance) - else: - _log.debug( - "STAGE_INSTANCE_CREATE referencing unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - - def parse_stage_instance_update(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is not None: - stage_instance = guild._stage_instances.get(int(data["id"])) - if stage_instance is not None: - old_stage_instance = copy.copy(stage_instance) - stage_instance._update(data) - self.dispatch("stage_instance_update", old_stage_instance, stage_instance) - else: - _log.debug( - ("STAGE_INSTANCE_UPDATE referencing unknown stage instance ID: %s. Discarding."), - data["id"], - ) - else: - _log.debug( - "STAGE_INSTANCE_UPDATE referencing unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - - def parse_stage_instance_delete(self, data) -> None: - guild = self._get_guild(int(data["guild_id"])) - if guild is not None: - try: - stage_instance = guild._stage_instances.pop(int(data["id"])) - except KeyError: - pass - else: - self.dispatch("stage_instance_delete", stage_instance) - else: - _log.debug( - "STAGE_INSTANCE_DELETE referencing unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - - def parse_voice_state_update(self, data) -> None: - guild = self._get_guild(get_as_snowflake(data, "guild_id")) - channel_id = get_as_snowflake(data, "channel_id") - flags = self.member_cache_flags - # self.user is *always* cached when this is called - self_id = self.user.id # type: ignore - if guild is not None: - if int(data["user_id"]) == self_id: - voice = self._get_voice_client(guild.id) - if voice is not None: - coro = voice.on_voice_state_update(data) - asyncio.create_task(logging_coroutine(coro, info="Voice Protocol voice state update handler")) - member, before, after = guild._update_voice_state(data, channel_id) # type: ignore - if member is not None: - if flags.voice: - if channel_id is None and flags._voice_only and member.id != self_id: - # Only remove from cache if we only have the voice flag enabled - # Member doesn't meet the Snowflake protocol currently - guild._remove_member(member) # type: ignore - elif channel_id is not None: - guild._add_member(member) - - self.dispatch("voice_state_update", member, before, after) - else: - _log.debug( - ("VOICE_STATE_UPDATE referencing an unknown member ID: %s. Discarding."), - data["user_id"], - ) - - def parse_voice_server_update(self, data) -> None: - try: - key_id = int(data["guild_id"]) - except KeyError: - key_id = int(data["channel_id"]) - - vc = self._get_voice_client(key_id) - if vc is not None: - coro = vc.on_voice_server_update(data) - asyncio.create_task(logging_coroutine(coro, info="Voice Protocol voice server update handler")) - - def parse_voice_channel_status_update(self, data) -> None: - raw = RawVoiceChannelStatusUpdateEvent(data) - self.dispatch("raw_voice_channel_status_update", raw) - guild = self._get_guild(int(data["guild_id"])) - channel_id = int(data["id"]) - if guild is not None: - channel = guild.get_channel(channel_id) - if channel is not None: - old_status = channel.status - channel.status = data.get("status", None) - self.dispatch("voice_channel_status_update", channel, old_status, channel.status) - else: - _log.debug( - "VOICE_CHANNEL_STATUS_UPDATE referencing an unknown channel ID: %s. Discarding.", - channel_id, - ) - else: - _log.debug( - "VOICE_CHANNEL_STATUS_UPDATE referencing unknown guild ID: %s. Discarding.", - data["guild_id"], - ) - - def parse_typing_start(self, data) -> None: - raw = RawTypingEvent(data) - - member_data = data.get("member") - if member_data: - guild = self._get_guild(raw.guild_id) - if guild is not None: - raw.member = Member(data=member_data, guild=guild, state=self) - else: - raw.member = None - else: - raw.member = None - self.dispatch("raw_typing", raw) - - channel, guild = self._get_guild_channel(data) - if channel is not None: - user = raw.member or self._get_typing_user(channel, raw.user_id) - - if user is not None: - self.dispatch("typing", channel, user, raw.when) - - def _get_typing_user(self, channel: MessageableChannel | None, user_id: int) -> User | Member | None: - if isinstance(channel, DMChannel): - return channel.recipient or self.get_user(user_id) - - elif isinstance(channel, (Thread, TextChannel)) and channel.guild is not None: - return channel.guild.get_member(user_id) # type: ignore - - elif isinstance(channel, GroupChannel): - return utils.find(lambda x: x.id == user_id, channel.recipients) - - return self.get_user(user_id) - async def _get_reaction_user(self, channel: MessageableChannel, user_id: int) -> User | Member | None: if isinstance(channel, TextChannel): return await channel.guild.get_member(user_id) diff --git a/discord/events/audit_log.py b/discord/events/audit_log.py new file mode 100644 index 0000000000..27b555543b --- /dev/null +++ b/discord/events/audit_log.py @@ -0,0 +1,64 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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. +""" + +import logging +from typing import Any, Self + +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.audit_logs import AuditLogEntry +from discord.raw_models import RawAuditLogEntryEvent + +_log = logging.getLogger(__name__) + + +class GuildAuditLogEntryCreate(Event, AuditLogEntry): + __event_name__ = "GUILD_AUDIT_LOG_ENTRY_CREATE" + + raw: RawAuditLogEntryEvent + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_AUDIT_LOG_ENTRY_CREATE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + raw = RawAuditLogEntryEvent(data) + raw.guild = guild + + user = await state.get_user(raw.user_id) + if user is not None: + data_copy = data.copy() + data_copy.pop("guild_id", None) + entry = AuditLogEntry(users={data_copy["user_id"]: user}, data=data_copy, guild=guild) + self = cls() + self.raw = raw + self.__dict__.update(entry.__dict__) + return self diff --git a/discord/events/integration.py b/discord/events/integration.py new file mode 100644 index 0000000000..c6dcfe77eb --- /dev/null +++ b/discord/events/integration.py @@ -0,0 +1,127 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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. +""" + +import logging +from typing import Any, Self + +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.guild import Guild +from discord.integrations import Integration, _integration_factory +from discord.raw_models import RawIntegrationDeleteEvent + +_log = logging.getLogger(__name__) + + +class GuildIntegrationsUpdate(Event): + __event_name__ = "GUILD_INTEGRATIONS_UPDATE" + + guild: Guild + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_INTEGRATIONS_UPDATE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + self = cls() + self.guild = guild + return self + + +class IntegrationCreate(Event, Integration): + __event_name__ = "INTEGRATION_CREATE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + data_copy = data.copy() + guild_id = int(data_copy.pop("guild_id")) + guild = await state._get_guild(guild_id) + if guild is None: + _log.debug( + "INTEGRATION_CREATE referencing an unknown guild ID: %s. Discarding.", + guild_id, + ) + return + + integration_cls, _ = _integration_factory(data_copy["type"]) + integration = integration_cls(data=data_copy, guild=guild) + + self = cls() + self.__dict__.update(integration.__dict__) + return self + + +class IntegrationUpdate(Event, Integration): + __event_name__ = "INTEGRATION_UPDATE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + data_copy = data.copy() + guild_id = int(data_copy.pop("guild_id")) + guild = await state._get_guild(guild_id) + if guild is None: + _log.debug( + "INTEGRATION_UPDATE referencing an unknown guild ID: %s. Discarding.", + guild_id, + ) + return + + integration_cls, _ = _integration_factory(data_copy["type"]) + integration = integration_cls(data=data_copy, guild=guild) + + self = cls() + self.__dict__.update(integration.__dict__) + return self + + +class IntegrationDelete(Event): + __event_name__ = "INTEGRATION_DELETE" + + raw: RawIntegrationDeleteEvent + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild_id = int(data["guild_id"]) + guild = await state._get_guild(guild_id) + if guild is None: + _log.debug( + "INTEGRATION_DELETE referencing an unknown guild ID: %s. Discarding.", + guild_id, + ) + return + + raw = RawIntegrationDeleteEvent(data) + + self = cls() + self.raw = raw + return self diff --git a/discord/events/scheduled_event.py b/discord/events/scheduled_event.py new file mode 100644 index 0000000000..4129b06450 --- /dev/null +++ b/discord/events/scheduled_event.py @@ -0,0 +1,178 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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. +""" + +import logging +from typing import Any, Self + +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.enums import ScheduledEventStatus +from discord.member import Member +from discord.raw_models import RawScheduledEventSubscription +from discord.scheduled_events import ScheduledEvent + +_log = logging.getLogger(__name__) + + +class GuildScheduledEventCreate(Event, ScheduledEvent): + __event_name__ = "GUILD_SCHEDULED_EVENT_CREATE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_SCHEDULED_EVENT_CREATE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + creator = None if not data.get("creator", None) else await guild.get_member(data.get("creator_id")) + scheduled_event = ScheduledEvent(state=state, guild=guild, creator=creator, data=data) + guild._add_scheduled_event(scheduled_event) + + self = cls() + self.__dict__.update(scheduled_event.__dict__) + return self + + +class GuildScheduledEventUpdate(Event, ScheduledEvent): + __event_name__ = "GUILD_SCHEDULED_EVENT_UPDATE" + + old: ScheduledEvent | None + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_SCHEDULED_EVENT_UPDATE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + creator = None if not data.get("creator", None) else await guild.get_member(data.get("creator_id")) + scheduled_event = ScheduledEvent(state=state, guild=guild, creator=creator, data=data) + old_event = guild.get_scheduled_event(int(data["id"])) + guild._add_scheduled_event(scheduled_event) + + self = cls() + self.old = old_event + self.__dict__.update(scheduled_event.__dict__) + return self + + +class GuildScheduledEventDelete(Event, ScheduledEvent): + __event_name__ = "GUILD_SCHEDULED_EVENT_DELETE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_SCHEDULED_EVENT_DELETE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + creator = None if not data.get("creator", None) else await guild.get_member(data.get("creator_id")) + scheduled_event = ScheduledEvent(state=state, guild=guild, creator=creator, data=data) + scheduled_event.status = ScheduledEventStatus.canceled + guild._remove_scheduled_event(scheduled_event) + + self = cls() + self.__dict__.update(scheduled_event.__dict__) + return self + + +class GuildScheduledEventUserAdd(Event): + __event_name__ = "GUILD_SCHEDULED_EVENT_USER_ADD" + + raw: RawScheduledEventSubscription + event: ScheduledEvent + member: Member + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_SCHEDULED_EVENT_USER_ADD referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + raw = RawScheduledEventSubscription(data, "USER_ADD") + raw.guild = guild + + member = await guild.get_member(data["user_id"]) + if member is not None: + event = guild.get_scheduled_event(data["guild_scheduled_event_id"]) + if event: + event.subscriber_count += 1 + guild._add_scheduled_event(event) + self = cls() + self.raw = raw + self.event = event + self.member = member + return self + + +class GuildScheduledEventUserRemove(Event): + __event_name__ = "GUILD_SCHEDULED_EVENT_USER_REMOVE" + + raw: RawScheduledEventSubscription + event: ScheduledEvent + member: Member + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "GUILD_SCHEDULED_EVENT_USER_REMOVE referencing an unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + raw = RawScheduledEventSubscription(data, "USER_REMOVE") + raw.guild = guild + + member = await guild.get_member(data["user_id"]) + if member is not None: + event = guild.get_scheduled_event(data["guild_scheduled_event_id"]) + if event: + event.subscriber_count -= 1 + guild._add_scheduled_event(event) + self = cls() + self.raw = raw + self.event = event + self.member = member + return self diff --git a/discord/events/stage_instance.py b/discord/events/stage_instance.py new file mode 100644 index 0000000000..a0740f0dce --- /dev/null +++ b/discord/events/stage_instance.py @@ -0,0 +1,115 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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. +""" + +import copy +import logging +from typing import Any, Self + +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.stage_instance import StageInstance + +_log = logging.getLogger(__name__) + + +class StageInstanceCreate(Event, StageInstance): + __event_name__ = "STAGE_INSTANCE_CREATE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "STAGE_INSTANCE_CREATE referencing unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + stage_instance = StageInstance(guild=guild, state=state, data=data) + guild._stage_instances[stage_instance.id] = stage_instance + + self = cls() + self.__dict__.update(stage_instance.__dict__) + return self + + +class StageInstanceUpdate(Event, StageInstance): + __event_name__ = "STAGE_INSTANCE_UPDATE" + + old: StageInstance + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "STAGE_INSTANCE_UPDATE referencing unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + stage_instance = guild._stage_instances.get(int(data["id"])) + if stage_instance is None: + _log.debug( + "STAGE_INSTANCE_UPDATE referencing unknown stage instance ID: %s. Discarding.", + data["id"], + ) + return + + old_stage_instance = copy.copy(stage_instance) + stage_instance._update(data) + + self = cls() + self.old = old_stage_instance + self.__dict__.update(stage_instance.__dict__) + return self + + +class StageInstanceDelete(Event, StageInstance): + __event_name__ = "STAGE_INSTANCE_DELETE" + + def __init__(self) -> None: ... + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "STAGE_INSTANCE_DELETE referencing unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + try: + stage_instance = guild._stage_instances.pop(int(data["id"])) + except KeyError: + return + + self = cls() + self.__dict__.update(stage_instance.__dict__) + return self diff --git a/discord/events/typing.py b/discord/events/typing.py new file mode 100644 index 0000000000..198a29f8d8 --- /dev/null +++ b/discord/events/typing.py @@ -0,0 +1,92 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 datetime import datetime +from typing import TYPE_CHECKING, Any, Self + +from discord import utils +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.channel import DMChannel, GroupChannel, TextChannel +from discord.member import Member +from discord.raw_models import RawTypingEvent +from discord.threads import Thread +from discord.user import User + +if TYPE_CHECKING: + from discord.message import MessageableChannel + + +class TypingStart(Event): + __event_name__ = "TYPING_START" + + raw: RawTypingEvent + channel: "MessageableChannel" + user: User | Member + when: datetime + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + raw = RawTypingEvent(data) + + member_data = data.get("member") + if member_data: + guild = await state._get_guild(raw.guild_id) + if guild is not None: + raw.member = Member(data=member_data, guild=guild, state=state) + else: + raw.member = None + else: + raw.member = None + + channel, guild = await state._get_guild_channel(data) + if channel is None: + return + + user = raw.member or await _get_typing_user(state, channel, raw.user_id) + if user is None: + return + + self = cls() + self.raw = raw + self.channel = channel # type: ignore + self.user = user + self.when = raw.when + return self + + +async def _get_typing_user( + state: ConnectionState, channel: "MessageableChannel | None", user_id: int +) -> User | Member | None: + """Helper function to get the user who is typing.""" + if isinstance(channel, DMChannel): + return channel.recipient or await state.get_user(user_id) + + elif isinstance(channel, (Thread, TextChannel)) and channel.guild is not None: + return await channel.guild.get_member(user_id) # type: ignore + + elif isinstance(channel, GroupChannel): + return utils.find(lambda x: x.id == user_id, channel.recipients) + + return await state.get_user(user_id) diff --git a/discord/events/voice.py b/discord/events/voice.py new file mode 100644 index 0000000000..99d37f6e41 --- /dev/null +++ b/discord/events/voice.py @@ -0,0 +1,152 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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. +""" + +import asyncio +import logging +from typing import TYPE_CHECKING, Any, Self + +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState +from discord.member import Member, VoiceState +from discord.raw_models import RawVoiceChannelStatusUpdateEvent +from discord.utils.private import get_as_snowflake + +if TYPE_CHECKING: + from discord.abc import VocalGuildChannel + +_log = logging.getLogger(__name__) + + +async def logging_coroutine(coroutine, *, info: str) -> None: + """Helper to log exceptions in coroutines.""" + try: + await coroutine + except Exception: + _log.exception("Exception occurred during %s", info) + + +class VoiceStateUpdate(Event): + __event_name__ = "VOICE_STATE_UPDATE" + + member: Member + before: VoiceState + after: VoiceState + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(get_as_snowflake(data, "guild_id")) + channel_id = get_as_snowflake(data, "channel_id") + flags = state.member_cache_flags + # state.user is *always* cached when this is called + self_id = state.user.id # type: ignore + + if guild is None: + return + + if int(data["user_id"]) == self_id: + voice = state._get_voice_client(guild.id) + if voice is not None: + coro = voice.on_voice_state_update(data) + asyncio.create_task(logging_coroutine(coro, info="Voice Protocol voice state update handler")) + + member, before, after = await guild._update_voice_state(data, channel_id) # type: ignore + if member is None: + _log.debug( + "VOICE_STATE_UPDATE referencing an unknown member ID: %s. Discarding.", + data["user_id"], + ) + return + + if flags.voice: + if channel_id is None and flags._voice_only and member.id != self_id: + # Only remove from cache if we only have the voice flag enabled + # Member doesn't meet the Snowflake protocol currently + guild._remove_member(member) # type: ignore + elif channel_id is not None: + await guild._add_member(member) + + self = cls() + self.member = member + self.before = before + self.after = after + return self + + +class VoiceServerUpdate(Event): + __event_name__ = "VOICE_SERVER_UPDATE" + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + try: + key_id = int(data["guild_id"]) + except KeyError: + key_id = int(data["channel_id"]) + + vc = state._get_voice_client(key_id) + if vc is not None: + coro = vc.on_voice_server_update(data) + asyncio.create_task(logging_coroutine(coro, info="Voice Protocol voice server update handler")) + + # This event doesn't dispatch to user code, it's internal for voice protocol + return None + + +class VoiceChannelStatusUpdate(Event): + __event_name__ = "VOICE_CHANNEL_STATUS_UPDATE" + + raw: RawVoiceChannelStatusUpdateEvent + channel: "VocalGuildChannel" + old_status: str | None + new_status: str | None + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + raw = RawVoiceChannelStatusUpdateEvent(data) + guild = await state._get_guild(int(data["guild_id"])) + channel_id = int(data["id"]) + + if guild is None: + _log.debug( + "VOICE_CHANNEL_STATUS_UPDATE referencing unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + return + + channel = guild.get_channel(channel_id) + if channel is None: + _log.debug( + "VOICE_CHANNEL_STATUS_UPDATE referencing an unknown channel ID: %s. Discarding.", + channel_id, + ) + return + + old_status = channel.status + channel.status = data.get("status", None) + + self = cls() + self.raw = raw + self.channel = channel # type: ignore + self.old_status = old_status + self.new_status = channel.status + return self diff --git a/discord/events/webhook.py b/discord/events/webhook.py new file mode 100644 index 0000000000..74cf8bbbb8 --- /dev/null +++ b/discord/events/webhook.py @@ -0,0 +1,70 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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. +""" + +import logging +from typing import TYPE_CHECKING, Any, Self + +from discord.app.event_emitter import Event +from discord.app.state import ConnectionState + +if TYPE_CHECKING: + from discord.abc import GuildChannel + +_log = logging.getLogger(__name__) + + +class WebhooksUpdate(Event): + __event_name__ = "WEBHOOKS_UPDATE" + + channel: "GuildChannel" + + @classmethod + async def __load__(cls, data: Any, state: ConnectionState) -> Self | None: + guild = await state._get_guild(int(data["guild_id"])) + if guild is None: + _log.debug( + "WEBHOOKS_UPDATE referencing an unknown guild ID: %s. Discarding", + data["guild_id"], + ) + return + + channel_id = data["channel_id"] + if channel_id is None: + _log.debug( + "WEBHOOKS_UPDATE channel ID was null for guild: %s. Discarding.", + data["guild_id"], + ) + return + + channel = guild.get_channel(int(channel_id)) + if channel is None: + _log.debug( + "WEBHOOKS_UPDATE referencing an unknown channel ID: %s. Discarding.", + data["channel_id"], + ) + return + + self = cls() + self.channel = channel # type: ignore + return self From ef0c971e0b5a1ab71b241faeda865f8861f0dbd2 Mon Sep 17 00:00:00 2001 From: Paillat Date: Thu, 13 Nov 2025 12:26:13 +0100 Subject: [PATCH 22/38] feat: Gears stuff and better state (#184) --- discord/__init__.py | 2 +- discord/abc.py | 995 +------- discord/app/cache.py | 32 +- discord/app/event_emitter.py | 103 +- discord/app/state.py | 35 +- discord/appinfo.py | 2 +- discord/asset.py | 31 +- discord/audit_logs.py | 22 +- discord/bot.py | 13 +- discord/channel/__init__.py | 119 + discord/channel/base.py | 2025 +++++++++++++++++ discord/channel/category.py | 273 +++ .../{channel.py => channel/channel.py.old} | 441 ++-- discord/channel/dm.py | 106 + discord/channel/forum.py | 210 ++ discord/channel/media.py | 227 ++ discord/channel/news.py | 283 +++ discord/channel/partial.py | 104 + discord/channel/stage.py | 345 +++ discord/channel/text.py | 327 +++ discord/{threads.py => channel/thread.py} | 226 +- discord/channel/voice.py | 328 +++ discord/client.py | 412 +--- discord/commands/core.py | 57 +- discord/commands/options.py | 656 +++--- discord/enums.py | 63 - discord/errors.py | 5 + discord/events/__init__.py | 344 +++ discord/events/audit_log.py | 20 +- discord/events/automod.py | 57 +- discord/events/channel.py | 180 +- discord/events/entitlement.py | 40 +- discord/events/gateway.py | 159 +- discord/events/guild.py | 346 ++- discord/events/integration.py | 50 +- discord/events/interaction.py | 77 +- discord/events/invite.py | 41 +- discord/events/message.py | 195 +- discord/events/scheduled_event.py | 73 +- discord/events/soundboard.py | 176 ++ discord/events/stage_instance.py | 37 +- discord/events/subscription.py | 30 +- discord/events/thread.py | 134 +- discord/events/typing.py | 31 +- discord/events/voice.py | 160 +- discord/events/webhook.py | 19 +- discord/ext/bridge/core.py | 6 +- discord/ext/commands/cog.py | 2 +- discord/ext/commands/context.py | 4 +- discord/ext/commands/converter.py | 10 +- discord/ext/commands/cooldowns.py | 3 +- discord/ext/commands/errors.py | 4 +- discord/ext/commands/help.py | 6 +- discord/flags.py | 4 +- discord/gateway.py | 50 - discord/gears/__init__.py | 27 + discord/gears/gear.py | 289 +++ discord/guild.py | 22 +- discord/interactions.py | 115 +- discord/invite.py | 4 +- discord/iterators.py | 28 +- discord/member.py | 4 +- discord/message.py | 22 +- discord/onboarding.py | 2 +- discord/raw_models.py | 2 +- discord/role.py | 2 +- discord/stage_instance.py | 2 +- discord/types/channel.py | 16 +- discord/types/interactions.py | 78 +- discord/ui/container.py | 2 +- discord/ui/section.py | 2 +- discord/ui/select.py | 4 +- discord/ui/view.py | 18 +- discord/user.py | 4 +- discord/utils/annotations.py | 231 ++ discord/utils/hybridmethod.py | 49 + discord/utils/private.py | 33 +- discord/utils/public.py | 2 +- discord/webhook/async_.py | 4 +- discord/webhook/sync.py | 6 +- discord/welcome_screen.py | 2 +- discord/widget.py | 2 +- docs/api/abcs.rst | 5 - docs/api/application_commands.rst | 5 - docs/api/clients.rst | 14 +- docs/api/events.rst | 1674 +++----------- docs/api/gears.rst | 168 ++ docs/api/index.rst | 1 + docs/api/models.rst | 18 +- docs/api/utils.rst | 3 - docs/ext/commands/api.rst | 7 +- pyproject.toml | 6 +- tests/event_helpers.py | 107 + tests/events/__init__.py | 1 + tests/events/test_events_channel.py | 172 ++ tests/events/test_events_guild.py | 408 ++++ tests/events/test_events_soundboard.py | 226 ++ tests/events/test_events_thread.py | 156 ++ tests/fixtures.py | 408 ++++ tests/integration/__init__.py | 1 + tests/integration/test_event_listeners.py | 327 +++ 101 files changed, 10459 insertions(+), 3920 deletions(-) create mode 100644 discord/channel/__init__.py create mode 100644 discord/channel/base.py create mode 100644 discord/channel/category.py rename discord/{channel.py => channel/channel.py.old} (93%) create mode 100644 discord/channel/dm.py create mode 100644 discord/channel/forum.py create mode 100644 discord/channel/media.py create mode 100644 discord/channel/news.py create mode 100644 discord/channel/partial.py create mode 100644 discord/channel/stage.py create mode 100644 discord/channel/text.py rename discord/{threads.py => channel/thread.py} (84%) create mode 100644 discord/channel/voice.py create mode 100644 discord/events/__init__.py create mode 100644 discord/events/soundboard.py create mode 100644 discord/gears/__init__.py create mode 100644 discord/gears/gear.py create mode 100644 discord/utils/annotations.py create mode 100644 discord/utils/hybridmethod.py create mode 100644 docs/api/gears.rst create mode 100644 tests/event_helpers.py create mode 100644 tests/events/__init__.py create mode 100644 tests/events/test_events_channel.py create mode 100644 tests/events/test_events_guild.py create mode 100644 tests/events/test_events_soundboard.py create mode 100644 tests/events/test_events_thread.py create mode 100644 tests/fixtures.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/test_event_listeners.py diff --git a/discord/__init__.py b/discord/__init__.py index bcffff183b..3b2440411b 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -33,6 +33,7 @@ from .automod import * from .bot import * from .channel import * +from .channel.thread import * from .client import * from .cog import * from .collectibles import * @@ -72,7 +73,6 @@ from .sticker import * from .team import * from .template import * -from .threads import * from .user import * from .voice_client import * from .webhook import * diff --git a/discord/abc.py b/discord/abc.py index 546deea7fd..0dbccca5e6 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -26,15 +26,14 @@ from __future__ import annotations import asyncio -import copy import time from typing import ( TYPE_CHECKING, - Any, Callable, Iterable, Protocol, Sequence, + TypeAlias, TypeVar, Union, overload, @@ -43,17 +42,11 @@ from . import utils from .context_managers import Typing -from .enums import ChannelType from .errors import ClientException, InvalidArgument from .file import File, VoiceMessage -from .flags import ChannelFlags, MessageFlags -from .invite import Invite +from .flags import MessageFlags from .iterators import HistoryIterator, MessagePinIterator from .mentions import AllowedMentions -from .partial_emoji import PartialEmoji, _EmojiTag -from .permissions import PermissionOverwrite, Permissions -from .role import Role -from .scheduled_events import ScheduledEvent from .sticker import GuildSticker, StickerItem from .utils.private import warn_deprecated from .voice_client import VoiceClient, VoiceProtocol @@ -62,7 +55,6 @@ "Snowflake", "User", "PrivateChannel", - "GuildChannel", "Messageable", "Connectable", "Mentionable", @@ -76,7 +68,6 @@ from .app.state import ConnectionState from .asset import Asset from .channel import ( - CategoryChannel, DMChannel, GroupChannel, PartialMessageable, @@ -84,16 +75,11 @@ TextChannel, VoiceChannel, ) + from .channel.thread import Thread from .client import Client from .embeds import Embed - from .enums import InviteTarget - from .guild import Guild - from .member import Member from .message import Message, MessageReference, PartialMessage from .poll import Poll - from .threads import Thread - from .types.channel import Channel as ChannelPayload - from .types.channel import GuildChannel as GuildChannelPayload from .types.channel import OverwriteType from .types.channel import PermissionOverwrite as PermissionOverwritePayload from .ui.view import View @@ -101,7 +87,7 @@ PartialMessageableChannel = TextChannel | VoiceChannel | StageChannel | Thread | DMChannel | PartialMessageable MessageableChannel = PartialMessageableChannel | GroupChannel - SnowflakeTime = "Snowflake" | datetime +SnowflakeTime: TypeAlias = "Snowflake | datetime" MISSING = utils.MISSING @@ -295,967 +281,6 @@ def is_member(self) -> bool: return self.type == self.MEMBER -GCH = TypeVar("GCH", bound="GuildChannel") - - -class GuildChannel: - """An ABC that details the common operations on a Discord guild channel. - - The following implement this ABC: - - - :class:`~discord.TextChannel` - - :class:`~discord.VoiceChannel` - - :class:`~discord.CategoryChannel` - - :class:`~discord.StageChannel` - - :class:`~discord.ForumChannel` - - This ABC must also implement :class:`~discord.abc.Snowflake`. - - Attributes - ---------- - name: :class:`str` - The channel name. - guild: :class:`~discord.Guild` - The guild the channel belongs to. - 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. - """ - - __slots__ = () - - id: int - name: str - guild: Guild - type: ChannelType - position: int - category_id: int | None - flags: ChannelFlags - _state: ConnectionState - _overwrites: list[_Overwrites] - - if TYPE_CHECKING: - - def __init__(self, *, state: ConnectionState, guild: Guild, data: dict[str, Any]): ... - - def __str__(self) -> str: - return self.name - - @property - def _sorting_bucket(self) -> int: - raise NotImplementedError - - async def _update(self, data: dict[str, Any]) -> None: - raise NotImplementedError - - async def _move( - self, - position: int, - parent_id: Any | None = None, - lock_permissions: bool = False, - *, - reason: str | None, - ) -> None: - if position < 0: - raise InvalidArgument("Channel position cannot be less than 0.") - - http = self._state.http - bucket = self._sorting_bucket - channels: list[GuildChannel] = [c for c in self.guild.channels if c._sorting_bucket == bucket] - - channels.sort(key=lambda c: c.position) - - try: - # remove ourselves from the channel list - channels.remove(self) - except ValueError: - # not there somehow lol - return - else: - index = next( - (i for i, c in enumerate(channels) if c.position >= position), - len(channels), - ) - # add ourselves at our designated position - channels.insert(index, self) - - payload = [] - for index, c in enumerate(channels): - d: dict[str, Any] = {"id": c.id, "position": index} - if parent_id is not MISSING and c.id == self.id: - d.update(parent_id=parent_id, lock_permissions=lock_permissions) - payload.append(d) - - await http.bulk_channel_update(self.guild.id, payload, reason=reason) - - async def _edit(self, options: dict[str, Any], reason: str | None) -> ChannelPayload | None: - try: - parent = options.pop("category") - except KeyError: - parent_id = MISSING - else: - parent_id = parent and parent.id - - try: - options["rate_limit_per_user"] = options.pop("slowmode_delay") - except KeyError: - pass - - try: - options["default_thread_rate_limit_per_user"] = options.pop("default_thread_slowmode_delay") - except KeyError: - pass - - try: - options["flags"] = options.pop("flags").value - except KeyError: - pass - - try: - options["available_tags"] = [tag.to_dict() for tag in options.pop("available_tags")] - except KeyError: - pass - - try: - rtc_region = options.pop("rtc_region") - except KeyError: - pass - else: - options["rtc_region"] = None if rtc_region is None else str(rtc_region) - - try: - video_quality_mode = options.pop("video_quality_mode") - except KeyError: - pass - else: - options["video_quality_mode"] = int(video_quality_mode) - - lock_permissions = options.pop("sync_permissions", False) - - try: - position = options.pop("position") - except KeyError: - if parent_id is not MISSING: - if lock_permissions: - category = self.guild.get_channel(parent_id) - if category: - options["permission_overwrites"] = [c._asdict() for c in category._overwrites] - options["parent_id"] = parent_id - elif lock_permissions and self.category_id is not None: - # if we're syncing permissions on a pre-existing channel category without changing it - # we need to update the permissions to point to the pre-existing category - category = self.guild.get_channel(self.category_id) - if category: - options["permission_overwrites"] = [c._asdict() for c in category._overwrites] - else: - await self._move( - position, - parent_id=parent_id, - lock_permissions=lock_permissions, - reason=reason, - ) - - overwrites = options.get("overwrites") - if overwrites is not None: - perms = [] - for target, perm in overwrites.items(): - if not isinstance(perm, PermissionOverwrite): - raise InvalidArgument(f"Expected PermissionOverwrite received {perm.__class__.__name__}") - - allow, deny = perm.pair() - payload = { - "allow": allow.value, - "deny": deny.value, - "id": target.id, - "type": (_Overwrites.ROLE if isinstance(target, Role) else _Overwrites.MEMBER), - } - - perms.append(payload) - options["permission_overwrites"] = perms - - try: - ch_type = options["type"] - except KeyError: - pass - else: - if not isinstance(ch_type, ChannelType): - raise InvalidArgument("type field must be of type ChannelType") - options["type"] = ch_type.value - - try: - default_reaction_emoji = options["default_reaction_emoji"] - except KeyError: - pass - else: - if isinstance(default_reaction_emoji, _EmojiTag): # GuildEmoji, PartialEmoji - default_reaction_emoji = default_reaction_emoji._to_partial() - elif isinstance(default_reaction_emoji, int): - default_reaction_emoji = PartialEmoji(name=None, id=default_reaction_emoji) - elif isinstance(default_reaction_emoji, str): - default_reaction_emoji = PartialEmoji.from_str(default_reaction_emoji) - elif default_reaction_emoji is None: - pass - else: - raise InvalidArgument("default_reaction_emoji must be of type: GuildEmoji | int | str | None") - - options["default_reaction_emoji"] = ( - default_reaction_emoji._to_forum_reaction_payload() if default_reaction_emoji else None - ) - - if options: - return await self._state.http.edit_channel(self.id, reason=reason, **options) - - def _fill_overwrites(self, data: GuildChannelPayload) -> None: - self._overwrites = [] - everyone_index = 0 - everyone_id = self.guild.id - - for index, overridden in enumerate(data.get("permission_overwrites", [])): - overwrite = _Overwrites(overridden) - self._overwrites.append(overwrite) - - if overwrite.type == _Overwrites.MEMBER: - continue - - if overwrite.id == everyone_id: - # the @everyone role is not guaranteed to be the first one - # in the list of permission overwrites, however the permission - # resolution code kind of requires that it is the first one in - # the list since it is special. So we need the index so we can - # swap it to be the first one. - everyone_index = index - - # do the swap - tmp = self._overwrites - if tmp: - tmp[everyone_index], tmp[0] = tmp[0], tmp[everyone_index] - - @property - def changed_roles(self) -> list[Role]: - """Returns a list of roles that have been overridden from - their default values in the :attr:`~discord.Guild.roles` attribute. - """ - ret = [] - g = self.guild - for overwrite in filter(lambda o: o.is_role(), self._overwrites): - role = g.get_role(overwrite.id) - if role is None: - continue - - role = copy.copy(role) - role.permissions.handle_overwrite(overwrite.allow, overwrite.deny) - ret.append(role) - return ret - - @property - def mention(self) -> str: - """The string that allows you to mention the channel.""" - return f"<#{self.id}>" - - @property - def jump_url(self) -> str: - """Returns a URL that allows the client to jump to the channel. - - .. versionadded:: 2.0 - """ - return f"https://discord.com/channels/{self.guild.id}/{self.id}" - - @property - def created_at(self) -> datetime: - """Returns the channel's creation time in UTC.""" - return utils.snowflake_time(self.id) - - def overwrites_for(self, obj: Role | User) -> PermissionOverwrite: - """Returns the channel-specific overwrites for a member or a role. - - Parameters - ---------- - obj: Union[:class:`~discord.Role`, :class:`~discord.abc.User`] - The role or user denoting - whose overwrite to get. - - Returns - ------- - :class:`~discord.PermissionOverwrite` - The permission overwrites for this object. - """ - - if isinstance(obj, User): - predicate = lambda p: p.is_member() - elif isinstance(obj, Role): - predicate = lambda p: p.is_role() - else: - predicate = lambda p: True - - for overwrite in filter(predicate, self._overwrites): - if overwrite.id == obj.id: - allow = Permissions(overwrite.allow) - deny = Permissions(overwrite.deny) - return PermissionOverwrite.from_pair(allow, deny) - - return PermissionOverwrite() - - async def get_overwrites(self) -> dict[Role | Member, PermissionOverwrite]: - """Returns all of the channel's overwrites. - - This is returned as a dictionary where the key contains the target which - can be either a :class:`~discord.Role` or a :class:`~discord.Member` and the value is the - overwrite as a :class:`~discord.PermissionOverwrite`. - - Returns - ------- - Dict[Union[:class:`~discord.Role`, :class:`~discord.Member`], :class:`~discord.PermissionOverwrite`] - The channel's permission overwrites. - """ - ret = {} - for ow in self._overwrites: - allow = Permissions(ow.allow) - deny = Permissions(ow.deny) - overwrite = PermissionOverwrite.from_pair(allow, deny) - target = None - - if ow.is_role(): - target = self.guild.get_role(ow.id) - elif ow.is_member(): - target = await self.guild.get_member(ow.id) - - # TODO: There is potential data loss here in the non-chunked - # case, i.e. target is None because get_member returned nothing. - # This can be fixed with a slight breaking change to the return type, - # i.e. adding discord.Object to the list of it - # However, for now this is an acceptable compromise. - if target is not None: - ret[target] = overwrite - return ret - - @property - def category(self) -> CategoryChannel | None: - """The category this channel belongs to. - - If there is no category then this is ``None``. - """ - return self.guild.get_channel(self.category_id) # type: ignore - - @property - def permissions_synced(self) -> bool: - """Whether the permissions for this channel are synced with the - category it belongs to. - - If there is no category then this is ``False``. - - .. versionadded:: 1.3 - """ - if self.category_id is None: - return False - - category = self.guild.get_channel(self.category_id) - return bool(category and category.overwrites == self.overwrites) - - def permissions_for(self, obj: Member | Role, /) -> Permissions: - """Handles permission resolution for the :class:`~discord.Member` - or :class:`~discord.Role`. - - This function takes into consideration the following cases: - - - Guild owner - - Guild roles - - Channel overrides - - Member overrides - - If a :class:`~discord.Role` is passed, then it checks the permissions - someone with that role would have, which is essentially: - - - The default role permissions - - The permissions of the role used as a parameter - - The default role permission overwrites - - The permission overwrites of the role used as a parameter - - .. versionchanged:: 2.0 - The object passed in can now be a role object. - - Parameters - ---------- - obj: Union[:class:`~discord.Member`, :class:`~discord.Role`] - The object to resolve permissions for. This could be either - a member or a role. If it's a role then member overwrites - are not computed. - - Returns - ------- - :class:`~discord.Permissions` - The resolved permissions for the member or role. - """ - - # The current cases can be explained as: - # Guild owner get all permissions -- no questions asked. Otherwise... - # The @everyone role gets the first application. - # After that, the applied roles that the user has in the channel - # (or otherwise) are then OR'd together. - # After the role permissions are resolved, the member permissions - # have to take into effect. - # After all that is done, you have to do the following: - - # If manage permissions is True, then all permissions are set to True. - - # The operation first takes into consideration the denied - # and then the allowed. - - if self.guild.owner_id == obj.id: - return Permissions.all() - - default = self.guild.default_role - base = Permissions(default.permissions.value if default else 0) - - # Handle the role case first - if isinstance(obj, Role): - base.value |= obj._permissions - - if base.administrator: - return Permissions.all() - - # Apply @everyone allow/deny first since it's special - try: - maybe_everyone = self._overwrites[0] - if maybe_everyone.id == self.guild.id: - base.handle_overwrite(allow=maybe_everyone.allow, deny=maybe_everyone.deny) - except IndexError: - pass - - if obj.is_default(): - return base - - overwrite = utils.find(lambda o: o.type == _Overwrites.ROLE and o.id == obj.id, self._overwrites) - if overwrite is not None: - base.handle_overwrite(overwrite.allow, overwrite.deny) - - return base - - roles = obj._roles - get_role = self.guild.get_role - - # Apply guild roles that the member has. - for role_id in roles: - role = get_role(role_id) - if role is not None: - base.value |= role._permissions - - # Guild-wide Administrator -> True for everything - # Bypass all channel-specific overrides - if base.administrator: - return Permissions.all() - - # Apply @everyone allow/deny first since it's special - try: - maybe_everyone = self._overwrites[0] - if maybe_everyone.id == self.guild.id: - base.handle_overwrite(allow=maybe_everyone.allow, deny=maybe_everyone.deny) - remaining_overwrites = self._overwrites[1:] - else: - remaining_overwrites = self._overwrites - except IndexError: - remaining_overwrites = self._overwrites - - denies = 0 - allows = 0 - - # Apply channel specific role permission overwrites - for overwrite in remaining_overwrites: - if overwrite.is_role() and roles.has(overwrite.id): - denies |= overwrite.deny - allows |= overwrite.allow - - base.handle_overwrite(allow=allows, deny=denies) - - # Apply member specific permission overwrites - for overwrite in remaining_overwrites: - if overwrite.is_member() and overwrite.id == obj.id: - base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny) - break - - # if you can't send a message in a channel then you can't have certain - # permissions as well - if not base.send_messages: - base.send_tts_messages = False - base.mention_everyone = False - base.embed_links = False - base.attach_files = False - - # if you can't read a channel then you have no permissions there - if not base.read_messages: - denied = Permissions.all_channel() - base.value &= ~denied.value - - return base - - async def delete(self, *, reason: str | None = None) -> None: - """|coro| - - Deletes the channel. - - You must have :attr:`~discord.Permissions.manage_channels` permission to use this. - - Parameters - ---------- - reason: Optional[:class:`str`] - The reason for deleting this channel. - Shows up on the audit log. - - Raises - ------ - ~discord.Forbidden - You do not have proper permissions to delete the channel. - ~discord.NotFound - The channel was not found or was already deleted. - ~discord.HTTPException - Deleting the channel failed. - """ - await self._state.http.delete_channel(self.id, reason=reason) - - @overload - async def set_permissions( - self, - target: Member | Role, - *, - overwrite: PermissionOverwrite | None = ..., - reason: str | None = ..., - ) -> None: ... - - @overload - async def set_permissions( - self, - target: Member | Role, - *, - reason: str | None = ..., - **permissions: bool, - ) -> None: ... - - async def set_permissions(self, target, *, overwrite=MISSING, reason=None, **permissions): - r"""|coro| - - Sets the channel specific permission overwrites for a target in the - channel. - - The ``target`` parameter should either be a :class:`~discord.Member` or a - :class:`~discord.Role` that belongs to guild. - - The ``overwrite`` parameter, if given, must either be ``None`` or - :class:`~discord.PermissionOverwrite`. For convenience, you can pass in - keyword arguments denoting :class:`~discord.Permissions` attributes. If this is - done, then you cannot mix the keyword arguments with the ``overwrite`` - parameter. - - If the ``overwrite`` parameter is ``None``, then the permission - overwrites are deleted. - - You must have the :attr:`~discord.Permissions.manage_roles` permission to use this. - - .. note:: - - This method *replaces* the old overwrites with the ones given. - - Examples - ---------- - - Setting allow and deny: :: - - await message.channel.set_permissions(message.author, read_messages=True, send_messages=False) - - Deleting overwrites :: - - await channel.set_permissions(member, overwrite=None) - - Using :class:`~discord.PermissionOverwrite` :: - - overwrite = discord.PermissionOverwrite() - overwrite.send_messages = False - overwrite.read_messages = True - await channel.set_permissions(member, overwrite=overwrite) - - Parameters - ----------- - target: Union[:class:`~discord.Member`, :class:`~discord.Role`] - The member or role to overwrite permissions for. - overwrite: Optional[:class:`~discord.PermissionOverwrite`] - The permissions to allow and deny to the target, or ``None`` to - delete the overwrite. - \*\*permissions - A keyword argument list of permissions to set for ease of use. - Cannot be mixed with ``overwrite``. - reason: Optional[:class:`str`] - The reason for doing this action. Shows up on the audit log. - - Raises - ------- - ~discord.Forbidden - You do not have permissions to edit channel specific permissions. - ~discord.HTTPException - Editing channel specific permissions failed. - ~discord.NotFound - The role or member being edited is not part of the guild. - ~discord.InvalidArgument - The overwrite parameter invalid or the target type was not - :class:`~discord.Role` or :class:`~discord.Member`. - """ - - http = self._state.http - - if isinstance(target, User): - perm_type = _Overwrites.MEMBER - elif isinstance(target, Role): - perm_type = _Overwrites.ROLE - else: - raise InvalidArgument("target parameter must be either Member or Role") - - if overwrite is MISSING: - if len(permissions) == 0: - raise InvalidArgument("No overwrite provided.") - try: - overwrite = PermissionOverwrite(**permissions) - except (ValueError, TypeError) as e: - raise InvalidArgument("Invalid permissions given to keyword arguments.") from e - elif len(permissions) > 0: - raise InvalidArgument("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): - (allow, deny) = overwrite.pair() - await http.edit_channel_permissions(self.id, target.id, allow.value, deny.value, perm_type, reason=reason) - else: - raise InvalidArgument("Invalid overwrite type provided.") - - async def _clone_impl( - self: GCH, - base_attrs: dict[str, Any], - *, - name: str | None = None, - reason: str | None = None, - ) -> GCH: - 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 - guild_id = self.guild.id - cls = self.__class__ - data = await self._state.http.create_channel(guild_id, self.type.value, reason=reason, **base_attrs) - obj = cls(state=self._state, guild=self.guild, data=data) - - # temporarily add it to the cache - self.guild._channels[obj.id] = obj # type: ignore - return obj - - async def clone(self: GCH, *, name: str | None = None, reason: str | None = None) -> GCH: - """|coro| - - Clones this channel. This creates a channel with the same properties - as this channel. - - You must have the :attr:`~discord.Permissions.manage_channels` permission to - do this. - - .. versionadded:: 1.1 - - Parameters - ---------- - name: Optional[:class:`str`] - The name of the new channel. If not provided, defaults to this - channel name. - reason: Optional[:class:`str`] - The reason for cloning this channel. Shows up on the audit log. - - Returns - ------- - :class:`.abc.GuildChannel` - The channel that was created. - - Raises - ------ - ~discord.Forbidden - You do not have the proper permissions to create this channel. - ~discord.HTTPException - Creating the channel failed. - """ - raise NotImplementedError - - @overload - async def move( - self, - *, - beginning: bool, - offset: int | utils.Undefined = MISSING, - category: Snowflake | None | utils.Undefined = MISSING, - sync_permissions: bool | utils.Undefined = MISSING, - reason: str | None | utils.Undefined = MISSING, - ) -> None: ... - - @overload - async def move( - self, - *, - end: bool, - offset: int | utils.Undefined = MISSING, - category: Snowflake | None | utils.Undefined = MISSING, - sync_permissions: bool | utils.Undefined = MISSING, - reason: str | utils.Undefined = MISSING, - ) -> None: ... - - @overload - async def move( - self, - *, - before: Snowflake, - offset: int | utils.Undefined = MISSING, - category: Snowflake | None | utils.Undefined = MISSING, - sync_permissions: bool | utils.Undefined = MISSING, - reason: str | utils.Undefined = MISSING, - ) -> None: ... - - @overload - async def move( - self, - *, - after: Snowflake, - offset: int | utils.Undefined = MISSING, - category: Snowflake | None | utils.Undefined = MISSING, - sync_permissions: bool | utils.Undefined = MISSING, - reason: str | utils.Undefined = MISSING, - ) -> None: ... - - async def move(self, **kwargs) -> None: - """|coro| - - A rich interface to help move a channel relative to other channels. - - If exact position movement is required, ``edit`` should be used instead. - - You must have the :attr:`~discord.Permissions.manage_channels` permission to - do this. - - .. note:: - - Voice channels will always be sorted below text channels. - This is a Discord limitation. - - .. versionadded:: 1.7 - - Parameters - ---------- - beginning: :class:`bool` - Whether to move the channel to the beginning of the - channel list (or category if given). - This is mutually exclusive with ``end``, ``before``, and ``after``. - end: :class:`bool` - Whether to move the channel to the end of the - 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. - This is mutually exclusive with ``beginning``, ``end``, and ``after``. - after: :class:`~discord.abc.Snowflake` - The channel that should be after our current channel. - This is mutually exclusive with ``beginning``, ``end``, and ``before``. - offset: :class:`int` - The number of channels to offset the move by. For example, - an offset of ``2`` with ``beginning=True`` would move - it 2 after the beginning. A positive number moves it below - while a negative number moves it above. Note that this - number is relative and computed after the ``beginning``, - ``end``, ``before``, and ``after`` parameters. - category: Optional[:class:`~discord.abc.Snowflake`] - The category to move this channel under. - If ``None`` is given then it moves it out of the category. - This parameter is ignored if moving a category channel. - sync_permissions: :class:`bool` - Whether to sync the permissions with the category (if given). - reason: :class:`str` - The reason for the move. - - Raises - ------ - InvalidArgument - An invalid position was given or a bad mix of arguments was passed. - Forbidden - You do not have permissions to move the channel. - HTTPException - Moving the channel failed. - """ - - if not kwargs: - return - - beginning, end = kwargs.get("beginning"), kwargs.get("end") - before, after = kwargs.get("before"), kwargs.get("after") - offset = kwargs.get("offset", 0) - if sum(bool(a) for a in (beginning, end, before, after)) > 1: - raise InvalidArgument("Only one of [before, after, end, beginning] can be used.") - - bucket = self._sorting_bucket - parent_id = kwargs.get("category", MISSING) - channels: list[GuildChannel] - if parent_id not in (MISSING, None): - parent_id = parent_id.id - channels = [ - ch for ch in self.guild.channels if ch._sorting_bucket == bucket and ch.category_id == parent_id - ] - else: - channels = [ - ch for ch in self.guild.channels if ch._sorting_bucket == bucket and ch.category_id == self.category_id - ] - - channels.sort(key=lambda c: (c.position, c.id)) - - try: - # Try to remove ourselves from the channel list - channels.remove(self) - except ValueError: - # If we're not there then it's probably due to not being in the category - pass - - index = None - if beginning: - index = 0 - elif end: - index = len(channels) - elif before: - index = next((i for i, c in enumerate(channels) if c.id == before.id), None) - elif after: - index = next((i + 1 for i, c in enumerate(channels) if c.id == after.id), None) - - if index is None: - raise InvalidArgument("Could not resolve appropriate move position") - - channels.insert(max((index + offset), 0), self) - payload = [] - lock_permissions = kwargs.get("sync_permissions", False) - reason = kwargs.get("reason") - for index, channel in enumerate(channels): - d = {"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) - - await self._state.http.bulk_channel_update(self.guild.id, payload, reason=reason) - - async def create_invite( - self, - *, - reason: str | None = None, - max_age: int = 0, - max_uses: int = 0, - temporary: bool = False, - unique: bool = True, - target_event: ScheduledEvent | None = None, - target_type: InviteTarget | None = None, - target_user: User | None = None, - target_application_id: int | None = None, - ) -> Invite: - """|coro| - - Creates an instant invite from a text or voice channel. - - You must have the :attr:`~discord.Permissions.create_instant_invite` permission to - do this. - - Parameters - ---------- - max_age: :class:`int` - How long the invite should last in seconds. If it's 0 then the invite - doesn't expire. Defaults to ``0``. - max_uses: :class:`int` - How many uses the invite could be used for. If it's 0 then there - are unlimited uses. Defaults to ``0``. - temporary: :class:`bool` - Denotes that the invite grants temporary membership - (i.e. they get kicked after they disconnect). Defaults to ``False``. - unique: :class:`bool` - Indicates if a unique invite URL should be created. Defaults to True. - If this is set to ``False`` then it will return a previously created - invite. - reason: Optional[:class:`str`] - The reason for creating this invite. Shows up on the audit log. - target_type: Optional[:class:`.InviteTarget`] - The type of target for the voice channel invite, if any. - - .. versionadded:: 2.0 - - target_user: Optional[:class:`User`] - The user whose stream to display for this invite, required if `target_type` is `TargetType.stream`. - The user must be streaming in the channel. - - .. versionadded:: 2.0 - - target_application_id: Optional[:class:`int`] - The id of the embedded application for the invite, required if `target_type` is - `TargetType.embedded_application`. - - .. versionadded:: 2.0 - - target_event: Optional[:class:`.ScheduledEvent`] - The scheduled event object to link to the event. - Shortcut to :meth:`.Invite.set_scheduled_event` - - See :meth:`.Invite.set_scheduled_event` for more - info on event invite linking. - - .. versionadded:: 2.0 - - Returns - ------- - :class:`~discord.Invite` - The invite that was created. - - Raises - ------ - ~discord.HTTPException - Invite creation failed. - - ~discord.NotFound - The channel that was passed is a category or an invalid channel. - """ - - data = await self._state.http.create_invite( - self.id, - reason=reason, - max_age=max_age, - max_uses=max_uses, - temporary=temporary, - unique=unique, - 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, - ) - invite = await Invite.from_incomplete(data=data, state=self._state) - if target_event: - invite.set_scheduled_event(target_event) - return invite - - async def invites(self) -> list[Invite]: - """|coro| - - Returns a list of all active instant invites from this channel. - - You must have :attr:`~discord.Permissions.manage_channels` to get this information. - - Returns - ------- - List[:class:`~discord.Invite`] - The list of invites that are currently active. - - Raises - ------ - ~discord.Forbidden - You do not have proper permissions to get the information. - ~discord.HTTPException - An error occurred while fetching the information. - """ - - state = self._state - data = await state.http.invites_from_channel(self.id) - guild = self.guild - return [Invite(state=state, data=invite, channel=self, guild=guild) for invite in data] - - class Messageable: """An ABC that details the common operations on a model that can send messages. @@ -1526,7 +551,7 @@ async def send( if reference is not None: try: _reference = reference.to_message_reference_dict() - from .message import MessageReference # noqa: PLC0415 + from .message import MessageReference if not isinstance(reference, MessageReference): warn_deprecated( @@ -1960,6 +985,10 @@ async def connect( return voice -class Mentionable: - # TODO: documentation, methods if needed - pass +@runtime_checkable +class Mentionable(Protocol): + """An ABC that details the common operations on an object that can + be mentioned. + """ + + def mention(self) -> str: ... diff --git a/discord/app/cache.py b/discord/app/cache.py index 545e1198ee..7f97b5f837 100644 --- a/discord/app/cache.py +++ b/discord/app/cache.py @@ -28,6 +28,7 @@ from discord import utils from discord.member import Member from discord.message import Message +from discord.soundboard import SoundboardSound from ..channel import DMChannel from ..emoji import AppEmoji, GuildEmoji @@ -142,6 +143,8 @@ async def store_private_channel(self, channel: "PrivateChannel") -> None: ... async def store_message(self, message: MessagePayload, channel: "MessageableChannel") -> Message: ... + async def store_built_message(self, message: Message) -> None: ... + async def upsert_message(self, message: Message) -> None: ... async def delete_message(self, message_id: int) -> None: ... @@ -166,6 +169,14 @@ async def get_all_members(self) -> list[Member]: ... async def clear(self, views: bool = True) -> None: ... + async def store_sound(self, sound: SoundboardSound) -> None: ... + + async def get_sound(self, sound_id: int) -> SoundboardSound | None: ... + + async def get_all_sounds(self) -> list[SoundboardSound]: ... + + async def delete_sound(self, sound_id: int) -> None: ... + class MemoryCache(Cache): def __init__(self, max_messages: int | None = None) -> None: @@ -177,6 +188,7 @@ def __init__(self, max_messages: int | None = None) -> None: self._stickers: dict[int, list[GuildSticker]] = {} self._views: dict[str, View] = {} self._modals: dict[str, Modal] = {} + self._sounds: dict[int, SoundboardSound] = {} self._messages: Deque[Message] = deque(maxlen=self.max_messages) self._emojis: dict[int, list[GuildEmoji | AppEmoji]] = {} @@ -362,9 +374,15 @@ async def upsert_message(self, message: Message) -> None: async def store_message(self, message: MessagePayload, channel: "MessageableChannel") -> Message: msg = await Message._from_data(state=self._state, channel=channel, data=message) - self._messages.append(msg) + self.store_built_message(msg) return msg + async def store_built_message(self, message: Message) -> None: + self._messages.append(message) + + async def delete_message(self, message_id: int) -> None: + self._messages.remove(utils.find(lambda m: m.id == message_id, reversed(self._messages))) + async def get_message(self, message_id: int) -> Message | None: return utils.find(lambda m: m.id == message_id, reversed(self._messages)) @@ -393,3 +411,15 @@ async def get_guild_members(self, guild_id: int) -> list[Member]: async def get_all_members(self) -> list[Member]: return self._flatten([list(members.values()) for members in self._guild_members.values()]) + + async def store_sound(self, sound: SoundboardSound) -> None: + self._sounds[sound.id] = sound + + async def get_sound(self, sound_id: int) -> SoundboardSound | None: + return self._sounds.get(sound_id) + + async def get_all_sounds(self) -> list[SoundboardSound]: + return list(self._sounds.values()) + + async def delete_sound(self, sound_id: int) -> None: + self._sounds.pop(sound_id, None) diff --git a/discord/app/event_emitter.py b/discord/app/event_emitter.py index bdcfb25f43..5af1b6a34a 100644 --- a/discord/app/event_emitter.py +++ b/discord/app/event_emitter.py @@ -24,9 +24,9 @@ import asyncio from abc import ABC, abstractmethod -from asyncio import Future from collections import defaultdict -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from collections.abc import Awaitable, Coroutine +from typing import TYPE_CHECKING, Any, Callable, Protocol, TypeAlias, TypeVar from typing_extensions import Self @@ -43,60 +43,81 @@ class Event(ABC): @abstractmethod async def __load__(cls, data: Any, state: "ConnectionState") -> Self | None: ... + def _populate_from_slots(self, obj: Any) -> None: + """ + Populate this event instance with attributes from another object. + + Handles both __slots__ and __dict__ based objects. + + Parameters + ---------- + obj: Any + The object to copy attributes from. + """ + # Collect all slots from the object's class hierarchy + slots = set() + for klass in type(obj).__mro__: + if hasattr(klass, "__slots__"): + slots.update(klass.__slots__) + + # Copy slot attributes + for slot in slots: + if hasattr(obj, slot): + try: + setattr(self, slot, getattr(obj, slot)) + except AttributeError: + # Some slots might be read-only or not settable + pass + + # Also copy __dict__ if it exists + if hasattr(obj, "__dict__"): + for key, value in obj.__dict__.items(): + try: + setattr(self, key, value) + except AttributeError: + pass + + +ListenerCallback: TypeAlias = Callable[[Event], Any] + + +class EventReciever(Protocol): + def __call__(self, event: Event) -> Awaitable[Any]: ... + class EventEmitter: def __init__(self, state: "ConnectionState") -> None: - self._listeners: dict[type[Event], list[Callable]] = {} - self._events: dict[str, list[type[Event]]] - self._wait_fors: dict[type[Event], list[Future]] = defaultdict(list) - self._state = state + self._receivers: list[EventReciever] = [] + self._events: dict[str, list[type[Event]]] = defaultdict(list) + self._state: ConnectionState = state + + from ..events import ALL_EVENTS + + for event_cls in ALL_EVENTS: + self.add_event(event_cls) def add_event(self, event: type[Event]) -> None: - try: - self._events[event.__event_name__].append(event) - except KeyError: - self._events[event.__event_name__] = [event] + self._events[event.__event_name__].append(event) def remove_event(self, event: type[Event]) -> list[type[Event]] | None: return self._events.pop(event.__event_name__, None) - def add_listener(self, event: type[Event], listener: Callable) -> None: - try: - self._listeners[event].append(listener) - except KeyError: - self.add_event(event) - self._listeners[event] = [listener] - - def remove_listener(self, event: type[Event], listener: Callable) -> None: - self._listeners[event].remove(listener) - - def add_wait_for(self, event: type[T]) -> Future[T]: - fut = Future() + def add_receiver(self, receiver: EventReciever) -> None: + self._receivers.append(receiver) - self._wait_fors[event].append(fut) - - return fut - - def remove_wait_for(self, event: type[Event], fut: Future) -> None: - self._wait_fors[event].remove(fut) + def remove_receiver(self, receiver: EventReciever) -> None: + self._receivers.remove(receiver) async def emit(self, event_str: str, data: Any) -> None: events = self._events.get(event_str, []) - for event in events: - eve = await event.__load__(data=data, state=self._state) + coros: list[Awaitable[None]] = [] + for event_cls in events: + event = await event_cls.__load__(data=data, state=self._state) - if eve is None: + if event is None: continue - funcs = self._listeners.get(event, []) - - for func in funcs: - asyncio.create_task(func(eve)) - - wait_fors = self._wait_fors.get(event) + coros.extend(receiver(event) for receiver in self._receivers) - if wait_fors is not None: - for wait_for in wait_fors: - wait_for.set_result(eve) - self._wait_fors.pop(event) + await asyncio.gather(*coros) diff --git a/discord/app/state.py b/discord/app/state.py index b57ad5e776..cdea364746 100644 --- a/discord/app/state.py +++ b/discord/app/state.py @@ -44,11 +44,14 @@ cast, ) +from discord.soundboard import SoundboardSound + from .. import utils from ..activity import BaseActivity from ..automod import AutoModRule from ..channel import * from ..channel import _channel_factory +from ..channel.thread import Thread, ThreadMember from ..emoji import AppEmoji, GuildEmoji from ..enums import ChannelType, InteractionType, Status, try_enum from ..flags import ApplicationFlags, Intents, MemberCacheFlags @@ -65,7 +68,6 @@ from ..raw_models import * from ..role import Role from ..sticker import GuildSticker -from ..threads import Thread, ThreadMember from ..ui.modal import Modal from ..ui.view import View from ..user import ClientUser, User @@ -237,12 +239,12 @@ def __init__( self._voice_clients: dict[int, VoiceClient] = {} if not intents.members or cache_flags._empty: - self.store_user = self.create_user # type: ignore + self.store_user = self.create_user_async # type: ignore self.deref_user = self.deref_user_no_intents # type: ignore self.cache_app_emojis: bool = options.get("cache_app_emojis", False) - self.emitter = EventEmitter(self) + self.emitter: EventEmitter = EventEmitter(self) self.cache: Cache = cache self.cache._state = self @@ -320,6 +322,9 @@ async def deref_user(self, user_id: int) -> None: def create_user(self, data: UserPayload) -> User: return User(state=self, data=data) + async def create_user_async(self, data: UserPayload) -> User: + return User(state=self, data=data) + def deref_user_no_intents(self, user_id: int) -> None: return @@ -373,6 +378,21 @@ async def _remove_guild(self, guild: Guild) -> None: del guild + async def _add_default_sounds(self) -> None: + default_sounds = await self.http.get_default_sounds() + for default_sound in default_sounds: + sound = SoundboardSound(state=self, http=self.http, data=default_sound) + await self._add_sound(sound) + + async def _add_sound(self, sound: SoundboardSound) -> None: + await self.cache.store_sound(sound) + + async def _remove_sound(self, sound: SoundboardSound) -> None: + await self.cache.delete_sound(sound.id) + + async def get_sounds(self) -> list[SoundboardSound]: + return list(await self.cache.get_all_sounds()) + async def get_emojis(self) -> list[GuildEmoji | AppEmoji]: return await self.cache.get_all_emojis() @@ -431,7 +451,7 @@ async def _get_guild_channel( # guild_id is in data guild = await self._get_guild(int(guild_id or data["guild_id"])) # type: ignore except KeyError: - channel = DMChannel._from_message(self, channel_id) + channel = DMChannel(id=channel_id, state=self) guild = None else: channel = guild and guild._resolve_channel(channel_id) @@ -487,15 +507,15 @@ async def query_members( ) raise - def _get_create_guild(self, data): + async def _get_create_guild(self, data): if data.get("unavailable") is False: # GUILD_CREATE with unavailable in the response # usually means that the guild has become available # and is therefore in the cache - guild = self._get_guild(int(data["id"])) + guild = await self._get_guild(int(data["id"])) if guild is not None: guild.unavailable = False - guild._from_data(data) + await guild._from_data(data, self) return guild return self._add_guild_from_data(data) @@ -660,6 +680,7 @@ async def _delay_ready(self) -> None: future = asyncio.ensure_future(self.chunk_guild(guild)) current_bucket.append(future) else: + await self._add_default_sounds() future = self.loop.create_future() future.set_result([]) diff --git a/discord/appinfo.py b/discord/appinfo.py index db950f23e1..e6e328d6fb 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -183,7 +183,7 @@ class AppInfo: ) def __init__(self, state: ConnectionState, data: AppInfoPayload): - from .team import Team # noqa: PLC0415 + from .team import Team self._state: ConnectionState = state self.id: int = int(data["id"]) diff --git a/discord/asset.py b/discord/asset.py index afdd47f3aa..684dcd1dd6 100644 --- a/discord/asset.py +++ b/discord/asset.py @@ -34,12 +34,15 @@ from . import utils from .errors import DiscordException, InvalidArgument +if TYPE_CHECKING: + from .app.state import ConnectionState + __all__ = ("Asset",) if TYPE_CHECKING: ValidStaticFormatTypes = Literal["webp", "jpeg", "jpg", "png"] ValidAssetFormatTypes = Literal["webp", "jpeg", "jpg", "png", "gif"] - from .state import ConnectionState + from .app.state import ConnectionState VALID_STATIC_FORMATS = frozenset({"jpeg", "jpg", "webp", "png"}) @@ -172,7 +175,7 @@ def __init__(self, state, *, url: str, key: str, animated: bool = False): self._key = key @classmethod - def _from_default_avatar(cls, state, index: int) -> Asset: + def _from_default_avatar(cls, state: ConnectionState, index: int) -> Asset: return cls( state, url=f"{cls.BASE}/embed/avatars/{index}.png", @@ -181,7 +184,7 @@ def _from_default_avatar(cls, state, index: int) -> Asset: ) @classmethod - def _from_avatar(cls, state, user_id: int, avatar: str) -> Asset: + def _from_avatar(cls, state: ConnectionState, user_id: int, avatar: str) -> Asset: animated = avatar.startswith("a_") format = "gif" if animated else "png" return cls( @@ -192,7 +195,7 @@ def _from_avatar(cls, state, user_id: int, avatar: str) -> Asset: ) @classmethod - def _from_avatar_decoration(cls, state, user_id: int, avatar_decoration: str) -> Asset: + def _from_avatar_decoration(cls, state: ConnectionState, user_id: int, avatar_decoration: str) -> Asset: animated = avatar_decoration.startswith("a_") endpoint = ( "avatar-decoration-presets" @@ -232,7 +235,7 @@ def _from_user_primary_guild_tag(cls, state: ConnectionState, identity_guild_id: ) @classmethod - def _from_guild_avatar(cls, state, guild_id: int, member_id: int, avatar: str) -> Asset: + def _from_guild_avatar(cls, state: ConnectionState, guild_id: int, member_id: int, avatar: str) -> Asset: animated = avatar.startswith("a_") format = "gif" if animated else "png" return cls( @@ -243,7 +246,7 @@ def _from_guild_avatar(cls, state, guild_id: int, member_id: int, avatar: str) - ) @classmethod - def _from_guild_banner(cls, state, guild_id: int, member_id: int, banner: str) -> Asset: + def _from_guild_banner(cls, state: ConnectionState, guild_id: int, member_id: int, banner: str) -> Asset: animated = banner.startswith("a_") format = "gif" if animated else "png" return cls( @@ -254,7 +257,7 @@ def _from_guild_banner(cls, state, guild_id: int, member_id: int, banner: str) - ) @classmethod - def _from_icon(cls, state, object_id: int, icon_hash: str, path: str) -> Asset: + def _from_icon(cls, state: ConnectionState, object_id: int, icon_hash: str, path: str) -> Asset: return cls( state, url=f"{cls.BASE}/{path}-icons/{object_id}/{icon_hash}.png?size=1024", @@ -263,7 +266,7 @@ def _from_icon(cls, state, object_id: int, icon_hash: str, path: str) -> Asset: ) @classmethod - def _from_cover_image(cls, state, object_id: int, cover_image_hash: str) -> Asset: + def _from_cover_image(cls, state: ConnectionState, object_id: int, cover_image_hash: str) -> Asset: return cls( state, url=f"{cls.BASE}/app-assets/{object_id}/store/{cover_image_hash}.png?size=1024", @@ -282,7 +285,7 @@ def _from_collectible(cls, state: ConnectionState, asset: str, animated: bool = ) @classmethod - def _from_guild_image(cls, state, guild_id: int, image: str, path: str) -> Asset: + def _from_guild_image(cls, state: ConnectionState, guild_id: int, image: str, path: str) -> Asset: animated = False format = "png" if path == "banners": @@ -297,7 +300,7 @@ def _from_guild_image(cls, state, guild_id: int, image: str, path: str) -> Asset ) @classmethod - def _from_guild_icon(cls, state, guild_id: int, icon_hash: str) -> Asset: + def _from_guild_icon(cls, state: ConnectionState, guild_id: int, icon_hash: str) -> Asset: animated = icon_hash.startswith("a_") format = "gif" if animated else "png" return cls( @@ -308,7 +311,7 @@ def _from_guild_icon(cls, state, guild_id: int, icon_hash: str) -> Asset: ) @classmethod - def _from_sticker_banner(cls, state, banner: int) -> Asset: + def _from_sticker_banner(cls, state: ConnectionState, banner: int) -> Asset: return cls( state, url=f"{cls.BASE}/app-assets/710982414301790216/store/{banner}.png", @@ -317,7 +320,7 @@ def _from_sticker_banner(cls, state, banner: int) -> Asset: ) @classmethod - def _from_user_banner(cls, state, user_id: int, banner_hash: str) -> Asset: + def _from_user_banner(cls, state: ConnectionState, user_id: int, banner_hash: str) -> Asset: animated = banner_hash.startswith("a_") format = "gif" if animated else "png" return cls( @@ -328,7 +331,7 @@ def _from_user_banner(cls, state, user_id: int, banner_hash: str) -> Asset: ) @classmethod - def _from_scheduled_event_image(cls, state, event_id: int, cover_hash: str) -> Asset: + def _from_scheduled_event_image(cls, state: ConnectionState, event_id: int, cover_hash: str) -> Asset: return cls( state, url=f"{cls.BASE}/guild-events/{event_id}/{cover_hash}.png", @@ -337,7 +340,7 @@ def _from_scheduled_event_image(cls, state, event_id: int, cover_hash: str) -> A ) @classmethod - def _from_soundboard_sound(cls, state, sound_id: int) -> Asset: + def _from_soundboard_sound(cls, state: ConnectionState, sound_id: int) -> Asset: return cls( state, url=f"{cls.BASE}/soundboard-sounds/{sound_id}", diff --git a/discord/audit_logs.py b/discord/audit_logs.py index e5d0b7a268..a59efed1e9 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -48,8 +48,9 @@ if TYPE_CHECKING: - from . import abc from .app.state import ConnectionState + from .channel.base import GuildChannel + from .channel.thread import Thread from .emoji import GuildEmoji from .guild import Guild from .member import Member @@ -57,7 +58,6 @@ from .scheduled_events import ScheduledEvent from .stage_instance import StageInstance from .sticker import GuildSticker - from .threads import Thread from .types.audit_log import AuditLogChange as AuditLogChangePayload from .types.audit_log import AuditLogEntry as AuditLogEntryPayload from .types.automod import AutoModAction as AutoModActionPayload @@ -80,13 +80,13 @@ def _transform_snowflake(entry: AuditLogEntry, data: Snowflake) -> int: return int(data) -def _transform_channel(entry: AuditLogEntry, data: Snowflake | None) -> abc.GuildChannel | Object | None: +def _transform_channel(entry: AuditLogEntry, data: Snowflake | None) -> GuildChannel | Object | None: if data is None: return None return entry.guild.get_channel(int(data)) or Object(id=data) -def _transform_channels(entry: AuditLogEntry, data: list[Snowflake] | None) -> list[abc.GuildChannel | Object] | None: +def _transform_channels(entry: AuditLogEntry, data: list[Snowflake] | None) -> list[GuildChannel | Object] | None: if data is None: return None return [_transform_channel(entry, channel) for channel in data] @@ -351,7 +351,7 @@ async def _from_data( before = await self._maybe_await(transformer(entry, before)) if attr == "location" and hasattr(self.before, "location_type"): - from .scheduled_events import ScheduledEventLocation # noqa: PLC0415 + from .scheduled_events import ScheduledEventLocation if self.before.location_type is enums.ScheduledEventLocationType.external: before = ScheduledEventLocation(state=state, value=before) @@ -369,7 +369,7 @@ async def _from_data( after = await self._maybe_await(transformer(entry, after)) if attr == "location" and hasattr(self.after, "location_type"): - from .scheduled_events import ScheduledEventLocation # noqa: PLC0415 + from .scheduled_events import ScheduledEventLocation if self.after.location_type is enums.ScheduledEventLocationType.external: after = ScheduledEventLocation(state=state, value=after) @@ -438,7 +438,7 @@ class _AuditLogProxyMemberPrune: class _AuditLogProxyMemberMoveOrMessageDelete: - channel: abc.GuildChannel + channel: GuildChannel count: int @@ -447,12 +447,12 @@ class _AuditLogProxyMemberDisconnect: class _AuditLogProxyPinAction: - channel: abc.GuildChannel + channel: GuildChannel message_id: int class _AuditLogProxyStageInstanceAction: - channel: abc.GuildChannel + channel: GuildChannel class AuditLogEntry(Hashable): @@ -593,7 +593,7 @@ async def get_target( self, ) -> ( Guild - | abc.GuildChannel + | GuildChannel | Member | User | Role @@ -639,7 +639,7 @@ def after(self) -> AuditLogDiff: def _convert_target_guild(self, target_id: int) -> Guild: return self.guild - def _convert_target_channel(self, target_id: int) -> abc.GuildChannel | Object: + def _convert_target_channel(self, target_id: int) -> GuildChannel | Object: return self.guild.get_channel(target_id) or Object(id=target_id) async def _convert_target_user(self, target_id: int) -> Member | User | None: diff --git a/discord/bot.py b/discord/bot.py index 41b126674a..bc9c683a43 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -46,7 +46,6 @@ ) from .client import Client -from .cog import CogMixin from .commands import ( ApplicationCommand, ApplicationContext, @@ -59,6 +58,7 @@ ) from .enums import IntegrationType, InteractionContextType, InteractionType from .errors import CheckFailure, DiscordException +from .events import InteractionCreate from .interactions import Interaction from .shard import AutoShardedClient from .types import interactions @@ -1082,7 +1082,7 @@ async def invoke_application_command(self, ctx: ApplicationContext) -> None: ctx: :class:`.ApplicationCommand` The invocation context to invoke. """ - self._bot.dispatch("application_command", ctx) + # self._bot.dispatch("application_command", ctx) # TODO: Remove when moving away from ApplicationContext try: if await self._bot.can_run(ctx, call_once=True): await ctx.command.invoke(ctx) @@ -1091,14 +1091,15 @@ async def invoke_application_command(self, ctx: ApplicationContext) -> None: except DiscordException as exc: await ctx.command.dispatch_error(ctx, exc) else: - self._bot.dispatch("application_command_completion", ctx) + # self._bot.dispatch("application_command_completion", ctx) # TODO: Remove when moving away from ApplicationContext + pass @property @abstractmethod def _bot(self) -> Bot | AutoShardedBot: ... -class BotBase(ApplicationCommandMixin, CogMixin, ABC): +class BotBase(ApplicationCommandMixin, ABC): _supports_prefixed_commands = False def __init__(self, description=None, *args, **options): @@ -1152,11 +1153,13 @@ def __init__(self, description=None, *args, **options): self._before_invoke = None self._after_invoke = None + self._bot.add_listener(self.on_interaction, event=InteractionCreate) + async def on_connect(self): if self.auto_sync_commands: await self.sync_commands() - async def on_interaction(self, interaction): + async def on_interaction(self, interaction: InteractionCreate): await self.process_application_commands(interaction) async def on_application_command_error(self, context: ApplicationContext, exception: DiscordException) -> None: diff --git a/discord/channel/__init__.py b/discord/channel/__init__.py new file mode 100644 index 0000000000..9ef560c65c --- /dev/null +++ b/discord/channel/__init__.py @@ -0,0 +1,119 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 ..enums import ChannelType, try_enum +from .base import ( + BaseChannel, + GuildChannel, + GuildMessageableChannel, + GuildPostableChannel, + GuildThreadableChannel, + GuildTopLevelChannel, +) +from .category import CategoryChannel +from .dm import DMChannel +from .dm import GroupDMChannel as GroupChannel +from .forum import ForumChannel +from .media import MediaChannel +from .news import NewsChannel +from .partial import PartialMessageable +from .stage import StageChannel +from .text import TextChannel +from .thread import Thread +from .voice import VoiceChannel + +__all__ = ( + "BaseChannel", + "CategoryChannel", + "DMChannel", + "ForumChannel", + "GroupChannel", + "GuildChannel", + "GuildMessageableChannel", + "GuildPostableChannel", + "GuildThreadableChannel", + "GuildTopLevelChannel", + "MediaChannel", + "NewsChannel", + "PartialMessageable", + "StageChannel", + "TextChannel", + "Thread", + "VoiceChannel", +) + + +def _guild_channel_factory(channel_type: int): + value = try_enum(ChannelType, channel_type) + if value is ChannelType.text: + return TextChannel, value + elif value is ChannelType.voice: + return VoiceChannel, value + elif value is ChannelType.category: + return CategoryChannel, value + elif value is ChannelType.news: + return NewsChannel, value + elif value is ChannelType.stage_voice: + return StageChannel, value + elif value is ChannelType.directory: + return None, value # todo: Add DirectoryChannel when applicable + elif value is ChannelType.forum: + return ForumChannel, value + elif value is ChannelType.media: + return MediaChannel, value + else: + return None, value + + +def _channel_factory(channel_type: int): + cls, value = _guild_channel_factory(channel_type) + if value is ChannelType.private: + return DMChannel, value + elif value is ChannelType.group: + return GroupChannel, value + else: + return cls, value + + +def _threaded_channel_factory(channel_type: int): + cls, value = _channel_factory(channel_type) + if value in ( + ChannelType.private_thread, + ChannelType.public_thread, + ChannelType.news_thread, + ): + return Thread, value + return cls, value + + +def _threaded_guild_channel_factory(channel_type: int): + cls, value = _guild_channel_factory(channel_type) + if value in ( + ChannelType.private_thread, + ChannelType.public_thread, + ChannelType.news_thread, + ): + return Thread, value + return cls, value diff --git a/discord/channel/base.py b/discord/channel/base.py new file mode 100644 index 0000000000..f446833eee --- /dev/null +++ b/discord/channel/base.py @@ -0,0 +1,2025 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 copy +import datetime +import logging +from abc import ABC, abstractmethod +from collections.abc import Collection, Iterable, Sequence +from typing import TYPE_CHECKING, Any, Callable, Generic, cast, overload + +from typing_extensions import Self, TypeVar, override + +from ..abc import Messageable, Snowflake, SnowflakeTime, User, _Overwrites, _purge_messages_helper +from ..emoji import GuildEmoji, PartialEmoji +from ..enums import ChannelType, InviteTarget, SortOrder, try_enum +from ..errors import ClientException +from ..flags import ChannelFlags, MessageFlags +from ..iterators import ArchivedThreadIterator +from ..mixins import Hashable +from ..utils import MISSING, Undefined, find, snowflake_time +from ..utils.private import SnowflakeList, bytes_to_base64_data, copy_doc, get_as_snowflake + +if TYPE_CHECKING: + from ..embeds import Embed + from ..errors import InvalidArgument + from ..file import File + from ..guild import Guild + from ..invite import Invite + from ..member import Member + from ..mentions import AllowedMentions + from ..message import EmojiInputType, Message, PartialMessage + from ..object import Object + from ..partial_emoji import _EmojiTag + from ..permissions import PermissionOverwrite, Permissions + from ..role import Role + from ..scheduled_events import ScheduledEvent + from ..sticker import GuildSticker, StickerItem + from ..types.channel import CategoryChannel as CategoryChannelPayload + from ..types.channel import Channel as ChannelPayload + from ..types.channel import ForumChannel as ForumChannelPayload + from ..types.channel import ForumTag as ForumTagPayload + from ..types.channel import GuildChannel as GuildChannelPayload + from ..types.channel import MediaChannel as MediaChannelPayload + from ..types.channel import NewsChannel as NewsChannelPayload + from ..types.channel import StageChannel as StageChannelPayload + from ..types.channel import TextChannel as TextChannelPayload + from ..types.channel import VoiceChannel as VoiceChannelPayload + from ..types.guild import ChannelPositionUpdate as ChannelPositionUpdatePayload + from ..ui.view import View + from ..webhook import Webhook + from .category import CategoryChannel + from .channel import ForumTag + from .text import TextChannel + from .thread import Thread + +_log = logging.getLogger(__name__) + +if TYPE_CHECKING: + from ..app.state import ConnectionState + + +P = TypeVar("P", bound="ChannelPayload") + + +class BaseChannel(ABC, Generic[P]): + __slots__: tuple[str, ...] = ("id", "_type", "_state", "_data") # pyright: ignore [reportIncompatibleUnannotatedOverride] + + def __init__(self, id: int, state: ConnectionState): + self.id: int = id + self._state: ConnectionState = state + self._data: P = {} # type: ignore + + async def _update(self, data: P) -> None: + self._type: int = data["type"] + self._data = self._data | data # type: ignore + + @classmethod + async def _from_data(cls, *, data: P, state: ConnectionState, **kwargs) -> Self: + if kwargs: + _log.warning("Unexpected keyword arguments passed to %s._from_data: %r", cls.__name__, kwargs) + self = cls(int(data["id"]), state) + await self._update(data) + return self + + @property + def type(self) -> ChannelType: + """The channel's Discord channel type.""" + return try_enum(ChannelType, self._type) + + async def _get_channel(self) -> Self: + return self + + @property + def created_at(self) -> datetime.datetime: + """The channel's creation time in UTC.""" + return snowflake_time(self.id) + + @abstractmethod + @override + def __repr__(self) -> str: ... + + @property + @abstractmethod + def jump_url(self) -> str: ... + + +P_guild = TypeVar( + "P_guild", + bound="TextChannelPayload | NewsChannelPayload | VoiceChannelPayload | CategoryChannelPayload | StageChannelPayload | ForumChannelPayload", + default="TextChannelPayload | NewsChannelPayload | VoiceChannelPayload | CategoryChannelPayload | StageChannelPayload | ForumChannelPayload", +) + + +class GuildChannel(BaseChannel[P_guild], ABC, Generic[P_guild]): + """Represents a Discord guild channel.""" + + """An ABC that details the common operations on a Discord guild channel. + + The following implement this ABC: + + - :class:`~discord.TextChannel` + - :class:`~discord.VoiceChannel` + - :class:`~discord.CategoryChannel` + - :class:`~discord.StageChannel` + - :class:`~discord.ForumChannel` + + This ABC must also implement :class:`~discord.abc.Snowflake`. + + Attributes + ---------- + name: :class:`str` + The channel name. + guild: :class:`~discord.Guild` + The guild the channel belongs to. + 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. + """ + + __slots__: tuple[str, ...] = ("name", "guild", "category_id", "flags", "_overwrites") + + @override + def __init__(self, id: int, *, guild: Guild, state: ConnectionState) -> None: + self.guild: Guild = guild + super().__init__(id, state) + + @classmethod + @override + async def _from_data(cls, *, data: P_guild, state: ConnectionState, guild: Guild, **kwargs) -> Self: + if kwargs: + _log.warning("Unexpected keyword arguments passed to %s._from_data: %r", cls.__name__, kwargs) + self = cls(int(data["id"]), guild=guild, state=state) + await self._update(data) + return self + + @override + async def _update(self, data: P_guild) -> None: + await super()._update(data) + self.name: str = data["name"] + self.category_id: int | None = get_as_snowflake(data, "parent_id") or getattr(self, "category_id", None) + if flags_value := data.get("flags", 0): + self.flags: ChannelFlags = ChannelFlags._from_value(flags_value) + self._fill_overwrites(data) + + @override + def __str__(self) -> str: + return self.name + + async def _edit(self, options: dict[str, Any], reason: str | None) -> ChannelPayload | None: + try: + parent = options.pop("category") + except KeyError: + parent_id = MISSING + else: + parent_id = parent and parent.id + + try: + options["rate_limit_per_user"] = options.pop("slowmode_delay") + except KeyError: + pass + + try: + options["default_thread_rate_limit_per_user"] = options.pop("default_thread_slowmode_delay") + except KeyError: + pass + + try: + options["flags"] = options.pop("flags").value + except KeyError: + pass + + try: + options["available_tags"] = [tag.to_dict() for tag in options.pop("available_tags")] + except KeyError: + pass + + try: + rtc_region = options.pop("rtc_region") + except KeyError: + pass + else: + options["rtc_region"] = None if rtc_region is None else str(rtc_region) + + try: + video_quality_mode = options.pop("video_quality_mode") + except KeyError: + pass + else: + options["video_quality_mode"] = int(video_quality_mode) + + lock_permissions = options.pop("sync_permissions", False) + + try: + position = options.pop("position") + except KeyError: + if parent_id is not MISSING: + if lock_permissions: + category = self.guild.get_channel(parent_id) + if category: + options["permission_overwrites"] = [c._asdict() for c in category._overwrites] + options["parent_id"] = parent_id + elif lock_permissions and self.category_id is not None: + # if we're syncing permissions on a pre-existing channel category without changing it + # we need to update the permissions to point to the pre-existing category + category = self.guild.get_channel(self.category_id) + if category: + options["permission_overwrites"] = [c._asdict() for c in category._overwrites] + else: + await self._move( + position, + parent_id=parent_id, + lock_permissions=lock_permissions, + reason=reason, + ) + + overwrites = options.get("overwrites") + if overwrites is not None: + perms = [] + for target, perm in overwrites.items(): + if not isinstance(perm, PermissionOverwrite): + raise InvalidArgument(f"Expected PermissionOverwrite received {perm.__class__.__name__}") + + allow, deny = perm.pair() + payload = { + "allow": allow.value, + "deny": deny.value, + "id": target.id, + "type": (_Overwrites.ROLE if isinstance(target, Role) else _Overwrites.MEMBER), + } + + perms.append(payload) + options["permission_overwrites"] = perms + + try: + ch_type = options["type"] + except KeyError: + pass + else: + if not isinstance(ch_type, ChannelType): + raise InvalidArgument("type field must be of type ChannelType") + options["type"] = ch_type.value + + try: + default_reaction_emoji = options["default_reaction_emoji"] + except KeyError: + pass + else: + if isinstance(default_reaction_emoji, _EmojiTag): # GuildEmoji, PartialEmoji + default_reaction_emoji = default_reaction_emoji._to_partial() + elif isinstance(default_reaction_emoji, int): + default_reaction_emoji = PartialEmoji(name=None, id=default_reaction_emoji) + elif isinstance(default_reaction_emoji, str): + default_reaction_emoji = PartialEmoji.from_str(default_reaction_emoji) + elif default_reaction_emoji is None: + pass + else: + raise InvalidArgument("default_reaction_emoji must be of type: GuildEmoji | int | str | None") + + options["default_reaction_emoji"] = ( + default_reaction_emoji._to_forum_reaction_payload() if default_reaction_emoji else None + ) + + if options: + return await self._state.http.edit_channel(self.id, reason=reason, **options) + + def _fill_overwrites(self, data: GuildChannelPayload) -> None: + self._overwrites: list[_Overwrites] = [] + everyone_index = 0 + everyone_id = self.guild.id + + for index, overridden in enumerate(data.get("permission_overwrites", [])): + overwrite = _Overwrites(overridden) + self._overwrites.append(overwrite) + + if overwrite.type == _Overwrites.MEMBER: + continue + + if overwrite.id == everyone_id: + # the @everyone role is not guaranteed to be the first one + # in the list of permission overwrites, however the permission + # resolution code kind of requires that it is the first one in + # the list since it is special. So we need the index so we can + # swap it to be the first one. + everyone_index = index + + # do the swap + tmp = self._overwrites + if tmp: + tmp[everyone_index], tmp[0] = tmp[0], tmp[everyone_index] + + @property + def changed_roles(self) -> list[Role]: + """Returns a list of roles that have been overridden from + their default values in the :attr:`~discord.Guild.roles` attribute. + """ + ret = [] + g = self.guild + for overwrite in filter(lambda o: o.is_role(), self._overwrites): + role = g.get_role(overwrite.id) + if role is None: + continue + + role = copy.copy(role) + role.permissions.handle_overwrite(overwrite.allow, overwrite.deny) + ret.append(role) + return ret + + @property + def mention(self) -> str: + """The string that allows you to mention the channel.""" + return f"<#{self.id}>" + + @property + @override + def jump_url(self) -> str: + """Returns a URL that allows the client to jump to the channel. + + .. versionadded:: 2.0 + """ + return f"https://discord.com/channels/{self.guild.id}/{self.id}" + + def overwrites_for(self, obj: Role | User) -> PermissionOverwrite: + """Returns the channel-specific overwrites for a member or a role. + + Parameters + ---------- + obj: Union[:class:`~discord.Role`, :class:`~discord.abc.User`] + The role or user denoting + whose overwrite to get. + + Returns + ------- + :class:`~discord.PermissionOverwrite` + The permission overwrites for this object. + """ + + if isinstance(obj, User): + predicate: Callable[[Any], bool] = lambda p: p.is_member() + elif isinstance(obj, Role): + predicate = lambda p: p.is_role() + else: + predicate = lambda p: True + + for overwrite in filter(predicate, self._overwrites): + if overwrite.id == obj.id: + allow = Permissions(overwrite.allow) + deny = Permissions(overwrite.deny) + return PermissionOverwrite.from_pair(allow, deny) + + return PermissionOverwrite() + + async def get_overwrites(self) -> dict[Role | Member | Object, PermissionOverwrite]: + """Returns all of the channel's overwrites. + + This is returned as a dictionary where the key contains the target which + can be either a :class:`~discord.Role` or a :class:`~discord.Member` and the value is the + overwrite as a :class:`~discord.PermissionOverwrite`. + + Returns + ------- + Dict[Union[:class:`~discord.Role`, :class:`~discord.Member`, :class:`~discord.Object`], :class:`~discord.PermissionOverwrite`] + The channel's permission overwrites. + """ + ret: dict[Role | Member | Object, PermissionOverwrite] = {} + for ow in self._overwrites: + allow = Permissions(ow.allow) + deny = Permissions(ow.deny) + overwrite = PermissionOverwrite.from_pair(allow, deny) + target = None + + if ow.is_role(): + target = self.guild.get_role(ow.id) + elif ow.is_member(): + target = await self.guild.get_member(ow.id) + + if target is not None: + ret[target] = overwrite + else: + ret[Object(id=ow.id)] = overwrite + return ret + + @property + def category(self) -> CategoryChannel | None: + """The category this channel belongs to. + + If there is no category then this is ``None``. + """ + return cast("CategoryChannel | None", self.guild.get_channel(self.category_id)) if self.category_id else None + + @property + def members(self) -> Collection[Member]: + """Returns all members that can view this channel. + + This is calculated based on the channel's permission overwrites and + the members' roles. + + Returns + ------- + Collection[:class:`Member`] + All members who have permission to view this channel. + """ + return [m for m in self.guild.members if self.permissions_for(m).read_messages] + + async def permissions_are_synced(self) -> bool: + """Whether the permissions for this channel are synced with the + category it belongs to. + + If there is no category then this is ``False``. + + .. versionadded:: 3.0 + """ + if self.category_id is None: + return False + + category: CategoryChannel | None = cast("CategoryChannel | None", self.guild.get_channel(self.category_id)) + return bool(category and await category.get_overwrites() == await self.get_overwrites()) + + def permissions_for(self, obj: Member | Role, /) -> Permissions: + """Handles permission resolution for the :class:`~discord.Member` + or :class:`~discord.Role`. + + This function takes into consideration the following cases: + + - Guild owner + - Guild roles + - Channel overrides + - Member overrides + + If a :class:`~discord.Role` is passed, then it checks the permissions + someone with that role would have, which is essentially: + + - The default role permissions + - The permissions of the role used as a parameter + - The default role permission overwrites + - The permission overwrites of the role used as a parameter + + .. versionchanged:: 2.0 + The object passed in can now be a role object. + + Parameters + ---------- + obj: Union[:class:`~discord.Member`, :class:`~discord.Role`] + The object to resolve permissions for. This could be either + a member or a role. If it's a role then member overwrites + are not computed. + + Returns + ------- + :class:`~discord.Permissions` + The resolved permissions for the member or role. + """ + + # The current cases can be explained as: + # Guild owner get all permissions -- no questions asked. Otherwise... + # The @everyone role gets the first application. + # After that, the applied roles that the user has in the channel + # (or otherwise) are then OR'd together. + # After the role permissions are resolved, the member permissions + # have to take into effect. + # After all that is done, you have to do the following: + + # If manage permissions is True, then all permissions are set to True. + + # The operation first takes into consideration the denied + # and then the allowed. + + if self.guild.owner_id == obj.id: + return Permissions.all() + + default = self.guild.default_role + base = Permissions(default.permissions.value if default else 0) + + # Handle the role case first + if isinstance(obj, Role): + base.value |= obj._permissions + + if base.administrator: + return Permissions.all() + + # Apply @everyone allow/deny first since it's special + try: + maybe_everyone = self._overwrites[0] + if maybe_everyone.id == self.guild.id: + base.handle_overwrite(allow=maybe_everyone.allow, deny=maybe_everyone.deny) + except IndexError: + pass + + if obj.is_default(): + return base + + overwrite = find(lambda o: o.type == _Overwrites.ROLE and o.id == obj.id, self._overwrites) + if overwrite is not None: + base.handle_overwrite(overwrite.allow, overwrite.deny) + + return base + + roles = obj._roles + get_role = self.guild.get_role + + # Apply guild roles that the member has. + for role_id in roles: + role = get_role(role_id) + if role is not None: + base.value |= role._permissions + + # Guild-wide Administrator -> True for everything + # Bypass all channel-specific overrides + if base.administrator: + return Permissions.all() + + # Apply @everyone allow/deny first since it's special + try: + maybe_everyone = self._overwrites[0] + if maybe_everyone.id == self.guild.id: + base.handle_overwrite(allow=maybe_everyone.allow, deny=maybe_everyone.deny) + remaining_overwrites = self._overwrites[1:] + else: + remaining_overwrites = self._overwrites + except IndexError: + remaining_overwrites = self._overwrites + + denies = 0 + allows = 0 + + # Apply channel specific role permission overwrites + for overwrite in remaining_overwrites: + if overwrite.is_role() and roles.has(overwrite.id): + denies |= overwrite.deny + allows |= overwrite.allow + + base.handle_overwrite(allow=allows, deny=denies) + + # Apply member specific permission overwrites + for overwrite in remaining_overwrites: + if overwrite.is_member() and overwrite.id == obj.id: + base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny) + break + + # if you can't send a message in a channel then you can't have certain + # permissions as well + if not base.send_messages: + base.send_tts_messages = False + base.mention_everyone = False + base.embed_links = False + base.attach_files = False + + # if you can't read a channel then you have no permissions there + if not base.read_messages: + denied = Permissions.all_channel() + base.value &= ~denied.value + + return base + + async def delete(self, *, reason: str | None = None) -> None: + """|coro| + + Deletes the channel. + + You must have :attr:`~discord.Permissions.manage_channels` permission to use this. + + Parameters + ---------- + reason: Optional[:class:`str`] + The reason for deleting this channel. + Shows up on the audit log. + + Raises + ------ + ~discord.Forbidden + You do not have proper permissions to delete the channel. + ~discord.NotFound + The channel was not found or was already deleted. + ~discord.HTTPException + Deleting the channel failed. + """ + await self._state.http.delete_channel(self.id, reason=reason) + + @overload + async def set_permissions( + self, + target: Member | Role, + *, + overwrite: PermissionOverwrite | None = ..., + reason: str | None = ..., + ) -> None: ... + + @overload + async def set_permissions( + self, + target: Member | Role, + *, + overwrite: Undefined = MISSING, + reason: str | None = ..., + **permissions: bool, + ) -> None: ... + + async def set_permissions( + self, + target: Member | Role, + *, + overwrite: PermissionOverwrite | None | Undefined = MISSING, + reason: str | None = None, + **permissions: bool, + ) -> None: + r"""|coro| + + Sets the channel specific permission overwrites for a target in the + channel. + + The ``target`` parameter should either be a :class:`~discord.Member` or a + :class:`~discord.Role` that belongs to guild. + + The ``overwrite`` parameter, if given, must either be ``None`` or + :class:`~discord.PermissionOverwrite`. For convenience, you can pass in + keyword arguments denoting :class:`~discord.Permissions` attributes. If this is + done, then you cannot mix the keyword arguments with the ``overwrite`` + parameter. + + If the ``overwrite`` parameter is ``None``, then the permission + overwrites are deleted. + + You must have the :attr:`~discord.Permissions.manage_roles` permission to use this. + + .. note:: + + This method *replaces* the old overwrites with the ones given. + + Examples + ---------- + + Setting allow and deny: :: + + await message.channel.set_permissions(message.author, read_messages=True, send_messages=False) + + Deleting overwrites :: + + await channel.set_permissions(member, overwrite=None) + + Using :class:`~discord.PermissionOverwrite` :: + + overwrite = discord.PermissionOverwrite() + overwrite.send_messages = False + overwrite.read_messages = True + await channel.set_permissions(member, overwrite=overwrite) + + Parameters + ----------- + target: Union[:class:`~discord.Member`, :class:`~discord.Role`] + The member or role to overwrite permissions for. + overwrite: Optional[:class:`~discord.PermissionOverwrite`] + The permissions to allow and deny to the target, or ``None`` to + delete the overwrite. + \*\*permissions + A keyword argument list of permissions to set for ease of use. + Cannot be mixed with ``overwrite``. + reason: Optional[:class:`str`] + The reason for doing this action. Shows up on the audit log. + + Raises + ------- + ~discord.Forbidden + You do not have permissions to edit channel specific permissions. + ~discord.HTTPException + Editing channel specific permissions failed. + ~discord.NotFound + The role or member being edited is not part of the guild. + ~discord.InvalidArgument + The overwrite parameter invalid or the target type was not + :class:`~discord.Role` or :class:`~discord.Member`. + """ + + http = self._state.http + + if isinstance(target, User): + perm_type = _Overwrites.MEMBER + elif isinstance(target, Role): + perm_type = _Overwrites.ROLE + else: + raise InvalidArgument("target parameter must be either Member or Role") + + if overwrite is MISSING: + if len(permissions) == 0: + raise InvalidArgument("No overwrite provided.") + try: + overwrite = PermissionOverwrite(**permissions) + except (ValueError, TypeError) as e: + raise InvalidArgument("Invalid permissions given to keyword arguments.") from e + elif len(permissions) > 0: + raise InvalidArgument("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): + (allow, deny) = overwrite.pair() + await http.edit_channel_permissions(self.id, target.id, allow.value, deny.value, perm_type, reason=reason) + else: + raise InvalidArgument("Invalid overwrite type provided.") + + async def _clone_impl( + self, + base_attrs: dict[str, Any], + *, + name: str | None = None, + reason: str | None = 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 + guild_id = self.guild.id + cls = self.__class__ + data: P_guild = cast( + "P_guild", await self._state.http.create_channel(guild_id, self.type.value, reason=reason, **base_attrs) + ) + clone = cls(id=int(data["id"]), guild=self.guild, state=self._state) + await clone._update(data) + + self.guild._channels[clone.id] = clone + return clone + + async def clone(self, *, name: str | None = None, reason: str | None = None) -> Self: + """|coro| + + Clones this channel. This creates a channel with the same properties + as this channel. + + You must have the :attr:`~discord.Permissions.manage_channels` permission to + do this. + + .. versionadded:: 1.1 + + Parameters + ---------- + name: Optional[:class:`str`] + The name of the new channel. If not provided, defaults to this + channel name. + reason: Optional[:class:`str`] + The reason for cloning this channel. Shows up on the audit log. + + Returns + ------- + :class:`.abc.GuildChannel` + The channel that was created. + + Raises + ------ + ~discord.Forbidden + You do not have the proper permissions to create this channel. + ~discord.HTTPException + Creating the channel failed. + """ + raise NotImplementedError + + async def create_invite( + self, + *, + reason: str | None = None, + max_age: int = 0, + max_uses: int = 0, + temporary: bool = False, + unique: bool = True, + target_event: ScheduledEvent | None = None, + target_type: InviteTarget | None = None, + target_user: User | None = None, + target_application_id: int | None = None, + ) -> Invite: + """|coro| + + Creates an instant invite from a text or voice channel. + + You must have the :attr:`~discord.Permissions.create_instant_invite` permission to + do this. + + Parameters + ---------- + max_age: :class:`int` + How long the invite should last in seconds. If it's 0 then the invite + doesn't expire. Defaults to ``0``. + max_uses: :class:`int` + How many uses the invite could be used for. If it's 0 then there + are unlimited uses. Defaults to ``0``. + temporary: :class:`bool` + Denotes that the invite grants temporary membership + (i.e. they get kicked after they disconnect). Defaults to ``False``. + unique: :class:`bool` + Indicates if a unique invite URL should be created. Defaults to True. + If this is set to ``False`` then it will return a previously created + invite. + reason: Optional[:class:`str`] + The reason for creating this invite. Shows up on the audit log. + target_type: Optional[:class:`.InviteTarget`] + The type of target for the voice channel invite, if any. + + .. versionadded:: 2.0 + + target_user: Optional[:class:`User`] + The user whose stream to display for this invite, required if `target_type` is `TargetType.stream`. + The user must be streaming in the channel. + + .. versionadded:: 2.0 + + target_application_id: Optional[:class:`int`] + The id of the embedded application for the invite, required if `target_type` is + `TargetType.embedded_application`. + + .. versionadded:: 2.0 + + target_event: Optional[:class:`.ScheduledEvent`] + The scheduled event object to link to the event. + Shortcut to :meth:`.Invite.set_scheduled_event` + + See :meth:`.Invite.set_scheduled_event` for more + info on event invite linking. + + .. versionadded:: 2.0 + + Returns + ------- + :class:`~discord.Invite` + The invite that was created. + + Raises + ------ + ~discord.HTTPException + Invite creation failed. + + ~discord.NotFound + The channel that was passed is a category or an invalid channel. + """ + if target_type is InviteTarget.unknown: + raise TypeError("target_type cannot be unknown") + + data = await self._state.http.create_invite( + self.id, + reason=reason, + max_age=max_age, + max_uses=max_uses, + temporary=temporary, + unique=unique, + 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, + ) + invite = await Invite.from_incomplete(data=data, state=self._state) + if target_event: + invite.set_scheduled_event(target_event) + return invite + + async def invites(self) -> list[Invite]: + """|coro| + + Returns a list of all active instant invites from this channel. + + You must have :attr:`~discord.Permissions.manage_channels` to get this information. + + Returns + ------- + List[:class:`~discord.Invite`] + The list of invites that are currently active. + + Raises + ------ + ~discord.Forbidden + You do not have proper permissions to get the information. + ~discord.HTTPException + An error occurred while fetching the information. + """ + + data = await self._state.http.invites_from_channel(self.id) + guild = self.guild + return [Invite(state=self._state, data=invite, channel=self, guild=guild) for invite in data] + + +P_guild_top_level = TypeVar( + "P_guild_top_level", + bound="TextChannelPayload | NewsChannelPayload | VoiceChannelPayload | CategoryChannelPayload | StageChannelPayload | ForumChannelPayload", + default="TextChannelPayload | NewsChannelPayload | VoiceChannelPayload | CategoryChannelPayload | StageChannelPayload | ForumChannelPayload", +) + + +class GuildTopLevelChannel(GuildChannel[P_guild_top_level], ABC, Generic[P_guild_top_level]): + """An ABC for guild channels that can be positioned in the channel list. + + This includes categories and all channels that appear in the channel sidebar + (text, voice, news, stage, forum, media channels). Threads do not inherit from + this class as they are not positioned in the main channel list. + + .. versionadded:: 3.0 + + Attributes + ---------- + position: int + The position in the channel list. This is a number that starts at 0. + e.g. the top channel is position 0. + """ + + __slots__: tuple[str, ...] = ("position",) + + @override + async def _update(self, data: P_guild_top_level) -> None: + await super()._update(data) + self.position: int = data.get("position", 0) + + @property + @abstractmethod + def _sorting_bucket(self) -> int: + """Returns the bucket for sorting channels by type.""" + raise NotImplementedError + + async def _move( + self, + position: int, + parent_id: Any | None = None, + lock_permissions: bool = False, + *, + reason: str | None, + ) -> None: + """Internal method to move a channel to a specific position. + + Parameters + ---------- + position: int + The new position for the channel. + parent_id: Any | None + The parent category ID, if moving to a category. + lock_permissions: bool + Whether to sync permissions with the category. + reason: str | None + The reason for moving the channel. + + Raises + ------ + InvalidArgument + The position is less than 0. + """ + if position < 0: + raise InvalidArgument("Channel position cannot be less than 0.") + + bucket = self._sorting_bucket + channels: list[Self] = [c for c in self.guild.channels if c._sorting_bucket == bucket] + + channels.sort(key=lambda c: c.position) + + try: + # remove ourselves from the channel list + channels.remove(self) + except ValueError: + # not there somehow lol + return + else: + index = next( + (i for i, c in enumerate(channels) if c.position >= position), + len(channels), + ) + # add ourselves at our designated position + channels.insert(index, self) + + payload: list[ChannelPositionUpdatePayload] = [] + for index, c in enumerate(channels): + d: ChannelPositionUpdatePayload = {"id": c.id, "position": index} + if parent_id is not MISSING and c.id == self.id: + d.update(parent_id=parent_id, lock_permissions=lock_permissions) + payload.append(d) + + await self._state.http.bulk_channel_update(self.guild.id, payload, reason=reason) + + @overload + async def move( + self, + *, + beginning: bool, + offset: int | Undefined = MISSING, + category: Snowflake | None | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + reason: str | None | Undefined = MISSING, + ) -> None: ... + + @overload + async def move( + self, + *, + end: bool, + offset: int | Undefined = MISSING, + category: Snowflake | None | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + reason: str | Undefined = MISSING, + ) -> None: ... + + @overload + async def move( + self, + *, + before: Snowflake, + offset: int | Undefined = MISSING, + category: Snowflake | None | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + reason: str | Undefined = MISSING, + ) -> None: ... + + @overload + async def move( + self, + *, + after: Snowflake, + offset: int | Undefined = MISSING, + category: Snowflake | None | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + reason: str | Undefined = MISSING, + ) -> None: ... + + async def move(self, **kwargs: Any) -> None: + """|coro| + + A rich interface to help move a channel relative to other channels. + + If exact position movement is required, ``edit`` should be used instead. + + You must have :attr:`~discord.Permissions.manage_channels` permission to + do this. + + .. note:: + + Voice channels will always be sorted below text channels. + This is a Discord limitation. + + .. versionadded:: 1.7 + + Parameters + ---------- + beginning: bool + Whether to move the channel to the beginning of the + channel list (or category if given). + This is mutually exclusive with ``end``, ``before``, and ``after``. + end: bool + Whether to move the channel to the end of the + channel list (or category if given). + This is mutually exclusive with ``beginning``, ``before``, and ``after``. + before: ~discord.abc.Snowflake + The channel that should be before our current channel. + This is mutually exclusive with ``beginning``, ``end``, and ``after``. + after: ~discord.abc.Snowflake + The channel that should be after our current channel. + This is mutually exclusive with ``beginning``, ``end``, and ``before``. + offset: int + The number of channels to offset the move by. For example, + an offset of ``2`` with ``beginning=True`` would move + it 2 after the beginning. A positive number moves it below + while a negative number moves it above. Note that this + number is relative and computed after the ``beginning``, + ``end``, ``before``, and ``after`` parameters. + category: ~discord.abc.Snowflake | None + The category to move this channel under. + If ``None`` is given then it moves it out of the category. + This parameter is ignored if moving a category channel. + sync_permissions: bool + Whether to sync the permissions with the category (if given). + reason: str | None + The reason for the move. + + Raises + ------ + InvalidArgument + An invalid position was given or a bad mix of arguments was passed. + Forbidden + You do not have permissions to move the channel. + HTTPException + Moving the channel failed. + """ + + if not kwargs: + return + + beginning, end = kwargs.get("beginning"), kwargs.get("end") + before, after = kwargs.get("before"), kwargs.get("after") + offset = kwargs.get("offset", 0) + if sum(bool(a) for a in (beginning, end, before, after)) > 1: + raise InvalidArgument("Only one of [before, after, end, beginning] can be used.") + + bucket = self._sorting_bucket + parent_id = kwargs.get("category", MISSING) + channels: list[GuildChannel] + if parent_id not in (MISSING, None): + parent_id = parent_id.id + channels = [ + ch for ch in self.guild.channels if ch._sorting_bucket == bucket and ch.category_id == parent_id + ] + else: + channels = [ + ch for ch in self.guild.channels if ch._sorting_bucket == bucket and ch.category_id == self.category_id + ] + + channels.sort(key=lambda c: (c.position, c.id)) + + try: + # Try to remove ourselves from the channel list + channels.remove(self) + except ValueError: + # If we're not there then it's probably due to not being in the category + pass + + index = None + if beginning: + index = 0 + elif end: + index = len(channels) + elif before: + index = next((i for i, c in enumerate(channels) if c.id == before.id), None) + elif after: + index = next((i + 1 for i, c in enumerate(channels) if c.id == after.id), None) + + if index is None: + raise InvalidArgument("Could not resolve appropriate move position") + # TODO: This could use self._move to avoid code duplication + channels.insert(max((index + offset), 0), self) + payload: list[ChannelPositionUpdatePayload] = [] + lock_permissions = kwargs.get("sync_permissions", False) + reason = kwargs.get("reason") + for index, channel in enumerate(channels): + d: ChannelPositionUpdatePayload = {"id": channel.id, "position": index} # pyright: ignore[reportAssignmentType] + if parent_id is not MISSING and channel.id == self.id: + d.update(parent_id=parent_id, lock_permissions=lock_permissions) + payload.append(d) + + await self._state.http.bulk_channel_update(self.guild.id, payload, reason=reason) + + +P_guild_threadable = TypeVar( + "P_guild_threadable", + bound="TextChannelPayload | NewsChannelPayload | ForumChannelPayload | MediaChannelPayload", + default="TextChannelPayload | NewsChannelPayload | ForumChannelPayload | MediaChannelPayload", +) + + +class GuildThreadableChannel: + """An ABC for guild channels that support thread creation. + + This includes text, news, forum, and media channels. + Voice, stage, and category channels do not support threads. + + This is a mixin class that adds threading capabilities to guild channels. + + .. versionadded:: 3.0 + + Attributes + ---------- + default_auto_archive_duration: int + The default auto archive duration in minutes for threads created in this channel. + default_thread_slowmode_delay: int | None + The initial slowmode delay to set on newly created threads in this channel. + """ + + __slots__ = () # Mixin class - slots defined in concrete classes + + # Type hints for attributes that this mixin expects from the inheriting class + if TYPE_CHECKING: + id: int + guild: Guild + default_auto_archive_duration: int + default_thread_slowmode_delay: int | None + + async def _update(self, data) -> None: + """Update threadable channel attributes.""" + await super()._update(data) # Call next in MRO + self.default_auto_archive_duration: int = data.get("default_auto_archive_duration", 1440) + self.default_thread_slowmode_delay: int | None = data.get("default_thread_rate_limit_per_user") + + @property + def threads(self) -> list[Thread]: + """Returns all the threads that you can see in this channel. + + .. versionadded:: 2.0 + + Returns + ------- + list[:class:`Thread`] + All active threads in this channel. + """ + return [thread for thread in self.guild._threads.values() if thread.parent_id == self.id] + + def get_thread(self, thread_id: int, /) -> Thread | None: + """Returns a thread with the given ID. + + .. versionadded:: 2.0 + + Parameters + ---------- + thread_id: int + The ID to search for. + + Returns + ------- + Thread | None + The returned thread or ``None`` if not found. + """ + return self.guild.get_thread(thread_id) + + def archived_threads( + self, + *, + private: bool = False, + joined: bool = False, + limit: int | None = 50, + before: Snowflake | datetime.datetime | None = None, + ) -> ArchivedThreadIterator: + """Returns an iterator that iterates over all archived threads in the channel. + + You must have :attr:`~Permissions.read_message_history` to use this. If iterating over private threads + then :attr:`~Permissions.manage_threads` is also required. + + .. versionadded:: 2.0 + + Parameters + ---------- + limit: int | None + The number of threads to retrieve. + If ``None``, retrieves every archived thread in the channel. Note, however, + that this would make it a slow operation. + before: Snowflake | datetime.datetime | None + Retrieve archived channels before the given date or ID. + private: bool + Whether to retrieve private archived threads. + joined: bool + Whether to retrieve private archived threads that you've joined. + You cannot set ``joined`` to ``True`` and ``private`` to ``False``. + + Yields + ------ + :class:`Thread` + The archived threads. + + Raises + ------ + Forbidden + You do not have permissions to get archived threads. + HTTPException + The request to get the archived threads failed. + """ + return ArchivedThreadIterator( + self.id, + self.guild, + limit=limit, + joined=joined, + private=private, + before=before, + ) + + +P_guild_postable = TypeVar( + "P_guild_postable", + bound="ForumChannelPayload | MediaChannelPayload", + default="ForumChannelPayload | MediaChannelPayload", +) + + +class ForumTag(Hashable): + """Represents a forum tag that can be added to a thread inside a :class:`ForumChannel` + . + .. versionadded:: 2.3 + + .. container:: operations + + .. describe:: x == y + + Checks if two forum tags are equal. + + .. describe:: x != y + + Checks if two forum tags are not equal. + + .. describe:: hash(x) + + Returns the forum tag's hash. + + .. describe:: str(x) + + Returns the forum tag's name. + + Attributes + ---------- + id: :class:`int` + The tag ID. + Note that if the object was created manually then this will be ``0``. + name: :class:`str` + The name of the tag. Can only be up to 20 characters. + moderated: :class:`bool` + Whether this tag can only be added or removed by a moderator with + the :attr:`~Permissions.manage_threads` permission. + emoji: :class:`PartialEmoji` + The emoji that is used to represent this tag. + Note that if the emoji is a custom emoji, it will *not* have name information. + """ + + __slots__ = ("name", "id", "moderated", "emoji") + + def __init__(self, *, name: str, emoji: EmojiInputType, moderated: bool = False) -> None: + self.name: str = name + self.id: int = 0 + self.moderated: bool = moderated + self.emoji: PartialEmoji + if isinstance(emoji, _EmojiTag): + self.emoji = emoji._to_partial() + elif isinstance(emoji, str): + self.emoji = PartialEmoji.from_str(emoji) + else: + raise TypeError(f"emoji must be a GuildEmoji, PartialEmoji, or str and not {emoji.__class__!r}") + + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return self.name + + @classmethod + def from_data(cls, *, state: ConnectionState, data: ForumTagPayload) -> ForumTag: + self = cls.__new__(cls) + self.name = data["name"] + self.id = int(data["id"]) + self.moderated = data.get("moderated", False) + + emoji_name = data["emoji_name"] or "" + emoji_id = get_as_snowflake(data, "emoji_id") or None + self.emoji = PartialEmoji.with_state(state=state, name=emoji_name, id=emoji_id) + return self + + def to_dict(self) -> dict[str, Any]: + payload: dict[str, Any] = { + "name": self.name, + "moderated": self.moderated, + } | self.emoji._to_forum_reaction_payload() + + if self.id: + payload["id"] = self.id + + return payload + + +class GuildPostableChannel( + GuildTopLevelChannel[P_guild_postable], GuildThreadableChannel, ABC, Generic[P_guild_postable] +): + """An ABC for guild channels that support posts (threads with tags). + + This is a common base for forum and media channels. These channels don't support + direct messaging, but users create posts (which are threads) with associated tags. + + .. versionadded:: 3.0 + + Attributes + ---------- + topic: str | None + The channel's topic/guidelines. ``None`` if it doesn't exist. + nsfw: bool + Whether the channel is marked as NSFW. + slowmode_delay: int + The number of seconds a member must wait between creating posts + in this channel. A value of ``0`` denotes that it is disabled. + last_message_id: int | None + The ID of the last message sent in this channel. It may not always point to an existing or valid message. + available_tags: list[ForumTag] + The set of tags that can be used in this channel. + default_sort_order: SortOrder | None + The default sort order type used to order posts in this channel. + default_reaction_emoji: str | GuildEmoji | None + The default reaction emoji for posts in this channel. + """ + + __slots__: tuple[str, ...] = ( + "topic", + "nsfw", + "slowmode_delay", + "last_message_id", + "default_auto_archive_duration", + "default_thread_slowmode_delay", + "available_tags", + "default_sort_order", + "default_reaction_emoji", + ) + + @override + async def _update(self, data: P_guild_postable) -> None: + await super()._update(data) + if not data.pop("_invoke_flag", False): + self.topic: str | None = data.get("topic") + self.nsfw: bool = data.get("nsfw", False) + self.slowmode_delay: int = data.get("rate_limit_per_user", 0) + self.last_message_id: int | None = get_as_snowflake(data, "last_message_id") + + self.available_tags: list[ForumTag] = [ + ForumTag.from_data(state=self._state, data=tag) for tag in (data.get("available_tags") or []) + ] + self.default_sort_order: SortOrder | None = data.get("default_sort_order", None) + if self.default_sort_order is not None: + self.default_sort_order = try_enum(SortOrder, self.default_sort_order) + + self.default_reaction_emoji = None + reaction_emoji_ctx: dict = data.get("default_reaction_emoji") + if reaction_emoji_ctx is not None: + emoji_name = reaction_emoji_ctx.get("emoji_name") + if emoji_name is not None: + self.default_reaction_emoji = reaction_emoji_ctx["emoji_name"] + else: + emoji_id = get_as_snowflake(reaction_emoji_ctx, "emoji_id") + if emoji_id: + self.default_reaction_emoji = await self._state.get_emoji(emoji_id) + + @property + def guidelines(self) -> str | None: + """The channel's guidelines. An alias of :attr:`topic`.""" + return self.topic + + @property + def requires_tag(self) -> bool: + """Whether a tag is required to be specified when creating a post in this channel. + + .. versionadded:: 2.3 + """ + return self.flags.require_tag + + def get_tag(self, id: int, /) -> ForumTag | None: + """Returns the :class:`ForumTag` from this channel with the given ID, if any. + + .. versionadded:: 2.3 + """ + return find(lambda t: t.id == id, self.available_tags) + + async def create_thread( + self, + name: str, + content: str | None = None, + *, + embed: Embed | None = None, + embeds: list[Embed] | None = None, + file: File | None = None, + files: list[File] | None = None, + stickers: Sequence[GuildSticker | StickerItem] | None = None, + delete_message_after: float | None = None, + nonce: int | str | None = None, + allowed_mentions: AllowedMentions | None = None, + view: View | None = None, + applied_tags: list[ForumTag] | None = None, + suppress: bool = False, + silent: bool = False, + auto_archive_duration: int | Undefined = MISSING, + slowmode_delay: int | Undefined = MISSING, + reason: str | None = None, + ) -> Thread: + """|coro| + + Creates a post (thread with initial message) in this forum or media channel. + + To create a post, you must have :attr:`~discord.Permissions.create_public_threads` or + :attr:`~discord.Permissions.send_messages` permission. + + .. versionadded:: 2.0 + + Parameters + ---------- + name: :class:`str` + The name of the post/thread. + content: :class:`str` + The content of the initial message. + embed: :class:`~discord.Embed` + The rich embed for the content. + embeds: list[:class:`~discord.Embed`] + A list of embeds to upload. Must be a maximum of 10. + file: :class:`~discord.File` + The file to upload. + files: list[:class:`~discord.File`] + A list of files to upload. Must be a maximum of 10. + stickers: Sequence[:class:`~discord.GuildSticker` | :class:`~discord.StickerItem`] + A list of stickers to upload. Must be a maximum of 3. + delete_message_after: :class:`float` + The time in seconds to wait before deleting the initial message. + nonce: :class:`str` | :class:`int` + The nonce to use for sending this message. + allowed_mentions: :class:`~discord.AllowedMentions` + Controls the mentions being processed in this message. + view: :class:`discord.ui.View` + A Discord UI View to add to the message. + applied_tags: list[:class:`ForumTag`] + A list of tags to apply to the new post. + suppress: :class:`bool` + Whether to suppress embeds in the initial message. + silent: :class:`bool` + Whether to send the message without triggering a notification. + auto_archive_duration: :class:`int` + The duration in minutes before the post is automatically archived for inactivity. + If not provided, the channel's default auto archive duration is used. + slowmode_delay: :class:`int` + The number of seconds a member must wait between sending messages in the new post. + If not provided, the channel's default slowmode is used. + reason: :class:`str` + The reason for creating the post. Shows up on the audit log. + + Returns + ------- + :class:`Thread` + The created post/thread. + + Raises + ------ + Forbidden + You do not have permissions to create a post. + HTTPException + Creating the post failed. + InvalidArgument + You provided invalid arguments. + """ + from ..errors import InvalidArgument + from ..file import File + from ..flags import MessageFlags + + state = self._state + message_content = str(content) if content is not None else None + + if embed is not None and embeds is not None: + raise InvalidArgument("cannot pass both embed and embeds parameter to create_thread()") + + if embed is not None: + embed = embed.to_dict() + + elif embeds is not None: + if len(embeds) > 10: + raise InvalidArgument("embeds parameter must be a list of up to 10 elements") + embeds = [e.to_dict() for e in embeds] + + if stickers is not None: + stickers = [sticker.id for sticker in stickers] + + if allowed_mentions is None: + allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict() + elif state.allowed_mentions is not None: + allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict() + else: + allowed_mentions = allowed_mentions.to_dict() + + flags = MessageFlags( + suppress_embeds=bool(suppress), + suppress_notifications=bool(silent), + ) + + if view: + if not hasattr(view, "__discord_ui_view__"): + raise InvalidArgument(f"view parameter must be View not {view.__class__!r}") + + components = view.to_components() + if view.is_components_v2(): + if embeds or content: + raise TypeError("cannot send embeds or content with a view using v2 component logic") + flags.is_components_v2 = True + else: + components = None + + if applied_tags is not None: + applied_tags = [str(tag.id) for tag in applied_tags] + + if file is not None and files is not None: + raise InvalidArgument("cannot pass both file and files parameter to create_thread()") + + if files is not None: + if len(files) > 10: + raise InvalidArgument("files parameter must be a list of up to 10 elements") + elif not all(isinstance(f, File) for f in files): + raise InvalidArgument("files parameter must be a list of File") + + if file is not None: + if not isinstance(file, File): + raise InvalidArgument("file parameter must be File") + files = [file] + + try: + data = await state.http.start_forum_thread( + self.id, + content=message_content, + name=name, + files=files, + embed=embed, + embeds=embeds, + nonce=nonce, + allowed_mentions=allowed_mentions, + stickers=stickers, + components=components, + auto_archive_duration=auto_archive_duration + if auto_archive_duration is not MISSING + else self.default_auto_archive_duration, + rate_limit_per_user=slowmode_delay + if slowmode_delay is not MISSING + else self.default_thread_slowmode_delay, + applied_tags=applied_tags, + flags=flags.value, + reason=reason, + ) + finally: + if files is not None: + for f in files: + f.close() + + from .thread import Thread + + ret = Thread(guild=self.guild, state=self._state, data=data) + msg = ret.get_partial_message(int(data["last_message_id"])) + if view and view.is_dispatchable(): + await state.store_view(view, msg.id) + + if delete_message_after is not None: + await msg.delete(delay=delete_message_after) + return ret + + +P_guild_messageable = TypeVar( + "P_guild_messageable", + bound="TextChannelPayload | NewsChannelPayload | VoiceChannelPayload | StageChannelPayload | ForumChannelPayload", + default="TextChannelPayload | NewsChannelPayload | VoiceChannelPayload | StageChannelPayload | ForumChannelPayload", +) + + +class GuildMessageableChannel(Messageable, ABC): + """An ABC mixin for guild channels that support messaging. + + This includes text and news channels, as well as threads. Voice and stage channels + do not support direct messaging (though they can have threads). + + This is a mixin class that adds messaging capabilities to guild channels. + + .. versionadded:: 3.0 + + Attributes + ---------- + topic: str | None + The channel's topic. ``None`` if it doesn't exist. + nsfw: bool + Whether the channel is marked as NSFW. + slowmode_delay: 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. + last_message_id: int | None + The ID of the last message sent in this channel. It may not always point to an existing or valid message. + """ + + __slots__ = () # Mixin class - slots defined in concrete classes + + # Attributes expected from inheriting classes + id: int + guild: Guild + _state: ConnectionState + topic: str | None + nsfw: bool + slowmode_delay: int + last_message_id: int | None + + async def _update(self, data) -> None: + """Update mutable attributes from API payload.""" + await super()._update(data) + # This data may be missing depending on how this object is being created/updated + if not data.pop("_invoke_flag", False): + self.topic = data.get("topic") + self.nsfw = data.get("nsfw", False) + # Does this need coercion into `int`? No idea yet. + self.slowmode_delay = data.get("rate_limit_per_user", 0) + self.last_message_id = get_as_snowflake(data, "last_message_id") + + @copy_doc(GuildChannel.permissions_for) + @override + def permissions_for(self, obj: Member | Role, /) -> Permissions: + base = super().permissions_for(obj) + + # text channels do not have voice related permissions + denied = Permissions.voice() + base.value &= ~denied.value + return base + + async def get_members(self) -> list[Member]: + """Returns all members that can see this channel.""" + return [m for m in await self.guild.get_members() if self.permissions_for(m).read_messages] + + async def get_last_message(self) -> Message | None: + """Fetches the last message from this channel in cache. + + The message might not be valid or point to an existing message. + + .. admonition:: Reliable Fetching + :class: helpful + + 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. + + Returns + ------- + Optional[:class:`Message`] + The last message in this channel or ``None`` if not found. + """ + return await self._state._get_message(self.last_message_id) if self.last_message_id else None + + async def edit(self, **options) -> Self: + """Edits the channel.""" + raise NotImplementedError + + @copy_doc(GuildChannel.clone) + @override + async def clone(self, *, name: str | None = None, reason: str | None = None) -> Self: + return await self._clone_impl( + { + "topic": self.topic, + "nsfw": self.nsfw, + "rate_limit_per_user": self.slowmode_delay, + }, + name=name, + reason=reason, + ) + + async def delete_messages(self, messages: Iterable[Snowflake], *, reason: str | None = None) -> None: + """|coro| + + Deletes a list of messages. This is similar to :meth:`Message.delete` + except it bulk deletes multiple messages. + + 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. + + You cannot bulk delete more than 100 messages or messages that + are older than 14 days old. + + You must have the :attr:`~Permissions.manage_messages` permission to + use this. + + Parameters + ---------- + messages: Iterable[:class:`abc.Snowflake`] + An iterable of messages denoting which ones to bulk delete. + reason: Optional[:class:`str`] + The reason for deleting the messages. Shows up on the audit log. + + Raises + ------ + ClientException + The number of messages to delete was more than 100. + Forbidden + You do not have proper permissions to delete the messages. + NotFound + If single delete, then the message was already deleted. + HTTPException + Deleting the messages failed. + """ + if not isinstance(messages, (list, tuple)): + messages = list(messages) + + if len(messages) == 0: + return # do nothing + + if len(messages) == 1: + message_id: int = messages[0].id + await self._state.http.delete_message(self.id, message_id, reason=reason) + return + + if len(messages) > 100: + raise ClientException("Can only bulk delete messages up to 100 messages") + + message_ids: SnowflakeList = [m.id for m in messages] + await self._state.http.delete_messages(self.id, message_ids, reason=reason) + + async def purge( + self, + *, + limit: int | None = 100, + check: Callable[[Message], bool] | Undefined = MISSING, + before: SnowflakeTime | None = None, + after: SnowflakeTime | None = None, + around: SnowflakeTime | None = None, + oldest_first: bool | None = False, + bulk: bool = True, + reason: str | None = None, + ) -> list[Message]: + """|coro| + + 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. + + You must have the :attr:`~Permissions.manage_messages` permission to + delete messages even if they are your own. + The :attr:`~Permissions.read_message_history` permission is + also needed to retrieve message history. + + Parameters + ---------- + limit: Optional[:class:`int`] + The number of messages to search through. This is not the number + of messages that will be deleted, though it can be. + check: Callable[[:class:`Message`], :class:`bool`] + The function used to check if a message should be deleted. + It must take a :class:`Message` as its sole parameter. + before: Optional[Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]] + Same as ``before`` in :meth:`history`. + after: Optional[Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]] + Same as ``after`` in :meth:`history`. + around: Optional[Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]] + Same as ``around`` in :meth:`history`. + oldest_first: Optional[:class:`bool`] + Same as ``oldest_first`` in :meth:`history`. + bulk: :class:`bool` + 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. + reason: Optional[:class:`str`] + The reason for deleting the messages. Shows up on the audit log. + + Returns + ------- + List[:class:`.Message`] + The list of messages that were deleted. + + Raises + ------ + Forbidden + You do not have proper permissions to do the actions required. + HTTPException + Purging the messages failed. + + Examples + -------- + + Deleting bot's messages :: + + def is_me(m): + return m.author == client.user + + + deleted = await channel.purge(limit=100, check=is_me) + await channel.send(f"Deleted {len(deleted)} message(s)") + """ + return await _purge_messages_helper( + self, + limit=limit, + check=check, + before=before, + after=after, + around=around, + oldest_first=oldest_first, + bulk=bulk, + reason=reason, + ) + + async def webhooks(self) -> list[Webhook]: + """|coro| + + Gets the list of webhooks from this channel. + + Requires :attr:`~.Permissions.manage_webhooks` permissions. + + Returns + ------- + List[:class:`Webhook`] + The webhooks for this channel. + + Raises + ------ + Forbidden + You don't have permissions to get the webhooks. + """ + + from .webhook import Webhook + + data = await self._state.http.channel_webhooks(self.id) + return [Webhook.from_state(d, state=self._state) for d in data] + + async def create_webhook(self, *, name: str, avatar: bytes | None = None, reason: str | None = None) -> Webhook: + """|coro| + + Creates a webhook for this channel. + + Requires :attr:`~.Permissions.manage_webhooks` permissions. + + .. versionchanged:: 1.1 + Added the ``reason`` keyword-only parameter. + + Parameters + ---------- + name: :class:`str` + The webhook's name. + avatar: Optional[:class:`bytes`] + A :term:`py:bytes-like object` representing the webhook's default avatar. + This operates similarly to :meth:`~ClientUser.edit`. + reason: Optional[:class:`str`] + The reason for creating this webhook. Shows up in the audit logs. + + Returns + ------- + :class:`Webhook` + The created webhook. + + Raises + ------ + HTTPException + Creating the webhook failed. + Forbidden + You do not have permissions to create a webhook. + """ + + from .webhook import Webhook + + if avatar is not None: + avatar = bytes_to_base64_data(avatar) # type: ignore + + data = await self._state.http.create_webhook(self.id, name=str(name), avatar=avatar, reason=reason) + return Webhook.from_state(data, state=self._state) + + async def follow(self, *, destination: TextChannel, reason: str | None = None) -> Webhook: + """ + Follows a channel using a webhook. + + Only news channels can be followed. + + .. note:: + + The webhook returned will not provide a token to do webhook + actions, as Discord does not provide it. + + .. versionadded:: 1.3 + + Parameters + ---------- + destination: :class:`TextChannel` + The channel you would like to follow from. + reason: Optional[:class:`str`] + The reason for following the channel. Shows up on the destination guild's audit log. + + .. versionadded:: 1.4 + + Returns + ------- + :class:`Webhook` + The created webhook. + + Raises + ------ + HTTPException + Following the channel failed. + Forbidden + You do not have the permissions to create a webhook. + """ + + from .news import NewsChannel + from .text import TextChannel + + if not isinstance(self, NewsChannel): + raise ClientException("The channel must be a news channel.") + + if not isinstance(destination, TextChannel): + raise InvalidArgument(f"Expected TextChannel received {destination.__class__.__name__}") + + from .webhook import Webhook + + data = await self._state.http.follow_webhook(self.id, webhook_channel_id=destination.id, reason=reason) + return Webhook._as_follower(data, channel=destination, user=self._state.user) + + def get_partial_message(self, message_id: int, /) -> PartialMessage: + """Creates a :class:`PartialMessage` from the message ID. + + This is useful if you want to work with a message and only have its ID without + doing an unnecessary API call. + + .. versionadded:: 1.6 + + Parameters + ---------- + message_id: :class:`int` + The message ID to create a partial message for. + + Returns + ------- + :class:`PartialMessage` + The partial message. + """ + + from .message import PartialMessage + + return PartialMessage(channel=self, id=message_id) diff --git a/discord/channel/category.py b/discord/channel/category.py new file mode 100644 index 0000000000..be1da44f10 --- /dev/null +++ b/discord/channel/category.py @@ -0,0 +1,273 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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, Any, overload + +from typing_extensions import override + +if TYPE_CHECKING: + from collections.abc import Mapping + + from ..app.state import ConnectionState + from ..guild import Guild + from ..member import Member + from ..permissions import PermissionOverwrite + from ..role import Role + from . import ForumChannel, StageChannel, TextChannel, VoiceChannel + +from ..enums import ChannelType, try_enum +from ..flags import ChannelFlags +from ..types.channel import CategoryChannel as CategoryChannelPayload +from ..utils.private import copy_doc +from .base import GuildChannel, GuildTopLevelChannel + + +def comparator(channel: GuildChannel): + # Sorts channels so voice channels (VoiceChannel, StageChannel) appear below non-voice channels + return isinstance(channel, (VoiceChannel, StageChannel)), (channel.position or -1) + + +class CategoryChannel(GuildTopLevelChannel[CategoryChannelPayload]): + """Represents a Discord channel category. + + These are useful to group channels to logical compartments. + + .. 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 category's hash. + + .. describe:: str(x) + + Returns the category's name. + + Attributes + ---------- + name: str + The category name. + guild: Guild + The guild the category belongs to. + id: int + The category channel ID. + position: int + The position in the category list. This is a number that starts at 0. e.g. the + top category is position 0. + flags: ChannelFlags + Extra features of the channel. + + .. versionadded:: 2.0 + """ + + __slots__: tuple[str, ...] = () + + @override + def __repr__(self) -> str: + return f"" + + @property + @override + def _sorting_bucket(self) -> int: + return ChannelType.category.value + + @property + def type(self) -> ChannelType: + """The channel's Discord type.""" + return try_enum(ChannelType, self._type) + + @copy_doc(GuildChannel.clone) + async def clone(self, *, name: str | None = None, reason: str | None = None) -> CategoryChannel: + return await self._clone_impl({}, name=name, reason=reason) + + @overload + async def edit( + self, + *, + name: str = ..., + position: int = ..., + overwrites: Mapping[Role | Member, PermissionOverwrite] = ..., + reason: str | None = ..., + ) -> CategoryChannel | None: ... + + @overload + async def edit(self) -> CategoryChannel | None: ... + + async def edit(self, *, reason=None, **options): + """|coro| + + Edits the channel. + + You must have the :attr:`~Permissions.manage_channels` permission to + use this. + + .. versionchanged:: 1.3 + The ``overwrites`` keyword-only parameter was added. + + .. versionchanged:: 2.0 + Edits are no longer in-place, the newly edited channel is returned instead. + + Parameters + ---------- + name: :class:`str` + The new category's name. + position: :class:`int` + The new category's position. + reason: Optional[:class:`str`] + The reason for editing this category. Shows up on the audit log. + overwrites: Dict[Union[:class:`Role`, :class:`Member`, :class:`~discord.abc.Snowflake`], :class:`PermissionOverwrite`] + The overwrites to apply to channel permissions. Useful for creating secret channels. + + Returns + ------- + Optional[:class:`.CategoryChannel`] + The newly edited category channel. If the edit was only positional + then ``None`` is returned instead. + + Raises + ------ + InvalidArgument + If position is less than 0 or greater than the number of categories. + Forbidden + You do not have permissions to edit the category. + HTTPException + Editing the category failed. + """ + + payload = await self._edit(options, reason=reason) + if payload is not None: + # the payload will always be the proper channel payload + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore + + @copy_doc(GuildTopLevelChannel.move) + async def move(self, **kwargs): + kwargs.pop("category", None) + await super().move(**kwargs) + + @property + def channels(self) -> list[GuildTopLevelChannel]: + """Returns the channels that are under this category. + + These are sorted by the official Discord UI, which places voice channels below the text channels. + """ + + ret = [c for c in self.guild.channels if c.category_id == self.id] + ret.sort(key=comparator) + return ret + + @property + def text_channels(self) -> list[TextChannel]: + """Returns the text channels that are under this category.""" + ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, TextChannel)] + ret.sort(key=lambda c: (c.position or -1, c.id)) + return ret + + @property + def voice_channels(self) -> list[VoiceChannel]: + """Returns the voice channels that are under this category.""" + ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, VoiceChannel)] + ret.sort(key=lambda c: (c.position or -1, c.id)) + return ret + + @property + def stage_channels(self) -> list[StageChannel]: + """Returns the stage channels that are under this category. + + .. versionadded:: 1.7 + """ + ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, StageChannel)] + ret.sort(key=lambda c: (c.position or -1, c.id)) + return ret + + @property + def forum_channels(self) -> list[ForumChannel]: + """Returns the forum channels that are under this category. + + .. versionadded:: 2.0 + """ + ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, ForumChannel)] + ret.sort(key=lambda c: (c.position or -1, c.id)) + return ret + + async def create_text_channel(self, name: str, **options: Any) -> TextChannel: + """|coro| + + A shortcut method to :meth:`Guild.create_text_channel` to create a :class:`TextChannel` in the category. + + Returns + ------- + :class:`TextChannel` + The channel that was just created. + """ + return await self.guild.create_text_channel(name, category=self, **options) + + async def create_voice_channel(self, name: str, **options: Any) -> VoiceChannel: + """|coro| + + A shortcut method to :meth:`Guild.create_voice_channel` to create a :class:`VoiceChannel` in the category. + + Returns + ------- + :class:`VoiceChannel` + The channel that was just created. + """ + return await self.guild.create_voice_channel(name, category=self, **options) + + async def create_stage_channel(self, name: str, **options: Any) -> StageChannel: + """|coro| + + A shortcut method to :meth:`Guild.create_stage_channel` to create a :class:`StageChannel` in the category. + + .. versionadded:: 1.7 + + Returns + ------- + :class:`StageChannel` + The channel that was just created. + """ + return await self.guild.create_stage_channel(name, category=self, **options) + + async def create_forum_channel(self, name: str, **options: Any) -> ForumChannel: + """|coro| + + A shortcut method to :meth:`Guild.create_forum_channel` to create a :class:`ForumChannel` in the category. + + .. versionadded:: 2.0 + + Returns + ------- + :class:`ForumChannel` + The channel that was just created. + """ + return await self.guild.create_forum_channel(name, category=self, **options) diff --git a/discord/channel.py b/discord/channel/channel.py.old similarity index 93% rename from discord/channel.py rename to discord/channel/channel.py.old index 57e894b8c4..00b149b748 100644 --- a/discord/channel.py +++ b/discord/channel/channel.py.old @@ -39,12 +39,14 @@ overload, ) +from typing_extensions import override + import discord.abc -from . import utils -from .asset import Asset -from .emoji import GuildEmoji -from .enums import ( +from .. import utils +from ..asset import Asset +from ..emoji import GuildEmoji +from ..enums import ( ChannelType, EmbeddedActivity, InviteTarget, @@ -55,21 +57,21 @@ VoiceRegion, try_enum, ) -from .enums import ThreadArchiveDuration as ThreadArchiveDurationEnum -from .errors import ClientException, InvalidArgument -from .file import File -from .flags import ChannelFlags, MessageFlags -from .invite import Invite -from .iterators import ArchivedThreadIterator -from .mixins import Hashable -from .object import Object -from .partial_emoji import PartialEmoji, _EmojiTag -from .permissions import PermissionOverwrite, Permissions -from .soundboard import PartialSoundboardSound, SoundboardSound -from .stage_instance import StageInstance -from .threads import Thread -from .utils import MISSING -from .utils.private import bytes_to_base64_data, copy_doc, get_as_snowflake +from ..enums import ThreadArchiveDuration as ThreadArchiveDurationEnum +from ..errors import ClientException, InvalidArgument +from ..file import File +from ..flags import ChannelFlags, MessageFlags +from ..invite import Invite +from ..iterators import ArchivedThreadIterator +from ..mixins import Hashable +from ..object import Object +from ..partial_emoji import PartialEmoji, _EmojiTag +from ..permissions import PermissionOverwrite, Permissions +from ..soundboard import PartialSoundboardSound, SoundboardSound +from ..stage_instance import StageInstance +from .thread import Thread +from ..utils import MISSING +from ..utils.private import bytes_to_base64_data, copy_doc, get_as_snowflake __all__ = ( "TextChannel", @@ -86,112 +88,30 @@ ) if TYPE_CHECKING: - from .abc import Snowflake, SnowflakeTime - from .app.state import ConnectionState - from .embeds import Embed - from .guild import Guild - from .guild import GuildChannel as GuildChannelType - from .member import Member, VoiceState - from .mentions import AllowedMentions - from .message import EmojiInputType, Message, PartialMessage - from .role import Role - from .sticker import GuildSticker, StickerItem - from .types.channel import CategoryChannel as CategoryChannelPayload - from .types.channel import DMChannel as DMChannelPayload - from .types.channel import ForumChannel as ForumChannelPayload - from .types.channel import ForumTag as ForumTagPayload - from .types.channel import GroupDMChannel as GroupChannelPayload - from .types.channel import StageChannel as StageChannelPayload - from .types.channel import TextChannel as TextChannelPayload - from .types.channel import VoiceChannel as VoiceChannelPayload - from .types.channel import VoiceChannelEffectSendEvent as VoiceChannelEffectSend - from .types.snowflake import SnowflakeList - from .types.threads import ThreadArchiveDuration - from .ui.view import View - from .user import BaseUser, ClientUser, User - from .webhook import Webhook - - -class ForumTag(Hashable): - """Represents a forum tag that can be added to a thread inside a :class:`ForumChannel` - . - .. versionadded:: 2.3 - - .. container:: operations - - .. describe:: x == y - - Checks if two forum tags are equal. - - .. describe:: x != y - - Checks if two forum tags are not equal. - - .. describe:: hash(x) - - Returns the forum tag's hash. - - .. describe:: str(x) - - Returns the forum tag's name. - - Attributes - ---------- - id: :class:`int` - The tag ID. - Note that if the object was created manually then this will be ``0``. - name: :class:`str` - The name of the tag. Can only be up to 20 characters. - moderated: :class:`bool` - Whether this tag can only be added or removed by a moderator with - the :attr:`~Permissions.manage_threads` permission. - emoji: :class:`PartialEmoji` - The emoji that is used to represent this tag. - Note that if the emoji is a custom emoji, it will *not* have name information. - """ - - __slots__ = ("name", "id", "moderated", "emoji") - - def __init__(self, *, name: str, emoji: EmojiInputType, moderated: bool = False) -> None: - self.name: str = name - self.id: int = 0 - self.moderated: bool = moderated - self.emoji: PartialEmoji - if isinstance(emoji, _EmojiTag): - self.emoji = emoji._to_partial() - elif isinstance(emoji, str): - self.emoji = PartialEmoji.from_str(emoji) - else: - raise TypeError(f"emoji must be a GuildEmoji, PartialEmoji, or str and not {emoji.__class__!r}") - - def __repr__(self) -> str: - return f"" - - def __str__(self) -> str: - return self.name - - @classmethod - def from_data(cls, *, state: ConnectionState, data: ForumTagPayload) -> ForumTag: - self = cls.__new__(cls) - self.name = data["name"] - self.id = int(data["id"]) - self.moderated = data.get("moderated", False) - - emoji_name = data["emoji_name"] or "" - emoji_id = get_as_snowflake(data, "emoji_id") or None - self.emoji = PartialEmoji.with_state(state=state, name=emoji_name, id=emoji_id) - return self - - def to_dict(self) -> dict[str, Any]: - payload: dict[str, Any] = { - "name": self.name, - "moderated": self.moderated, - } | self.emoji._to_forum_reaction_payload() - - if self.id: - payload["id"] = self.id - - return payload + from ..abc import Snowflake, SnowflakeTime + from ..app.state import ConnectionState + from ..embeds import Embed + from ..guild import Guild + from ..guild import GuildChannel as GuildChannelType + from ..member import Member, VoiceState + from ..mentions import AllowedMentions + from ..message import EmojiInputType, Message, PartialMessage + from ..role import Role + from ..sticker import GuildSticker, StickerItem + from ..types.channel import CategoryChannel as CategoryChannelPayload + from ..types.channel import DMChannel as DMChannelPayload + from ..types.channel import ForumChannel as ForumChannelPayload + from ..types.channel import ForumTag as ForumTagPayload + from ..types.channel import GroupDMChannel as GroupChannelPayload + from ..types.channel import StageChannel as StageChannelPayload + from ..types.channel import TextChannel as TextChannelPayload + from ..types.channel import VoiceChannel as VoiceChannelPayload + from ..types.channel import VoiceChannelEffectSendEvent as VoiceChannelEffectSend + from ..types.snowflake import SnowflakeList + from ..types.threads import ThreadArchiveDuration + from ..ui.view import View + from ..user import BaseUser, ClientUser, User + from ..webhook import Webhook class _TextChannel(discord.abc.GuildChannel, Hashable): @@ -219,13 +139,31 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): def __init__( self, *, - state: ConnectionState, + id: int, guild: Guild, - data: TextChannelPayload | ForumChannelPayload, + state: ConnectionState, ): + """Initialize with permanent attributes only.""" self._state: ConnectionState = state - self.id: int = int(data["id"]) - self.guild = guild + self.id: int = id + self.guild: Guild = guild + + @classmethod + async def _from_data( + cls, + *, + data: TextChannelPayload | ForumChannelPayload, + state: ConnectionState, + guild: Guild, + ): + """Create channel instance from API payload.""" + self = cls( + id=int(data["id"]), + guild=guild, + state=state, + ) + await self._update(data) + return self @property def _repr_attrs(self) -> tuple[str, ...]: @@ -237,6 +175,7 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__} {joined}>" async def _update(self, data: TextChannelPayload | ForumChannelPayload) -> None: + """Update mutable attributes from API payload.""" # This data will always exist self.name: str = data["name"] self.category_id: int | None = get_as_snowflake(data, "parent_id") @@ -659,7 +598,7 @@ def archived_threads( ) -class TextChannel(discord.abc.Messageable, _TextChannel): +class TextChannel(discord.abc.Messageable, ForumChannel): """Represents a Discord text channel. .. container:: operations @@ -723,15 +662,33 @@ class TextChannel(discord.abc.Messageable, _TextChannel): .. versionadded:: 2.3 """ - def __init__(self, *, state: ConnectionState, guild: Guild, data: TextChannelPayload): - super().__init__(state=state, guild=guild, data=data) + def __init__(self, *, id: int, guild: Guild, state: ConnectionState): + """Initialize with permanent attributes only.""" + super().__init__(id=id, guild=guild, state=state) + + @classmethod + async def _from_data( + cls, + *, + data: TextChannelPayload, + state: ConnectionState, + guild: Guild, + ): + """Create channel instance from API payload.""" + self = cls( + id=int(data["id"]), + guild=guild, + state=state, + ) + await self._update(data) + return self @property def _repr_attrs(self) -> tuple[str, ...]: return super()._repr_attrs + ("news",) async def _update(self, data: TextChannelPayload) -> None: - super()._update(data) + await super()._update(data) async def _get_channel(self) -> TextChannel: return self @@ -837,7 +794,7 @@ async def edit(self, *, reason=None, **options): payload = await self._edit(options, reason=reason) if payload is not None: # the payload will always be the proper channel payload - return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore async def create_thread( self, @@ -1002,11 +959,31 @@ class ForumChannel(_TextChannel): .. versionadded:: 2.5 """ - def __init__(self, *, state: ConnectionState, guild: Guild, data: ForumChannelPayload): - super().__init__(state=state, guild=guild, data=data) + def __init__(self, *, id: int, guild: Guild, state: ConnectionState): + """Initialize with permanent attributes only.""" + super().__init__(id=id, guild=guild, state=state) + @classmethod + @override + async def _from_data( + cls, + *, + data: ForumChannelPayload, + state: ConnectionState, + guild: Guild, + ): + """Create channel instance from API payload.""" + self = cls( + id=int(data["id"]), + guild=guild, + state=state, + ) + await self._update(data) + return self + + @override async def _update(self, data: ForumChannelPayload) -> None: - super()._update(data) + await super()._update(data) self.available_tags: list[ForumTag] = [ ForumTag.from_data(state=self._state, data=tag) for tag in (data.get("available_tags") or []) ] @@ -1154,7 +1131,7 @@ async def edit(self, *, reason=None, **options): payload = await self._edit(options, reason=reason) if payload is not None: # the payload will always be the proper channel payload - return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore async def create_thread( self, @@ -1520,7 +1497,7 @@ async def edit(self, *, reason=None, **options): payload = await self._edit(options, reason=reason) if payload is not None: # the payload will always be the proper channel payload - return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): @@ -1545,14 +1522,34 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha def __init__( self, *, - state: ConnectionState, + id: int, guild: Guild, - data: VoiceChannelPayload | StageChannelPayload, + state: ConnectionState, + type: int | ChannelType, ): + """Initialize with permanent attributes only.""" self._state: ConnectionState = state - self.id: int = int(data["id"]) + self.id: int = id self.guild = guild - self._update(data) + self._type: int = int(type) + + @classmethod + async def _from_data( + cls, + *, + data: VoiceChannelPayload | StageChannelPayload, + state: ConnectionState, + guild: Guild, + ): + """Create channel instance from API payload.""" + self = cls( + id=int(data["id"]), + guild=guild, + state=state, + type=data["type"], + ) + await self._update(data) + return self def _get_voice_client_key(self) -> tuple[int, str]: return self.guild.id, "guild_id" @@ -1704,15 +1701,35 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): def __init__( self, *, - state: ConnectionState, + id: int, guild: Guild, - data: VoiceChannelPayload, + state: ConnectionState, + type: int | ChannelType, ): + """Initialize with permanent attributes only.""" + super().__init__(id=id, guild=guild, state=state, type=type) self.status: str | None = None - super().__init__(state=state, guild=guild, data=data) + + @classmethod + async def _from_data( + cls, + *, + data: VoiceChannelPayload, + state: ConnectionState, + guild: Guild, + ): + """Create channel instance from API payload.""" + self = cls( + id=int(data["id"]), + guild=guild, + state=state, + type=data["type"], + ) + await self._update(data) + return self async def _update(self, data: VoiceChannelPayload): - super()._update(data) + await super()._update(data) if data.get("status"): self.status = data.get("status") @@ -2084,7 +2101,7 @@ async def edit(self, *, reason=None, **options): payload = await self._edit(options, reason=reason) if payload is not None: # the payload will always be the proper channel payload - return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore async def create_activity_invite(self, activity: EmbeddedActivity | int, **kwargs) -> Invite: """|coro| @@ -2252,7 +2269,7 @@ class StageChannel(discord.abc.Messageable, VocalGuildChannel): __slots__ = ("topic",) async def _update(self, data: StageChannelPayload) -> None: - super()._update(data) + await super()._update(data) self.topic = data.get("topic") def __repr__(self) -> str: @@ -2734,7 +2751,7 @@ async def edit(self, *, reason=None, **options): payload = await self._edit(options, reason=reason) if payload is not None: # the payload will always be the proper channel payload - return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore class CategoryChannel(discord.abc.GuildChannel, Hashable): @@ -2778,22 +2795,30 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable): .. versionadded:: 2.0 """ - __slots__ = ( - "name", - "id", - "guild", - "_state", - "position", - "_overwrites", - "category_id", - "flags", - ) + __slots__ = ("name", "id", "guild", "_state", "position", "_overwrites", "category_id", "flags", "_type") - def __init__(self, *, state: ConnectionState, guild: Guild, data: CategoryChannelPayload): + def __init__(self, *, id: int, guild: Guild, state: ConnectionState) -> None: + """Initialize with permanent attributes only.""" self._state: ConnectionState = state - self.id: int = int(data["id"]) + self.id: int = id self.guild = guild - self._update(data) + + @classmethod + async def _from_data( + cls, + *, + data: CategoryChannelPayload, + state: ConnectionState, + guild: Guild, + ): + """Create channel instance from API payload.""" + self = cls( + id=int(data["id"]), + guild=guild, + state=state, + ) + await self._update(data) + return self def __repr__(self) -> str: return f"" @@ -2801,6 +2826,7 @@ def __repr__(self) -> str: async def _update(self, data: CategoryChannelPayload) -> None: # This data will always exist self.name: str = data["name"] + self._type: int = data["type"] self.category_id: int | None = get_as_snowflake(data, "parent_id") # This data may be missing depending on how this object is being created/updated @@ -2816,7 +2842,7 @@ def _sorting_bucket(self) -> int: @property def type(self) -> ChannelType: """The channel's Discord type.""" - return ChannelType.category + return try_enum(ChannelType, self._type) @copy_doc(discord.abc.GuildChannel.clone) async def clone(self, *, name: str | None = None, reason: str | None = None) -> CategoryChannel: @@ -2879,7 +2905,7 @@ async def edit(self, *, reason=None, **options): payload = await self._edit(options, reason=reason) if payload is not None: # the payload will always be the proper channel payload - return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore @copy_doc(discord.abc.GuildChannel.move) async def move(self, **kwargs): @@ -3023,21 +3049,28 @@ class DMChannel(discord.abc.Messageable, Hashable): The direct message channel ID. """ - __slots__ = ("id", "recipient", "me", "_state") + __slots__ = ("id", "recipient", "me", "_state", "_type") - def __init__(self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload): + def __init__(self, *, me: ClientUser, state: ConnectionState, id: int) -> None: + """Initialize with permanent attributes only.""" self._state: ConnectionState = state - self._recipients = data.get("recipients") self.recipient: User | None = None self.me: ClientUser = me - self.id: int = int(data["id"]) - # there shouldn't be any point in time where a DM channel - # is made without the event loop having started - asyncio.create_task(self._load()) + self.id: int = id + + @classmethod + async def _from_data(cls, *, data: DMChannelPayload, state: ConnectionState, me: ClientUser) -> DMChannel: + """Create channel instance from API payload.""" + self = cls(me=me, state=state, id=int(data["id"])) + await self._update(data) + return self - async def _load(self) -> None: - if r := self._recipients: - self.recipient = await self._state.store_user(r[0]) + async def _update(self, data: DMChannelPayload) -> None: + """Update mutable attributes from API payload.""" + recipients = data.get("recipients", []) + self._type = data["type"] + if recipients: + self.recipient = await self._state.store_user(recipients[0]) async def _get_channel(self): return self @@ -3063,7 +3096,7 @@ def _from_message(cls: type[DMC], state: ConnectionState, channel_id: int) -> DM @property def type(self) -> ChannelType: """The channel's Discord type.""" - return ChannelType.private + return try_enum(ChannelType, self._type) @property def jump_url(self) -> str: @@ -3327,7 +3360,7 @@ class PartialMessageable(discord.abc.Messageable, Hashable): The channel type associated with this partial messageable, if given. """ - def __init__(self, state: ConnectionState, id: int, type: ChannelType | None = None): + def __init__(self, state: ConnectionState, id: int): self._state: ConnectionState = state self._channel: Object = Object(id=id) self.id: int = id @@ -3437,57 +3470,3 @@ def __init__( else None ) self.data = data - - -def _guild_channel_factory(channel_type: int): - value = try_enum(ChannelType, channel_type) - if value is ChannelType.text: - return TextChannel, value - elif value is ChannelType.voice: - return VoiceChannel, value - elif value is ChannelType.category: - return CategoryChannel, value - elif value is ChannelType.news: - return TextChannel, value - elif value is ChannelType.stage_voice: - return StageChannel, value - elif value is ChannelType.directory: - return None, value # todo: Add DirectoryChannel when applicable - elif value is ChannelType.forum: - return ForumChannel, value - elif value is ChannelType.media: - return MediaChannel, value - else: - return None, value - - -def _channel_factory(channel_type: int): - cls, value = _guild_channel_factory(channel_type) - if value is ChannelType.private: - return DMChannel, value - elif value is ChannelType.group: - return GroupChannel, value - else: - return cls, value - - -def _threaded_channel_factory(channel_type: int): - cls, value = _channel_factory(channel_type) - if value in ( - ChannelType.private_thread, - ChannelType.public_thread, - ChannelType.news_thread, - ): - return Thread, value - return cls, value - - -def _threaded_guild_channel_factory(channel_type: int): - cls, value = _guild_channel_factory(channel_type) - if value in ( - ChannelType.private_thread, - ChannelType.public_thread, - ChannelType.news_thread, - ): - return Thread, value - return cls, value diff --git a/discord/channel/dm.py b/discord/channel/dm.py new file mode 100644 index 0000000000..626ad439f2 --- /dev/null +++ b/discord/channel/dm.py @@ -0,0 +1,106 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 collections.abc import Collection +from typing import TYPE_CHECKING + +from typing_extensions import override + +from ..abc import Messageable, Snowflake +from ..asset import Asset +from ..permissions import Permissions +from ..types.channel import DMChannel as DMChannelPayload +from ..types.channel import GroupDMChannel as GroupDMChannelPayload +from .base import BaseChannel, P + +if TYPE_CHECKING: + from ..app.state import ConnectionState + from ..message import Message + from ..user import User + + +class DMChannel(BaseChannel[DMChannelPayload], Messageable): + __slots__: tuple[str, ...] = ("last_message", "recipient") + + def __init__(self, id: int, state: "ConnectionState") -> None: + super().__init__(id, state) + self.recipient: User | None = None + self.last_message: Message | None = None + + @override + async def _update(self, data: DMChannelPayload) -> None: + await super()._update(data) + if last_message_id := data.get("last_message_id", None): + self.last_message = await self._state.cache.get_message(int(last_message_id)) + if recipients := data.get("recipients"): + self.recipient = await self._state.cache.store_user(recipients[0]) + + @override + def __repr__(self) -> str: + return f"" + + @property + @override + def jump_url(self) -> str: + """Returns a URL that allows the client to jump to the channel.""" + return f"https://discord.com/channels/@me/{self.id}" + + +class GroupDMChannel(BaseChannel[GroupDMChannelPayload], Messageable): + __slots__: tuple[str, ...] = ("recipients", "icon_hash", "owner", "name") + + def __init__(self, id: int, state: "ConnectionState") -> None: + super().__init__(id, state) + self.recipients: Collection[User] = set() + self.icon_hash: str | None = None + self.owner: User | None = None + + @override + async def _update(self, data: GroupDMChannelPayload) -> None: + await super()._update(data) + self.name: str = data["name"] + if recipients := data.get("recipients"): + self.recipients = {await self._state.cache.store_user(recipient_data) for recipient_data in recipients} + if icon_hash := data.get("icon"): + self.icon_hash = icon_hash + if owner_id := data.get("owner_id"): + self.owner = await self._state.cache.get_user(int(owner_id)) + + @override + def __repr__(self) -> str: + return f"" + + @property + @override + def jump_url(self) -> str: + """Returns a URL that allows the client to jump to the channel.""" + return f"https://discord.com/channels/@me/{self.id}" + + @property + def icon(self) -> Asset | None: + """Returns the channel's icon asset if available.""" + if self.icon_hash is None: + return None + return Asset._from_icon(self._state, self.id, self.icon_hash, path="channel") diff --git a/discord/channel/forum.py b/discord/channel/forum.py new file mode 100644 index 0000000000..39fad7f36d --- /dev/null +++ b/discord/channel/forum.py @@ -0,0 +1,210 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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, Mapping, overload + +from typing_extensions import Self, override + +from ..enums import ChannelType, SortOrder +from ..flags import ChannelFlags +from ..utils import MISSING, Undefined +from .base import GuildPostableChannel + +if TYPE_CHECKING: + from ..abc import Snowflake + from ..emoji import GuildEmoji + from ..member import Member + from ..permissions import PermissionOverwrite + from ..role import Role + from ..types.channel import ForumChannel as ForumChannelPayload + from .category import CategoryChannel + from .channel import ForumTag + +__all__ = ("ForumChannel",) + + +class ForumChannel(GuildPostableChannel["ForumChannelPayload"]): + """Represents a Discord forum 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 + ---------- + id: :class:`int` + The channel's ID. + name: :class:`str` + The channel's name. + guild: :class:`Guild` + The guild the channel belongs to. + topic: :class:`str` | None + The channel's topic/guidelines. ``None`` if it doesn't exist. + category_id: :class:`int` | None + 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. + nsfw: :class:`bool` + Whether the channel is marked as NSFW. + slowmode_delay: :class:`int` + The number of seconds a member must wait between creating posts + in this channel. A value of `0` denotes that it is disabled. + last_message_id: :class:`int` | None + The last message ID sent to this channel. It may not point to an existing or valid message. + default_auto_archive_duration: :class:`int` + The default auto archive duration in minutes for posts created in this channel. + default_thread_slowmode_delay: :class:`int` | None + The initial slowmode delay to set on newly created posts in this channel. + available_tags: list[:class:`ForumTag`] + The set of tags that can be used in this forum channel. + default_sort_order: :class:`SortOrder` | None + The default sort order type used to order posts in this channel. + default_reaction_emoji: :class:`str` | :class:`GuildEmoji` | None + The default forum reaction emoji. + + .. versionadded:: 3.0 + """ + + __slots__: tuple[str, ...] = () + + @property + @override + def _sorting_bucket(self) -> int: + return ChannelType.forum.value + + def __repr__(self) -> str: + attrs = [ + ("id", self.id), + ("name", self.name), + ("position", self.position), + ("nsfw", self.nsfw), + ("category_id", self.category_id), + ] + joined = " ".join(f"{k}={v!r}" for k, v in attrs) + return f"" + + @overload + async def edit( + self, + *, + name: str | Undefined = MISSING, + topic: str | Undefined = MISSING, + position: int | Undefined = MISSING, + nsfw: bool | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + category: CategoryChannel | None | Undefined = MISSING, + slowmode_delay: int | Undefined = MISSING, + default_auto_archive_duration: int | Undefined = MISSING, + default_thread_slowmode_delay: int | Undefined = MISSING, + default_sort_order: SortOrder | Undefined = MISSING, + default_reaction_emoji: GuildEmoji | int | str | None | Undefined = MISSING, + available_tags: list[ForumTag] | Undefined = MISSING, + require_tag: bool | Undefined = MISSING, + overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] | Undefined = MISSING, + reason: str | None = None, + ) -> Self: ... + + @overload + async def edit(self) -> Self: ... + + async def edit(self, *, reason: str | None = None, **options) -> Self: + """|coro| + + Edits the forum channel. + + You must have :attr:`~Permissions.manage_channels` permission to use this. + + Parameters + ---------- + name: :class:`str` + The new channel name. + topic: :class:`str` + The new channel's topic/guidelines. + position: :class:`int` + The new channel's position. + nsfw: :class:`bool` + Whether the channel should be marked as NSFW. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing category. + category: :class:`CategoryChannel` | None + The new category for this channel. Can be ``None`` to remove the category. + slowmode_delay: :class:`int` + Specifies the slowmode rate limit for users in this channel, in seconds. + A value of ``0`` disables slowmode. The maximum value possible is ``21600``. + default_auto_archive_duration: :class:`int` + The new default auto archive duration in minutes for posts created in this channel. + Must be one of ``60``, ``1440``, ``4320``, or ``10080``. + default_thread_slowmode_delay: :class:`int` + The new default slowmode delay in seconds for posts created in this channel. + default_sort_order: :class:`SortOrder` + The default sort order type to use to order posts in this channel. + default_reaction_emoji: :class:`GuildEmoji` | :class:`int` | :class:`str` | None + The default reaction emoji for posts. + Can be a unicode emoji or a custom emoji. + available_tags: list[:class:`ForumTag`] + The set of tags that can be used in this channel. Must be less than ``20``. + require_tag: :class:`bool` + Whether a tag should be required to be specified when creating a post in this channel. + overwrites: Mapping[:class:`Role` | :class:`Member` | :class:`~discord.abc.Snowflake`, :class:`PermissionOverwrite`] + The overwrites to apply to channel permissions. + reason: :class:`str` | None + The reason for editing this channel. Shows up on the audit log. + + Returns + ------- + :class:`.ForumChannel` + The newly edited forum channel. + + Raises + ------ + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + """ + if "require_tag" in options: + options["flags"] = ChannelFlags._from_value(self.flags.value) + options["flags"].require_tag = options.pop("require_tag") + + payload = await self._edit(options, reason=reason) + if payload is not None: + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore + return self diff --git a/discord/channel/media.py b/discord/channel/media.py new file mode 100644 index 0000000000..64b4ea5620 --- /dev/null +++ b/discord/channel/media.py @@ -0,0 +1,227 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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, Mapping, overload + +from typing_extensions import Self, override + +from ..enums import ChannelType, SortOrder +from ..flags import ChannelFlags +from ..utils import MISSING, Undefined +from .base import GuildPostableChannel + +if TYPE_CHECKING: + from ..abc import Snowflake + from ..emoji import GuildEmoji + from ..member import Member + from ..permissions import PermissionOverwrite + from ..role import Role + from ..types.channel import MediaChannel as MediaChannelPayload + from .category import CategoryChannel + from .channel import ForumTag + +__all__ = ("MediaChannel",) + + +class MediaChannel(GuildPostableChannel["MediaChannelPayload"]): + """Represents a Discord media channel. + + .. versionadded:: 2.7 + + .. 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 + ---------- + id: :class:`int` + The channel's ID. + name: :class:`str` + The channel's name. + guild: :class:`Guild` + The guild the channel belongs to. + topic: :class:`str` | None + The channel's topic/guidelines. ``None`` if it doesn't exist. + category_id: :class:`int` | None + 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. + nsfw: :class:`bool` + Whether the channel is marked as NSFW. + slowmode_delay: :class:`int` + The number of seconds a member must wait between creating posts + in this channel. A value of `0` denotes that it is disabled. + last_message_id: :class:`int` | None + The last message ID sent to this channel. It may not point to an existing or valid message. + default_auto_archive_duration: :class:`int` + The default auto archive duration in minutes for posts created in this channel. + default_thread_slowmode_delay: :class:`int` | None + The initial slowmode delay to set on newly created posts in this channel. + available_tags: list[:class:`ForumTag`] + The set of tags that can be used in this media channel. + default_sort_order: :class:`SortOrder` | None + The default sort order type used to order posts in this channel. + default_reaction_emoji: :class:`str` | :class:`GuildEmoji` | None + The default reaction emoji. + + .. versionadded:: 3.0 + """ + + __slots__: tuple[str, ...] = () + + @property + @override + def _sorting_bucket(self) -> int: + return ChannelType.media.value + + @property + def media_download_options_hidden(self) -> bool: + """Whether media download options are hidden in this media channel. + + .. versionadded:: 2.7 + """ + return self.flags.hide_media_download_options + + def __repr__(self) -> str: + attrs = [ + ("id", self.id), + ("name", self.name), + ("position", self.position), + ("nsfw", self.nsfw), + ("category_id", self.category_id), + ] + joined = " ".join(f"{k}={v!r}" for k, v in attrs) + return f"" + + @overload + async def edit( + self, + *, + name: str | Undefined = MISSING, + topic: str | Undefined = MISSING, + position: int | Undefined = MISSING, + nsfw: bool | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + category: CategoryChannel | None | Undefined = MISSING, + slowmode_delay: int | Undefined = MISSING, + default_auto_archive_duration: int | Undefined = MISSING, + default_thread_slowmode_delay: int | Undefined = MISSING, + default_sort_order: SortOrder | Undefined = MISSING, + default_reaction_emoji: GuildEmoji | int | str | None | Undefined = MISSING, + available_tags: list[ForumTag] | Undefined = MISSING, + require_tag: bool | Undefined = MISSING, + hide_media_download_options: bool | Undefined = MISSING, + overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] | Undefined = MISSING, + reason: str | None = None, + ) -> Self: ... + + @overload + async def edit(self) -> Self: ... + + async def edit(self, *, reason: str | None = None, **options) -> Self: + """|coro| + + Edits the media channel. + + You must have :attr:`~Permissions.manage_channels` permission to use this. + + Parameters + ---------- + name: :class:`str` + The new channel name. + topic: :class:`str` + The new channel's topic/guidelines. + position: :class:`int` + The new channel's position. + nsfw: :class:`bool` + Whether the channel should be marked as NSFW. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing category. + category: :class:`CategoryChannel` | None + The new category for this channel. Can be ``None`` to remove the category. + slowmode_delay: :class:`int` + Specifies the slowmode rate limit for users in this channel, in seconds. + A value of ``0`` disables slowmode. The maximum value possible is ``21600``. + default_auto_archive_duration: :class:`int` + The new default auto archive duration in minutes for posts created in this channel. + Must be one of ``60``, ``1440``, ``4320``, or ``10080``. + default_thread_slowmode_delay: :class:`int` + The new default slowmode delay in seconds for posts created in this channel. + default_sort_order: :class:`SortOrder` + The default sort order type to use to order posts in this channel. + default_reaction_emoji: :class:`GuildEmoji` | :class:`int` | :class:`str` | None + The default reaction emoji for posts. + Can be a unicode emoji or a custom emoji. + available_tags: list[:class:`ForumTag`] + The set of tags that can be used in this channel. Must be less than ``20``. + require_tag: :class:`bool` + Whether a tag should be required to be specified when creating a post in this channel. + hide_media_download_options: :class:`bool` + Whether to hide the media download options in this media channel. + overwrites: Mapping[:class:`Role` | :class:`Member` | :class:`~discord.abc.Snowflake`, :class:`PermissionOverwrite`] + The overwrites to apply to channel permissions. + reason: :class:`str` | None + The reason for editing this channel. Shows up on the audit log. + + Returns + ------- + :class:`.MediaChannel` + The newly edited media channel. + + Raises + ------ + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + """ + # Handle require_tag flag + if "require_tag" in options or "hide_media_download_options" in options: + options["flags"] = ChannelFlags._from_value(self.flags.value) + if "require_tag" in options: + options["flags"].require_tag = options.pop("require_tag") + if "hide_media_download_options" in options: + options["flags"].hide_media_download_options = options.pop("hide_media_download_options") + + payload = await self._edit(options, reason=reason) + if payload is not None: + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore + return self diff --git a/discord/channel/news.py b/discord/channel/news.py new file mode 100644 index 0000000000..a9dbc299d2 --- /dev/null +++ b/discord/channel/news.py @@ -0,0 +1,283 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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, Mapping + +from typing_extensions import Self, override + +from ..enums import ChannelType +from ..utils import MISSING, Undefined +from .base import GuildMessageableChannel, GuildThreadableChannel, GuildTopLevelChannel + +if TYPE_CHECKING: + from ..abc import Snowflake + from ..member import Member + from ..permissions import PermissionOverwrite + from ..role import Role + from ..types.channel import NewsChannel as NewsChannelPayload + from ..types.channel import TextChannel as TextChannelPayload + from .category import CategoryChannel + from .text import TextChannel + from .thread import Thread + +__all__ = ("NewsChannel",) + + +class NewsChannel( + GuildTopLevelChannel["NewsChannelPayload"], + GuildMessageableChannel, + GuildThreadableChannel, +): + """Represents a Discord guild news/announcement 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 + ---------- + id: :class:`int` + The channel's ID. + name: :class:`str` + The channel's name. + guild: :class:`Guild` + The guild the channel belongs to. + topic: :class:`str` | None + The channel's topic. ``None`` if it isn't set. + category_id: :class:`int` | None + 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. + nsfw: :class:`bool` + Whether the channel is marked as NSFW. + 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. + last_message_id: :class:`int` | None + The last message ID of the message sent to this channel. It may + *not* point to an existing or valid message. + default_auto_archive_duration: :class:`int` + The default auto archive duration in minutes for threads created in this channel. + + .. versionadded:: 3.0 + """ + + __slots__: tuple[str, ...] = ( + "topic", + "nsfw", + "slowmode_delay", + "last_message_id", + "default_auto_archive_duration", + "default_thread_slowmode_delay", + ) + + @property + @override + def _sorting_bucket(self) -> int: + return ChannelType.news.value + + def __repr__(self) -> str: + attrs = [ + ("id", self.id), + ("name", self.name), + ("position", self.position), + ("nsfw", self.nsfw), + ("category_id", self.category_id), + ] + joined = " ".join(f"{k}={v!r}" for k, v in attrs) + return f"" + + async def edit( + self, + *, + name: str | Undefined = MISSING, + topic: str | Undefined = MISSING, + position: int | Undefined = MISSING, + nsfw: bool | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + category: CategoryChannel | None | Undefined = MISSING, + slowmode_delay: int | Undefined = MISSING, + default_auto_archive_duration: int | Undefined = MISSING, + default_thread_slowmode_delay: int | Undefined = MISSING, + type: ChannelType | Undefined = MISSING, + overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] | Undefined = MISSING, + reason: str | None = None, + ) -> Self | TextChannel: + """|coro| + + Edits the channel. + + You must have :attr:`~Permissions.manage_channels` permission to use this. + + Parameters + ---------- + name: :class:`str` + The new channel name. + topic: :class:`str` + The new channel's topic. + position: :class:`int` + The new channel's position. + nsfw: :class:`bool` + Whether the channel is marked as NSFW. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing category. + category: :class:`CategoryChannel` | None + The new category for this channel. Can be ``None`` to remove the category. + slowmode_delay: :class:`int` + Specifies the slowmode rate limit for user in this channel, in seconds. + default_auto_archive_duration: :class:`int` + The new default auto archive duration in minutes for threads created in this channel. + default_thread_slowmode_delay: :class:`int` + The new default slowmode delay in seconds for threads created in this channel. + type: :class:`ChannelType` + Change the type of this news channel. Only conversion between text and news is supported. + overwrites: Mapping[:class:`Role` | :class:`Member` | :class:`~discord.abc.Snowflake`, :class:`PermissionOverwrite`] + The overwrites to apply to channel permissions. + reason: :class:`str` | None + The reason for editing this channel. Shows up on the audit log. + + Returns + ------- + :class:`.NewsChannel` | :class:`.TextChannel` + The newly edited channel. If type was changed, the appropriate channel type is returned. + + Raises + ------ + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + """ + options = {} + if name is not MISSING: + options["name"] = name + if topic is not MISSING: + options["topic"] = topic + if position is not MISSING: + options["position"] = position + if nsfw is not MISSING: + options["nsfw"] = nsfw + if sync_permissions is not MISSING: + options["sync_permissions"] = sync_permissions + if category is not MISSING: + options["category"] = category + if slowmode_delay is not MISSING: + options["slowmode_delay"] = slowmode_delay + if default_auto_archive_duration is not MISSING: + options["default_auto_archive_duration"] = default_auto_archive_duration + if default_thread_slowmode_delay is not MISSING: + options["default_thread_slowmode_delay"] = default_thread_slowmode_delay + if type is not MISSING: + options["type"] = type + if overwrites is not MISSING: + options["overwrites"] = overwrites + + payload = await self._edit(options, reason=reason) + if payload is not None: + if payload.get("type") == ChannelType.text.value: + from .text import TextChannel + + return await TextChannel._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore + + async def create_thread( + self, + *, + name: str, + message: Snowflake | None = None, + auto_archive_duration: int | Undefined = MISSING, + type: ChannelType | None = None, + slowmode_delay: int | None = None, + invitable: bool | None = None, + reason: str | None = None, + ) -> Thread: + """|coro| + + Creates a thread in this news channel. + + Parameters + ---------- + name: :class:`str` + The name of the thread. + message: :class:`abc.Snowflake` | None + A snowflake representing the message to create the thread with. + auto_archive_duration: :class:`int` + The duration in minutes before a thread is automatically archived for inactivity. + type: :class:`ChannelType` | None + The type of thread to create. + slowmode_delay: :class:`int` | None + Specifies the slowmode rate limit for users in this thread, in seconds. + invitable: :class:`bool` | None + Whether non-moderators can add other non-moderators to this thread. + reason: :class:`str` | None + The reason for creating a new thread. + + Returns + ------- + :class:`Thread` + The created thread + """ + from .thread import Thread + + if type is None: + type = ChannelType.private_thread + + if message is None: + data = await self._state.http.start_thread_without_message( + self.id, + name=name, + auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + type=type.value, + rate_limit_per_user=slowmode_delay or 0, + invitable=invitable, + reason=reason, + ) + else: + data = await self._state.http.start_thread_with_message( + self.id, + message.id, + name=name, + auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + rate_limit_per_user=slowmode_delay or 0, + reason=reason, + ) + + return Thread(guild=self.guild, state=self._state, data=data) diff --git a/discord/channel/partial.py b/discord/channel/partial.py new file mode 100644 index 0000000000..81058cd40e --- /dev/null +++ b/discord/channel/partial.py @@ -0,0 +1,104 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +from ..abc import Messageable +from ..enums import ChannelType +from ..mixins import Hashable +from ..object import Object + +if TYPE_CHECKING: + from ..message import PartialMessage + from ..state import ConnectionState + +__all__ = ("PartialMessageable",) + + +class PartialMessageable(Messageable, Hashable): + """Represents a partial messageable to aid with working messageable channels when + only a channel ID are present. + + The only way to construct this class is through :meth:`Client.get_partial_messageable`. + + Note that this class is trimmed down and has no rich attributes. + + .. versionadded:: 2.0 + + .. container:: operations + + .. describe:: x == y + + Checks if two partial messageables are equal. + + .. describe:: x != y + + Checks if two partial messageables are not equal. + + .. describe:: hash(x) + + Returns the partial messageable's hash. + + Attributes + ---------- + id: :class:`int` + The channel ID associated with this partial messageable. + type: Optional[:class:`ChannelType`] + The channel type associated with this partial messageable, if given. + """ + + def __init__(self, state: ConnectionState, id: int, type: ChannelType | None = None): + self._state: ConnectionState = state + self._channel: Object = Object(id=id) + self.id: int = id + self.type: ChannelType | None = type + + async def _get_channel(self) -> Object: + return self._channel + + def get_partial_message(self, message_id: int, /) -> PartialMessage: + """Creates a :class:`PartialMessage` from the message ID. + + This is useful if you want to work with a message and only have its ID without + doing an unnecessary API call. + + Parameters + ---------- + message_id: :class:`int` + The message ID to create a partial message for. + + Returns + ------- + :class:`PartialMessage` + The partial message. + """ + from ..message import PartialMessage + + return PartialMessage(channel=self, id=message_id) + + def __repr__(self) -> str: + return f"" diff --git a/discord/channel/stage.py b/discord/channel/stage.py new file mode 100644 index 0000000000..8c96a666b8 --- /dev/null +++ b/discord/channel/stage.py @@ -0,0 +1,345 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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, Mapping + +from typing_extensions import Self, override + +from ..abc import Connectable +from ..enums import ChannelType, StagePrivacyLevel, VideoQualityMode, VoiceRegion, try_enum +from ..utils import MISSING, Undefined +from .base import GuildMessageableChannel, GuildTopLevelChannel + +if TYPE_CHECKING: + from ..abc import Snowflake + from ..member import Member + from ..permissions import PermissionOverwrite + from ..role import Role + from ..stage_instance import StageInstance + from ..types.channel import StageChannel as StageChannelPayload + from .category import CategoryChannel + +__all__ = ("StageChannel",) + + +class StageChannel( + GuildTopLevelChannel["StageChannelPayload"], + GuildMessageableChannel, + Connectable, +): + """Represents a Discord guild stage channel. + + .. versionadded:: 1.7 + + .. 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 + ---------- + id: :class:`int` + The channel's ID. + name: :class:`str` + The channel's name. + guild: :class:`Guild` + The guild the channel belongs to. + topic: :class:`str` | None + The channel's topic. ``None`` if it isn't set. + category_id: :class:`int` | None + 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. + 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 stage channel. + A value of ``0`` indicates no limit. + rtc_region: :class:`VoiceRegion` | None + The region for the stage channel's voice communication. + A value of ``None`` indicates automatic voice region detection. + video_quality_mode: :class:`VideoQualityMode` + The camera video quality for the stage channel's participants. + last_message_id: :class:`int` | None + The ID of the last message sent to this channel. It may not always point to an existing or valid message. + slowmode_delay: :class:`int` + Specifies the slowmode rate limit for users in this channel, in seconds. + nsfw: :class:`bool` + Whether the channel is marked as NSFW. + + .. versionadded:: 3.0 + """ + + __slots__: tuple[str, ...] = ( + "topic", + "nsfw", + "slowmode_delay", + "last_message_id", + "bitrate", + "user_limit", + "rtc_region", + "video_quality_mode", + ) + + @override + async def _update(self, data: StageChannelPayload) -> None: + await super()._update(data) + self.bitrate: int = data.get("bitrate", 64000) + self.user_limit: int = data.get("user_limit", 0) + rtc = data.get("rtc_region") + self.rtc_region: VoiceRegion | None = try_enum(VoiceRegion, rtc) if rtc is not None else None + self.video_quality_mode: VideoQualityMode = try_enum(VideoQualityMode, data.get("video_quality_mode", 1)) + + @property + @override + def _sorting_bucket(self) -> int: + return ChannelType.stage_voice.value + + @property + def requesting_to_speak(self) -> list[Member]: + """A list of members who are requesting to speak in the stage channel.""" + return [member for member in self.members if member.voice and member.voice.requested_to_speak_at is not None] + + @property + def speakers(self) -> list[Member]: + """A list of members who have been permitted to speak in the stage channel. + + .. versionadded:: 2.0 + """ + return [member for member in self.members if member.voice and not member.voice.suppress] + + @property + def listeners(self) -> list[Member]: + """A list of members who are listening in the stage channel. + + .. versionadded:: 2.0 + """ + return [member for member in self.members if member.voice and member.voice.suppress] + + def __repr__(self) -> str: + attrs = [ + ("id", self.id), + ("name", self.name), + ("topic", self.topic), + ("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(f"{k}={v!r}" for k, v in attrs) + return f"" + + @property + def instance(self) -> StageInstance | None: + """Returns the currently running stage instance if any. + + .. versionadded:: 2.0 + + Returns + ------- + :class:`StageInstance` | None + The stage instance or ``None`` if not active. + """ + return self.guild.get_stage_instance(self.id) + + @property + def moderators(self) -> list[Member]: + """Returns a list of members who have stage moderator permissions. + + .. versionadded:: 2.0 + + Returns + ------- + list[:class:`Member`] + The members with stage moderator permissions. + """ + from ..permissions import Permissions + + required = Permissions.stage_moderator() + return [m for m in self.members if (self.permissions_for(m) & required) == required] + + async def edit( + self, + *, + name: str | Undefined = MISSING, + topic: str | Undefined = MISSING, + position: int | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + category: CategoryChannel | None | Undefined = MISSING, + overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] | Undefined = MISSING, + rtc_region: VoiceRegion | None | Undefined = MISSING, + video_quality_mode: VideoQualityMode | Undefined = MISSING, + reason: str | None = None, + ) -> Self: + """|coro| + + Edits the stage channel. + + You must have :attr:`~Permissions.manage_channels` permission to use this. + + Parameters + ---------- + name: :class:`str` + The new channel's name. + topic: :class:`str` + The new channel's topic. + position: :class:`int` + The new channel's position. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing category. + category: :class:`CategoryChannel` | None + The new category for this channel. Can be ``None`` to remove the category. + overwrites: Mapping[:class:`Role` | :class:`Member` | :class:`~discord.abc.Snowflake`, :class:`PermissionOverwrite`] + The overwrites to apply to channel permissions. + rtc_region: :class:`VoiceRegion` | None + The new region for the stage channel's voice communication. + video_quality_mode: :class:`VideoQualityMode` + The camera video quality for the stage channel's participants. + reason: :class:`str` | None + The reason for editing this channel. Shows up on the audit log. + + Returns + ------- + :class:`.StageChannel` + The newly edited stage channel. + + Raises + ------ + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + """ + options = {} + if name is not MISSING: + options["name"] = name + if topic is not MISSING: + options["topic"] = topic + if position is not MISSING: + options["position"] = position + if sync_permissions is not MISSING: + options["sync_permissions"] = sync_permissions + if category is not MISSING: + options["category"] = category + if overwrites is not MISSING: + options["overwrites"] = overwrites + if rtc_region is not MISSING: + options["rtc_region"] = rtc_region + if video_quality_mode is not MISSING: + options["video_quality_mode"] = video_quality_mode + + payload = await self._edit(options, reason=reason) + if payload is not None: + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore + + async def create_instance( + self, + *, + topic: str, + privacy_level: StagePrivacyLevel = StagePrivacyLevel.guild_only, + reason: str | None = None, + send_notification: bool = False, + ) -> StageInstance: + """|coro| + + Creates a stage instance. + + You must have :attr:`~Permissions.manage_channels` permission to do this. + + Parameters + ---------- + topic: :class:`str` + The stage instance's topic. + privacy_level: :class:`StagePrivacyLevel` + The stage instance's privacy level. + send_notification: :class:`bool` + Whether to send a notification to everyone in the server that the stage is starting. + reason: :class:`str` | None + The reason for creating the stage instance. Shows up on the audit log. + + Returns + ------- + :class:`StageInstance` + The created stage instance. + + Raises + ------ + Forbidden + You do not have permissions to create a stage instance. + HTTPException + Creating the stage instance failed. + """ + from ..stage_instance import StageInstance + + payload = await self._state.http.create_stage_instance( + self.id, + topic=topic, + privacy_level=int(privacy_level), + send_start_notification=send_notification, + reason=reason, + ) + return StageInstance(guild=self.guild, state=self._state, data=payload) + + async def fetch_instance(self) -> StageInstance | None: + """|coro| + + Fetches the currently running stage instance. + + Returns + ------- + :class:`StageInstance` | None + The stage instance or ``None`` if not active. + + Raises + ------ + NotFound + The stage instance is not active or was deleted. + HTTPException + Fetching the stage instance failed. + """ + from ..stage_instance import StageInstance + + try: + payload = await self._state.http.get_stage_instance(self.id) + return StageInstance(guild=self.guild, state=self._state, data=payload) + except Exception: + return None diff --git a/discord/channel/text.py b/discord/channel/text.py new file mode 100644 index 0000000000..4c92925d3b --- /dev/null +++ b/discord/channel/text.py @@ -0,0 +1,327 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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, Mapping + +from typing_extensions import Self, override + +from ..enums import ChannelType +from ..utils import MISSING, Undefined +from .base import GuildMessageableChannel, GuildThreadableChannel, GuildTopLevelChannel + +if TYPE_CHECKING: + from ..abc import Snowflake + from ..member import Member + from ..permissions import PermissionOverwrite + from ..role import Role + from ..types.channel import NewsChannel as NewsChannelPayload + from ..types.channel import TextChannel as TextChannelPayload + from .category import CategoryChannel + from .news import NewsChannel + from .thread import Thread + +__all__ = ("TextChannel",) + + +class TextChannel( + GuildTopLevelChannel["TextChannelPayload"], + GuildMessageableChannel, + GuildThreadableChannel, +): + """Represents a Discord guild text 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 + ---------- + id: :class:`int` + The channel's ID. + name: :class:`str` + The channel's name. + guild: :class:`Guild` + The guild the channel belongs to. + topic: :class:`str` | None + The channel's topic. ``None`` if it isn't set. + category_id: :class:`int` | None + 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. + nsfw: :class:`bool` + Whether the channel is marked as NSFW. + 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. + last_message_id: :class:`int` | None + The last message ID of the message sent to this channel. It may + *not* point to an existing or valid message. + default_auto_archive_duration: :class:`int` + The default auto archive duration in minutes for threads created in this channel. + + .. versionadded:: 3.0 + """ + + __slots__: tuple[str, ...] = ( + "topic", + "nsfw", + "slowmode_delay", + "last_message_id", + "default_auto_archive_duration", + "default_thread_slowmode_delay", + ) + + @property + @override + def _sorting_bucket(self) -> int: + return ChannelType.text.value + + def __repr__(self) -> str: + attrs = [ + ("id", self.id), + ("name", self.name), + ("position", self.position), + ("nsfw", self.nsfw), + ("category_id", self.category_id), + ] + joined = " ".join(f"{k}={v!r}" for k, v in attrs) + return f"" + + async def edit( + self, + *, + name: str | Undefined = MISSING, + topic: str | Undefined = MISSING, + position: int | Undefined = MISSING, + nsfw: bool | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + category: CategoryChannel | None | Undefined = MISSING, + slowmode_delay: int | Undefined = MISSING, + default_auto_archive_duration: int | Undefined = MISSING, + default_thread_slowmode_delay: int | Undefined = MISSING, + type: ChannelType | Undefined = MISSING, + overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] | Undefined = MISSING, + reason: str | None = None, + ) -> Self | NewsChannel: + """|coro| + + Edits the channel. + + You must have :attr:`~Permissions.manage_channels` permission to + use this. + + .. versionchanged:: 1.3 + The ``overwrites`` keyword-only parameter was added. + + .. versionchanged:: 1.4 + The ``type`` keyword-only parameter was added. + + .. versionchanged:: 2.0 + Edits are no longer in-place, the newly edited channel is returned instead. + + .. versionchanged:: 3.0 + The ``default_thread_slowmode_delay`` keyword-only parameter was added. + + Parameters + ---------- + name: :class:`str` + The new channel name. + topic: :class:`str` + The new channel's topic. + position: :class:`int` + The new channel's position. + nsfw: :class:`bool` + Whether the channel is marked as NSFW. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing + category. Defaults to ``False``. + category: :class:`CategoryChannel` | None + The new category for this channel. Can be ``None`` to remove the + category. + slowmode_delay: :class:`int` + Specifies the slowmode rate limit for user in this channel, in seconds. + A value of ``0`` disables slowmode. The maximum value possible is ``21600``. + default_auto_archive_duration: :class:`int` + The new default auto archive duration in minutes for threads created in this channel. + Must be one of ``60``, ``1440``, ``4320``, or ``10080``. + default_thread_slowmode_delay: :class:`int` + The new default slowmode delay in seconds for threads created in this channel. + type: :class:`ChannelType` + Change the type of this text channel. Currently, only conversion between + :attr:`ChannelType.text` and :attr:`ChannelType.news` is supported. This + is only available to guilds that contain ``NEWS`` in :attr:`Guild.features`. + overwrites: Mapping[:class:`Role` | :class:`Member` | :class:`~discord.abc.Snowflake`, :class:`PermissionOverwrite`] + The overwrites to apply to channel permissions. Useful for creating secret channels. + reason: :class:`str` | None + The reason for editing this channel. Shows up on the audit log. + + Returns + ------- + :class:`.TextChannel` | :class:`.NewsChannel` + The newly edited channel. If the edit was only positional + then ``None`` is returned instead. If the type was changed, + the appropriate channel type is returned. + + Raises + ------ + InvalidArgument + If position is less than 0 or greater than the number of channels, or if + the permission overwrite information is not in proper form. + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + """ + options = {} + if name is not MISSING: + options["name"] = name + if topic is not MISSING: + options["topic"] = topic + if position is not MISSING: + options["position"] = position + if nsfw is not MISSING: + options["nsfw"] = nsfw + if sync_permissions is not MISSING: + options["sync_permissions"] = sync_permissions + if category is not MISSING: + options["category"] = category + if slowmode_delay is not MISSING: + options["slowmode_delay"] = slowmode_delay + if default_auto_archive_duration is not MISSING: + options["default_auto_archive_duration"] = default_auto_archive_duration + if default_thread_slowmode_delay is not MISSING: + options["default_thread_slowmode_delay"] = default_thread_slowmode_delay + if type is not MISSING: + options["type"] = type + if overwrites is not MISSING: + options["overwrites"] = overwrites + + payload = await self._edit(options, reason=reason) + if payload is not None: + # Check if type was changed to news + if payload.get("type") == ChannelType.news.value: + from .news import NewsChannel + + return await NewsChannel._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore + + async def create_thread( + self, + *, + name: str, + message: Snowflake | None = None, + auto_archive_duration: int | Undefined = MISSING, + type: ChannelType | None = None, + slowmode_delay: int | None = None, + invitable: bool | None = None, + reason: str | None = None, + ) -> Thread: + """|coro| + + Creates a thread in this text channel. + + To create a public thread, you must have :attr:`~discord.Permissions.create_public_threads`. + For a private thread, :attr:`~discord.Permissions.create_private_threads` is needed instead. + + .. versionadded:: 2.0 + + Parameters + ---------- + name: :class:`str` + The name of the thread. + message: :class:`abc.Snowflake` | None + A snowflake representing the message to create the thread with. + 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. + If not provided, the channel's default auto archive duration is used. + type: :class:`ChannelType` | None + 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``. + slowmode_delay: :class:`int` | None + Specifies the slowmode rate limit for users in this thread, in seconds. + A value of ``0`` disables slowmode. The maximum value possible is ``21600``. + invitable: :class:`bool` | None + Whether non-moderators can add other non-moderators to this thread. + Only available for private threads, where it defaults to True. + reason: :class:`str` | None + The reason for creating a new thread. Shows up on the audit log. + + Returns + ------- + :class:`Thread` + The created thread + + Raises + ------ + Forbidden + You do not have permissions to create a thread. + HTTPException + Starting the thread failed. + """ + from .thread import Thread + + if type is None: + type = ChannelType.private_thread + + if message is None: + data = await self._state.http.start_thread_without_message( + self.id, + name=name, + auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + type=type.value, + rate_limit_per_user=slowmode_delay or 0, + invitable=invitable, + reason=reason, + ) + else: + data = await self._state.http.start_thread_with_message( + self.id, + message.id, + name=name, + auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + rate_limit_per_user=slowmode_delay or 0, + reason=reason, + ) + + return Thread(guild=self.guild, state=self._state, data=data) diff --git a/discord/threads.py b/discord/channel/thread.py similarity index 84% rename from discord/threads.py rename to discord/channel/thread.py index 1f2a7600a4..3a942dff86 100644 --- a/discord/threads.py +++ b/discord/channel/thread.py @@ -27,19 +27,23 @@ from typing import TYPE_CHECKING, Callable, Iterable +from typing_extensions import override + from discord import utils -from .abc import Messageable, _purge_messages_helper -from .enums import ( +from ..abc import Messageable, _purge_messages_helper +from ..enums import ( ChannelType, try_enum, ) -from .enums import ThreadArchiveDuration as ThreadArchiveDurationEnum -from .errors import ClientException -from .flags import ChannelFlags -from .mixins import Hashable -from .utils import MISSING -from .utils.private import get_as_snowflake, parse_time +from ..enums import ThreadArchiveDuration as ThreadArchiveDurationEnum +from ..errors import ClientException +from ..flags import ChannelFlags +from ..mixins import Hashable +from ..types.threads import Thread as ThreadPayload +from ..utils import MISSING +from ..utils.private import get_as_snowflake, parse_time +from .base import BaseChannel, GuildMessageableChannel __all__ = ( "Thread", @@ -47,21 +51,20 @@ ) if TYPE_CHECKING: - from .abc import Snowflake, SnowflakeTime - from .app.state import ConnectionState - from .channel import CategoryChannel, ForumChannel, ForumTag, TextChannel - from .guild import Guild - from .member import Member - from .message import Message, PartialMessage - from .permissions import Permissions - from .role import Role - from .types.snowflake import SnowflakeList - from .types.threads import Thread as ThreadPayload - from .types.threads import ThreadArchiveDuration, ThreadMetadata - from .types.threads import ThreadMember as ThreadMemberPayload - - -class Thread(Messageable, Hashable): + from ..abc import Snowflake, SnowflakeTime + from ..app.state import ConnectionState + from ..guild import Guild + from ..member import Member + from ..message import Message, PartialMessage + from ..permissions import Permissions + from ..role import Role + from ..types.snowflake import SnowflakeList + from ..types.threads import ThreadArchiveDuration, ThreadMetadata + from ..types.threads import ThreadMember as ThreadMemberPayload + from . import CategoryChannel, ForumChannel, ForumTag, TextChannel + + +class Thread(BaseChannel[ThreadPayload], GuildMessageableChannel): """Represents a Discord thread. .. container:: operations @@ -86,55 +89,55 @@ class Thread(Messageable, Hashable): Attributes ---------- - name: :class:`str` + name: str The thread name. - guild: :class:`Guild` + guild: Guild The guild the thread belongs to. - id: :class:`int` + id: int The thread ID. .. note:: This ID is the same as the thread starting message ID. - parent_id: :class:`int` + parent_id: int The parent :class:`TextChannel` ID this thread belongs to. - owner_id: :class:`int` + owner_id: int The user's ID that created this thread. - last_message_id: Optional[:class:`int`] + last_message_id: int | None The last message ID of the message sent to this thread. It may *not* point to an existing or valid message. - slowmode_delay: :class:`int` + slowmode_delay: int The number of seconds a member must wait between sending messages in this thread. A value of `0` denotes that it is disabled. Bots and users with :attr:`~Permissions.manage_channels` or :attr:`~Permissions.manage_messages` bypass slowmode. - message_count: :class:`int` + message_count: int An approximate number of messages in this thread. This caps at 50. - member_count: :class:`int` + member_count: int An approximate number of members in this thread. This caps at 50. - me: Optional[:class:`ThreadMember`] + me: ThreadMember | None A thread member representing yourself, if you've joined the thread. This could not be available. - archived: :class:`bool` + archived: bool Whether the thread is archived. - locked: :class:`bool` + locked: bool Whether the thread is locked. - invitable: :class:`bool` + invitable: bool Whether non-moderators can add other non-moderators to this thread. This is always ``True`` for public threads. - auto_archive_duration: :class:`int` + auto_archive_duration: int The duration in minutes until the thread is automatically archived due to inactivity. Usually a value of 60, 1440, 4320 and 10080. - archive_timestamp: :class:`datetime.datetime` + archive_timestamp: datetime.datetime An aware timestamp of when the thread's archived status was last updated in UTC. - created_at: Optional[:class:`datetime.datetime`] + created_at: datetime.datetime | None An aware timestamp of when the thread was created. Only available for threads created after 2022-01-09. - flags: :class:`ChannelFlags` + flags: ChannelFlags Extra features of the thread. .. versionadded:: 2.0 - total_message_sent: :class:`int` + total_message_sent: int Number of messages ever sent in a thread. It's similar to message_count on message creation, but will not decrement the number when a message is deleted. @@ -142,20 +145,14 @@ class Thread(Messageable, Hashable): .. versionadded:: 2.3 """ - __slots__ = ( - "name", - "id", + __slots__: tuple[str, ...] = ( "guild", - "_type", - "_state", "_members", "_applied_tags", "owner_id", "parent_id", - "last_message_id", "message_count", "member_count", - "slowmode_delay", "me", "locked", "archived", @@ -163,86 +160,83 @@ class Thread(Messageable, Hashable): "auto_archive_duration", "archive_timestamp", "created_at", - "flags", "total_message_sent", ) - def __init__(self, *, guild: Guild, state: ConnectionState, data: ThreadPayload): - self._state: ConnectionState = state - self.guild = guild + @override + def __init__(self, *, id: int, guild: Guild, state: ConnectionState): + super().__init__(id, state) + self.guild: Guild = guild self._members: dict[int, ThreadMember] = {} - self._from_data(data) + + @classmethod + @override + async def _from_data( + cls, + *, + data: ThreadPayload, + state: ConnectionState, + guild: Guild, + ) -> Thread: + """Create thread instance from API payload.""" + self = cls( + id=int(data["id"]), + guild=guild, + state=state, + ) + await self._update(data) + return self + + @override + async def _update(self, data: ThreadPayload) -> None: + """Update mutable attributes from API payload.""" + await super()._update(data) + + # Thread-specific attributes + self.parent_id: int = int(data.get("parent_id", self.parent_id if hasattr(self, "parent_id") else 0)) + self.owner_id: int | None = int(data["owner_id"]) if data.get("owner_id") is not None else None + self.message_count: int | None = data.get("message_count") + self.member_count: int | None = data.get("member_count") + self.total_message_sent: int | None = data.get("total_message_sent") + self._applied_tags: list[int] = [int(tag_id) for tag_id in data.get("applied_tags", [])] + + # Handle thread metadata + if "thread_metadata" in data: + metadata = data["thread_metadata"] + self.archived: bool = metadata["archived"] + self.auto_archive_duration: int = metadata["auto_archive_duration"] + self.archive_timestamp = parse_time(metadata["archive_timestamp"]) + self.locked: bool = metadata["locked"] + self.invitable: bool = metadata.get("invitable", True) + self.created_at = parse_time(metadata.get("create_timestamp")) + + # Handle thread member data + if "member" in data: + self.me: ThreadMember | None = ThreadMember(self, data["member"]) + elif not hasattr(self, "me"): + self.me = None async def _get_channel(self): return self + @override def __repr__(self) -> str: return ( f"" ) - def __str__(self) -> str: - return self.name - - def _from_data(self, data: ThreadPayload): - # This data will always exist - self.id = int(data["id"]) - self.parent_id = int(data["parent_id"]) - self.name = data["name"] - self._type = try_enum(ChannelType, data["type"]) - - # This data may be missing depending on how this object is being created - self.owner_id = int(data.get("owner_id")) if data.get("owner_id", None) is not None else None - self.last_message_id = get_as_snowflake(data, "last_message_id") - self.slowmode_delay = data.get("rate_limit_per_user", 0) - self.message_count = data.get("message_count", None) - self.member_count = data.get("member_count", None) - self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) - self.total_message_sent = data.get("total_message_sent", None) - self._applied_tags: list[int] = [int(tag_id) for tag_id in data.get("applied_tags", [])] - - # Here, we try to fill in potentially missing data - if thread := self.guild.get_thread(self.id) and data.pop("_invoke_flag", False): - self.owner_id = thread.owner_id if self.owner_id is None else self.owner_id - self.last_message_id = thread.last_message_id if self.last_message_id is None else self.last_message_id - self.message_count = thread.message_count if self.message_count is None else self.message_count - self.total_message_sent = ( - thread.total_message_sent if self.total_message_sent is None else self.total_message_sent - ) - self.member_count = thread.member_count if self.member_count is None else self.member_count - - self._unroll_metadata(data["thread_metadata"]) - - try: - member = data["member"] - except KeyError: - self.me = None - else: - self.me = ThreadMember(self, member) - - def _unroll_metadata(self, data: ThreadMetadata): - self.archived = data["archived"] - self.auto_archive_duration = data["auto_archive_duration"] - self.archive_timestamp = parse_time(data["archive_timestamp"]) - self.locked = data["locked"] - self.invitable = data.get("invitable", True) - self.created_at = parse_time(data.get("create_timestamp", None)) - - async def _update(self, data): - try: - self.name = data["name"] - except KeyError: - pass - - self._applied_tags: list[int] = [int(tag_id) for tag_id in data.get("applied_tags", [])] - self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) - self.slowmode_delay = data.get("rate_limit_per_user", 0) + @property + def topic(self) -> None: + """Threads don't have topics. Always returns None.""" + return None - try: - self._unroll_metadata(data["thread_metadata"]) - except KeyError: - pass + @property + @override + def nsfw(self) -> bool: + """Whether the thread is NSFW. Inherited from parent channel.""" + parent = self.parent + return parent.nsfw if parent else False @property def type(self) -> ChannelType: @@ -287,7 +281,7 @@ def applied_tags(self) -> list[ForumTag]: This is only available for threads in forum or media channels. """ - from .channel import ForumChannel # noqa: PLC0415 # to prevent circular import + from .channel import ForumChannel # to prevent circular import if isinstance(self.parent, ForumChannel): return [tag for tag_id in self._applied_tags if (tag := self.parent.get_tag(tag_id)) is not None] @@ -654,7 +648,7 @@ async def edit( data = await self._state.http.edit_channel(self.id, **payload, reason=reason) # The data payload will always be a Thread payload - return Thread(data=data, state=self._state, guild=self.guild) # type: ignore + return await Thread._from_data(data=data, state=self._state, guild=self.guild) # type: ignore async def archive(self, locked: bool | utils.Undefined = MISSING) -> Thread: """|coro| @@ -822,7 +816,7 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: The partial message. """ - from .message import PartialMessage # noqa: PLC0415 + from .message import PartialMessage return PartialMessage(channel=self, id=message_id) diff --git a/discord/channel/voice.py b/discord/channel/voice.py new file mode 100644 index 0000000000..5b0c932d80 --- /dev/null +++ b/discord/channel/voice.py @@ -0,0 +1,328 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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, Mapping + +from typing_extensions import Self, override + +from ..abc import Connectable +from ..enums import ChannelType, InviteTarget, VideoQualityMode, VoiceRegion, try_enum +from ..utils import MISSING, Undefined +from .base import GuildMessageableChannel, GuildTopLevelChannel + +if TYPE_CHECKING: + from ..abc import Snowflake + from ..enums import EmbeddedActivity + from ..invite import Invite + from ..member import Member + from ..permissions import PermissionOverwrite + from ..role import Role + from ..soundboard import PartialSoundboardSound + from ..types.channel import VoiceChannel as VoiceChannelPayload + from .category import CategoryChannel + +__all__ = ("VoiceChannel",) + + +class VoiceChannel( + GuildTopLevelChannel["VoiceChannelPayload"], + GuildMessageableChannel, + Connectable, +): + """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 + ---------- + id: :class:`int` + The channel's ID. + name: :class:`str` + The channel's name. + guild: :class:`Guild` + The guild the channel belongs to. + category_id: :class:`int` | None + 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. + 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. + A value of ``0`` indicates no limit. + rtc_region: :class:`VoiceRegion` | None + The region for the voice channel's voice communication. + A value of ``None`` indicates automatic voice region detection. + video_quality_mode: :class:`VideoQualityMode` + The camera video quality for the voice channel's participants. + last_message_id: :class:`int` | None + The ID of the last message sent to this channel. It may not always point to an existing or valid message. + 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. + status: :class:`str` | None + The channel's status, if set. + nsfw: :class:`bool` + Whether the channel is marked as NSFW. + + .. versionadded:: 3.0 + """ + + __slots__: tuple[str, ...] = ( + "topic", + "nsfw", + "slowmode_delay", + "last_message_id", + "bitrate", + "user_limit", + "rtc_region", + "video_quality_mode", + "status", + ) + + @override + async def _update(self, data: VoiceChannelPayload) -> None: + await super()._update(data) + self.bitrate: int = data.get("bitrate", 64000) + self.user_limit: int = data.get("user_limit", 0) + rtc = data.get("rtc_region") + self.rtc_region: VoiceRegion | None = try_enum(VoiceRegion, rtc) if rtc is not None else None + self.video_quality_mode: VideoQualityMode = try_enum(VideoQualityMode, data.get("video_quality_mode", 1)) + self.status: str | None = data.get("status") + + @property + @override + def _sorting_bucket(self) -> int: + return ChannelType.voice.value + + def __repr__(self) -> str: + attrs = [ + ("id", self.id), + ("name", self.name), + ("status", self.status), + ("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(f"{k}={v!r}" for k, v in attrs) + return f"" + + async def edit( + self, + *, + name: str | Undefined = MISSING, + bitrate: int | Undefined = MISSING, + user_limit: int | Undefined = MISSING, + position: int | Undefined = MISSING, + sync_permissions: bool | Undefined = MISSING, + category: CategoryChannel | None | Undefined = MISSING, + overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] | Undefined = MISSING, + rtc_region: VoiceRegion | None | Undefined = MISSING, + video_quality_mode: VideoQualityMode | Undefined = MISSING, + slowmode_delay: int | Undefined = MISSING, + nsfw: bool | Undefined = MISSING, + reason: str | None = None, + ) -> Self: + """|coro| + + Edits the voice channel. + + You must have :attr:`~Permissions.manage_channels` permission to use this. + + Parameters + ---------- + name: :class:`str` + The new channel's name. + bitrate: :class:`int` + The new channel's bitrate. + user_limit: :class:`int` + The new channel's user limit. + position: :class:`int` + The new channel's position. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing category. + category: :class:`CategoryChannel` | None + The new category for this channel. Can be ``None`` to remove the category. + overwrites: Mapping[:class:`Role` | :class:`Member` | :class:`~discord.abc.Snowflake`, :class:`PermissionOverwrite`] + The overwrites to apply to channel permissions. + rtc_region: :class:`VoiceRegion` | None + The new region for the voice channel's voice communication. + A value of ``None`` indicates automatic voice region detection. + video_quality_mode: :class:`VideoQualityMode` + The camera video quality for the voice channel's participants. + slowmode_delay: :class:`int` + Specifies the slowmode rate limit for user in this channel, in seconds. + nsfw: :class:`bool` + Whether the channel is marked as NSFW. + reason: :class:`str` | None + The reason for editing this channel. Shows up on the audit log. + + Returns + ------- + :class:`.VoiceChannel` + The newly edited voice channel. If the edit was only positional then ``None`` is returned. + + Raises + ------ + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + """ + options = {} + if name is not MISSING: + options["name"] = name + if bitrate is not MISSING: + options["bitrate"] = bitrate + if user_limit is not MISSING: + options["user_limit"] = user_limit + if position is not MISSING: + options["position"] = position + if sync_permissions is not MISSING: + options["sync_permissions"] = sync_permissions + if category is not MISSING: + options["category"] = category + if overwrites is not MISSING: + options["overwrites"] = overwrites + if rtc_region is not MISSING: + options["rtc_region"] = rtc_region + if video_quality_mode is not MISSING: + options["video_quality_mode"] = video_quality_mode + if slowmode_delay is not MISSING: + options["slowmode_delay"] = slowmode_delay + if nsfw is not MISSING: + options["nsfw"] = nsfw + + payload = await self._edit(options, reason=reason) + if payload is not None: + return await self.__class__._from_data(data=payload, state=self._state, guild=self.guild) # type: ignore + + async def create_activity_invite(self, activity: EmbeddedActivity | int, **kwargs) -> Invite: + """|coro| + + A shortcut method that creates an instant activity invite. + + You must have :attr:`~discord.Permissions.start_embedded_activities` permission to do this. + + Parameters + ---------- + activity: :class:`EmbeddedActivity` | :class:`int` + The embedded activity to create an invite for. Can be an :class:`EmbeddedActivity` enum member + or the application ID as an integer. + max_age: :class:`int` + How long the invite should last in seconds. If it's 0 then the invite doesn't expire. + max_uses: :class:`int` + How many uses the invite could be used for. If it's 0 then there are unlimited uses. + temporary: :class:`bool` + Denotes that the invite grants temporary membership. + unique: :class:`bool` + Indicates if a unique invite URL should be created. + reason: :class:`str` | None + The reason for creating this invite. Shows up on the audit log. + + Returns + ------- + :class:`~discord.Invite` + The invite that was created. + + Raises + ------ + HTTPException + Invite creation failed. + """ + from ..enums import EmbeddedActivity + + if isinstance(activity, EmbeddedActivity): + activity = activity.value + + return await self.create_invite( + target_type=InviteTarget.embedded_application, + target_application_id=activity, + **kwargs, + ) + + async def set_status(self, status: str | None, *, reason: str | None = None) -> None: + """|coro| + + Sets the voice channel status. + + You must have :attr:`~discord.Permissions.manage_channels` and + :attr:`~discord.Permissions.connect` permissions to do this. + + Parameters + ---------- + status: :class:`str` | None + The new voice channel status. Set to ``None`` to remove the status. + reason: :class:`str` | None + The reason for setting the voice channel status. Shows up on the audit log. + + Raises + ------ + Forbidden + You do not have permissions to set the voice channel status. + HTTPException + Setting the voice channel status failed. + """ + await self._state.http.edit_voice_channel_status(self.id, status, reason=reason) + + async def send_soundboard_sound(self, sound: PartialSoundboardSound) -> None: + """|coro| + + Sends a soundboard sound to the voice channel. + + Parameters + ---------- + sound: :class:`PartialSoundboardSound` + The soundboard sound to send. + + Raises + ------ + Forbidden + You do not have proper permissions to send the soundboard sound. + HTTPException + Sending the soundboard sound failed. + """ + await self._state.http.send_soundboard_sound(self.id, sound) diff --git a/discord/client.py b/discord/client.py index 71bd01751e..27667955e6 100644 --- a/discord/client.py +++ b/discord/client.py @@ -30,6 +30,7 @@ import signal import sys import traceback +from collections.abc import Awaitable from types import TracebackType from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Coroutine, Generator, Sequence, TypeVar @@ -40,16 +41,19 @@ from . import utils from .activity import ActivityTypes, BaseActivity, create_activity from .app.cache import Cache, MemoryCache +from .app.event_emitter import Event from .app.state import ConnectionState from .appinfo import AppInfo, PartialAppInfo from .application_role_connection import ApplicationRoleConnectionMetadata from .backoff import ExponentialBackoff from .channel import PartialMessageable, _threaded_channel_factory +from .channel.thread import Thread from .emoji import AppEmoji, GuildEmoji from .enums import ChannelType, Status from .errors import * from .flags import ApplicationFlags, Intents from .gateway import * +from .gears import Gear from .guild import Guild from .http import HTTPClient from .invite import Invite @@ -61,13 +65,13 @@ from .stage_instance import StageInstance from .sticker import GuildSticker, StandardSticker, StickerPack, _sticker_factory from .template import Template -from .threads import Thread from .ui.view import View from .user import ClientUser, User from .utils import MISSING from .utils.private import ( SequenceProxy, bytes_to_base64_data, + copy_doc, resolve_invite, resolve_template, ) @@ -76,8 +80,8 @@ from .widget import Widget if TYPE_CHECKING: - from .abc import GuildChannel, PrivateChannel, Snowflake, SnowflakeTime - from .channel import DMChannel + from .abc import PrivateChannel, Snowflake, SnowflakeTime + from .channel import DMChannel, GuildChannel from .interactions import Interaction from .member import Member from .message import Message @@ -243,7 +247,6 @@ def __init__( # self.ws is set in the connect method self.ws: DiscordWebSocket = None # type: ignore self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop - self._listeners: dict[str, list[tuple[asyncio.Future, Callable[..., bool]]]] = {} self.shard_id: int | None = options.get("shard_id") self.shard_count: int | None = options.get("shard_count") @@ -264,7 +267,14 @@ def __init__( self._hooks: dict[str, Callable] = {"before_identify": self._call_before_identify_hook} self._enable_debug_events: bool = options.pop("enable_debug_events", False) - self._connection: ConnectionState = self._get_state(**options) + self._connection: ConnectionState = ConnectionState( + handlers=self._handlers, + hooks=self._hooks, + http=self.http, + loop=self.loop, + cache=MemoryCache(), + **options, + ) self._connection.shard_count = self.shard_count self._closed: bool = False self._ready: asyncio.Event = asyncio.Event() @@ -272,6 +282,10 @@ def __init__( self._connection._get_client = lambda: self self._event_handlers: dict[str, list[Coro]] = {} + self._main_gear: Gear = Gear() + + self._connection.emitter.add_receiver(self._handle_event) + if VoiceClient.warn_nacl: VoiceClient.warn_nacl = False _log.warning("PyNaCl is not installed, voice will NOT be supported") @@ -279,6 +293,9 @@ def __init__( # Used to hard-reference tasks so they don't get garbage collected (discarded with done_callbacks) self._tasks = set() + async def _handle_event(self, event: Event) -> None: + await asyncio.gather(*self._main_gear._handle_event(event)) + async def __aenter__(self) -> Client: loop = asyncio.get_running_loop() self.loop = loop @@ -298,22 +315,47 @@ async def __aexit__( if not self.is_closed(): await self.close() + # Gear methods + + @copy_doc(Gear.attach_gear) + def attach_gear(self, gear: Gear) -> None: + return self._main_gear.attach_gear(gear) + + @copy_doc(Gear.detach_gear) + def detach_gear(self, gear: Gear) -> None: + return self._main_gear.detach_gear(gear) + + @copy_doc(Gear.add_listener) + def add_listener( + self, + callback: Callable[[Event], Awaitable[None]], + *, + event: type[Event] | Undefined = MISSING, + is_instance_function: bool = False, + once: bool = False, + ) -> None: + return self._main_gear.add_listener(callback, event=event, is_instance_function=is_instance_function, once=once) + + @copy_doc(Gear.remove_listener) + def remove_listener( + self, + callback: Callable[[Event], Awaitable[None]], + event: type[Event] | Undefined = MISSING, + is_instance_function: bool = False, + ) -> None: + return self._main_gear.remove_listener(callback, event=event, is_instance_function=is_instance_function) + + @copy_doc(Gear.listen) + def listen( + self, event: type[Event] | Undefined = MISSING, once: bool = False + ) -> Callable[[Callable[[Event], Awaitable[None]]], Callable[[Event], Awaitable[None]]]: + return self._main_gear.listen(event=event, once=once) + # internals def _get_websocket(self, guild_id: int | None = None, *, shard_id: int | None = None) -> DiscordWebSocket: return self.ws - def _get_state(self, **options: Any) -> ConnectionState: - return ConnectionState( - dispatch=self.dispatch, - handlers=self._handlers, - hooks=self._hooks, - http=self.http, - loop=self.loop, - cache=MemoryCache(), - **options, - ) - def _handle_ready(self) -> None: self._ready.set() @@ -465,71 +507,6 @@ def _schedule_event( task.add_done_callback(self._tasks.discard) return task - def dispatch(self, event: str, *args: Any, **kwargs: Any) -> None: - _log.debug("Dispatching event %s", event) - method = f"on_{event}" - - listeners = self._listeners.get(event) - if listeners: - removed = [] - for i, (future, condition) in enumerate(listeners): - if future.cancelled(): - removed.append(i) - continue - - try: - result = condition(*args) - except Exception as exc: - future.set_exception(exc) - removed.append(i) - else: - if result: - if len(args) == 0: - future.set_result(None) - elif len(args) == 1: - future.set_result(args[0]) - else: - future.set_result(args) - removed.append(i) - - if len(removed) == len(listeners): - self._listeners.pop(event) - else: - for idx in reversed(removed): - del listeners[idx] - - # Schedule the main handler registered with @event - try: - coro = getattr(self, method) - except AttributeError: - pass - else: - self._schedule_event(coro, method, *args, **kwargs) - - # collect the once listeners as removing them from the list - # while iterating over it causes issues - once_listeners = [] - - # Schedule additional handlers registered with @listen - for coro in self._event_handlers.get(method, []): - self._schedule_event(coro, method, *args, **kwargs) - - try: - if coro._once: # added using @listen() - once_listeners.append(coro) - - except AttributeError: # added using @Cog.add_listener() - # https://github.com/Pycord-Development/pycord/pull/1989 - # Although methods are similar to functions, attributes can't be added to them. - # This means that we can't add the `_once` attribute in the `add_listener` method - # and can only be added using the `@listen` decorator. - - continue - - # remove the once listeners - for coro in once_listeners: - self._event_handlers[method].remove(coro) - async def on_error(self, event_method: str, *args: Any, **kwargs: Any) -> None: """|coro| @@ -691,7 +668,7 @@ async def connect(self, *, reconnect: bool = True) -> None: await self.ws.poll_event() except ReconnectWebSocket as e: _log.info("Got a request to %s the websocket.", e.op) - self.dispatch("disconnect") + # self.dispatch("disconnect") # TODO: dispatch event ws_params.update( sequence=self.ws.sequence, resume=e.resume, @@ -1151,8 +1128,6 @@ async def get_all_members(self) -> AsyncGenerator[Member]: for member in guild.members: yield member - # listeners/waiters - async def wait_until_ready(self) -> None: """|coro| @@ -1160,275 +1135,6 @@ async def wait_until_ready(self) -> None: """ await self._ready.wait() - def wait_for( - self, - event: str, - *, - check: Callable[..., bool] | None = None, - timeout: float | None = None, - ) -> Any: - """|coro| - - Waits for a WebSocket event to be dispatched. - - This could be used to wait for a user to reply to a message, - or to react to a message, or to edit a message in a self-contained - way. - - The ``timeout`` parameter is passed onto :func:`asyncio.wait_for`. By default, - it does not timeout. Note that this does propagate the - :exc:`asyncio.TimeoutError` for you in case of timeout and is provided for - ease of use. - - In case the event returns multiple arguments, a :class:`tuple` containing those - arguments is returned instead. Please check the - :ref:`documentation ` for a list of events and their - parameters. - - This function returns the **first event that meets the requirements**. - - Parameters - ---------- - event: :class:`str` - The event name, similar to the :ref:`event reference `, - but without the ``on_`` prefix, to wait for. - check: Optional[Callable[..., :class:`bool`]] - A predicate to check what to wait for. The arguments must meet the - parameters of the event being waited for. - timeout: Optional[:class:`float`] - The number of seconds to wait before timing out and raising - :exc:`asyncio.TimeoutError`. - - Returns - ------- - Any - Returns no arguments, a single argument, or a :class:`tuple` of multiple - arguments that mirrors the parameters passed in the - :ref:`event reference `. - - Raises - ------ - asyncio.TimeoutError - Raised if a timeout is provided and reached. - - Examples - -------- - - Waiting for a user reply: :: - - @client.event - async def on_message(message): - if message.content.startswith("$greet"): - channel = message.channel - await channel.send("Say hello!") - - def check(m): - return m.content == "hello" and m.channel == channel - - msg = await client.wait_for("message", check=check) - await channel.send(f"Hello {msg.author}!") - - Waiting for a thumbs up reaction from the message author: :: - - @client.event - async def on_message(message): - if message.content.startswith("$thumb"): - channel = message.channel - await channel.send("Send me that \N{THUMBS UP SIGN} reaction, mate") - - def check(reaction, user): - return user == message.author and str(reaction.emoji) == "\N{THUMBS UP SIGN}" - - try: - reaction, user = await client.wait_for("reaction_add", timeout=60.0, check=check) - except asyncio.TimeoutError: - await channel.send("\N{THUMBS DOWN SIGN}") - else: - await channel.send("\N{THUMBS UP SIGN}") - """ - - future = self.loop.create_future() - if check is None: - - def _check(*args): - return True - - check = _check - - ev = event.lower() - try: - listeners = self._listeners[ev] - except KeyError: - listeners = [] - self._listeners[ev] = listeners - - listeners.append((future, check)) - return asyncio.wait_for(future, timeout) - - # event registration - def add_listener(self, func: Coro, name: str | utils.Undefined = MISSING) -> None: - """The non decorator alternative to :meth:`.listen`. - - Parameters - ---------- - func: :ref:`coroutine ` - The function to call. - name: :class:`str` - The name of the event to listen for. Defaults to ``func.__name__``. - - Raises - ------ - TypeError - The ``func`` parameter is not a coroutine function. - ValueError - The ``name`` (event name) does not start with ``on_``. - - Example - ------- - - .. code-block:: python3 - - async def on_ready(): - pass - - - async def my_message(message): - pass - - - client.add_listener(on_ready) - client.add_listener(my_message, "on_message") - """ - name = func.__name__ if name is MISSING else name - - if not name.startswith("on_"): - raise ValueError("The 'name' parameter must start with 'on_'") - - if not asyncio.iscoroutinefunction(func): - raise TypeError("Listeners must be coroutines") - - if name in self._event_handlers: - self._event_handlers[name].append(func) - else: - self._event_handlers[name] = [func] - - _log.debug( - "%s has successfully been registered as a handler for event %s", - func.__name__, - name, - ) - - def remove_listener(self, func: Coro, name: str | utils.Undefined = MISSING) -> None: - """Removes a listener from the pool of listeners. - - Parameters - ---------- - func - The function that was used as a listener to remove. - name: :class:`str` - The name of the event we want to remove. Defaults to - ``func.__name__``. - """ - - name = func.__name__ if name is MISSING else name - - if name in self._event_handlers: - try: - self._event_handlers[name].remove(func) - except ValueError: - pass - - def listen(self, name: str | utils.Undefined = MISSING, once: bool = False) -> Callable[[Coro], Coro]: - """A decorator that registers another function as an external - event listener. Basically this allows you to listen to multiple - events from different places e.g. such as :func:`.on_ready` - - The functions being listened to must be a :ref:`coroutine `. - - Raises - ------ - TypeError - The function being listened to is not a coroutine. - ValueError - The ``name`` (event name) does not start with ``on_``. - - Example - ------- - - .. code-block:: python3 - - @client.listen() - async def on_message(message): - print("one") - - - # in some other file... - - - @client.listen("on_message") - async def my_message(message): - print("two") - - - # listen to the first event only - @client.listen("on_ready", once=True) - async def on_ready(): - print("ready!") - - Would print one and two in an unspecified order. - """ - - def decorator(func: Coro) -> Coro: - # Special case, where default should be overwritten - if name == "on_application_command_error": - return self.event(func) - - func._once = once - self.add_listener(func, name) - return func - - if asyncio.iscoroutinefunction(name): - coro = name - name = coro.__name__ - return decorator(coro) - - return decorator - - def event(self, coro: Coro) -> Coro: - """A decorator that registers an event to listen to. - - You can find more info about the events on the :ref:`documentation below `. - - The events must be a :ref:`coroutine `, if not, :exc:`TypeError` is raised. - - .. note:: - - This replaces any default handlers. - Developers are encouraged to use :py:meth:`~discord.Client.listen` for adding additional handlers - instead of :py:meth:`~discord.Client.event` unless default method replacement is intended. - - Raises - ------ - TypeError - The coroutine passed is not actually a coroutine. - - Example - ------- - - .. code-block:: python3 - - @client.event - async def on_ready(): - print("Ready!") - """ - - if not asyncio.iscoroutinefunction(coro): - raise TypeError("event registered must be a coroutine function") - - setattr(self, coro.__name__, coro) - _log.debug("%s has successfully been registered as an event", coro.__name__) - return coro - async def change_presence( self, *, diff --git a/discord/commands/core.py b/discord/commands/core.py index 76a90e6d9b..acbc2a757f 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -45,7 +45,10 @@ Union, ) +from discord.interactions import AutocompleteInteraction, Interaction + from ..channel import PartialMessageable, _threaded_guild_channel_factory +from ..channel.thread import Thread from ..enums import Enum as DiscordEnum from ..enums import ( IntegrationType, @@ -66,7 +69,6 @@ from ..message import Attachment, Message from ..object import Object from ..role import Role -from ..threads import Thread from ..user import User from ..utils import MISSING, find, utcnow from ..utils.private import async_all, maybe_awaitable, warn_deprecated @@ -111,7 +113,7 @@ def wrap_callback(coro): - from ..ext.commands.errors import CommandError # noqa: PLC0415 + from ..ext.commands.errors import CommandError @functools.wraps(coro) async def wrapped(*args, **kwargs): @@ -131,7 +133,7 @@ async def wrapped(*args, **kwargs): def hooked_wrapped_callback(command, ctx, coro): - from ..ext.commands.errors import CommandError # noqa: PLC0415 + from ..ext.commands.errors import CommandError @functools.wraps(coro) async def wrapped(arg): @@ -188,7 +190,7 @@ class ApplicationCommand(_BaseCommand, Generic[CogT, P, T]): cog = None def __init__(self, func: Callable, **kwargs) -> None: - from ..ext.commands.cooldowns import BucketType, CooldownMapping, MaxConcurrency # noqa: PLC0415 + from ..ext.commands.cooldowns import BucketType, CooldownMapping, MaxConcurrency cooldown = getattr(func, "__commands_cooldown__", kwargs.get("cooldown")) @@ -330,7 +332,7 @@ def _prepare_cooldowns(self, ctx: ApplicationContext): retry_after = bucket.update_rate_limit(current) if retry_after: - from ..ext.commands.errors import CommandOnCooldown # noqa: PLC0415 + from ..ext.commands.errors import CommandOnCooldown raise CommandOnCooldown(bucket, retry_after, self._buckets.type) # type: ignore @@ -464,7 +466,9 @@ async def dispatch_error(self, ctx: ApplicationContext, error: Exception) -> Non wrapped = wrap_callback(local) await wrapped(ctx, error) finally: - ctx.bot.dispatch("application_command_error", ctx, error) + ctx.bot.dispatch( + "application_command_error", ctx, error + ) # TODO: Remove this when migrating away from ApplicationContext def _get_signature_parameters(self): return OrderedDict(inspect.signature(self.callback).parameters) @@ -1003,7 +1007,7 @@ async def _invoke(self, ctx: ApplicationContext) -> None: arg = Object(id=int(arg)) elif op.input_type == SlashCommandOptionType.string and (converter := op.converter) is not None: - from discord.ext.commands import Converter # noqa: PLC0415 + from discord.ext.commands import Converter if isinstance(converter, Converter): if isinstance(converter, type): @@ -1042,30 +1046,14 @@ async def _invoke(self, ctx: ApplicationContext) -> None: else: await self.callback(ctx, **kwargs) - async def invoke_autocomplete_callback(self, ctx: AutocompleteContext): - values = {i.name: i.default for i in self.options} - - for op in ctx.interaction.data.get("options", []): - if op.get("focused", False): - # op_name is used because loop variables leak in surrounding scope - option = find(lambda o, op_name=op["name"]: o.name == op_name, self.options) - values.update({i["name"]: i["value"] for i in ctx.interaction.data["options"]}) - ctx.command = self - ctx.focused = option - ctx.value = op.get("value") - ctx.options = values - - if option.autocomplete._is_instance_method: - instance = getattr(option.autocomplete, "__self__", ctx.cog) - result = option.autocomplete(instance, ctx) - else: - result = option.autocomplete(ctx) - - if inspect.isawaitable(result): - result = await result + async def invoke_autocomplete_callback(self, interaction: AutocompleteInteraction) -> None: + option = find(lambda o: o.name == interaction.name, self.options) + if not option.autocomplete: + raise ClientException(f"Option {interaction.name} is not an autocomplete option.") + result = await option.autocomplete(interaction) - choices = [o if isinstance(o, OptionChoice) else OptionChoice(o) for o in result][:25] - return await ctx.interaction.response.send_autocomplete_result(choices=choices) + choices = [o if isinstance(o, OptionChoice) else OptionChoice(o) for o in result][:25] + return await interaction.response.send_autocomplete_result(choices=choices) def copy(self): """Creates a copy of this command. @@ -1230,7 +1218,7 @@ def __init__( self.description_localizations: dict[str, str] = kwargs.get("description_localizations", MISSING) # similar to ApplicationCommand - from ..ext.commands.cooldowns import BucketType, CooldownMapping, MaxConcurrency # noqa: PLC0415 + from ..ext.commands.cooldowns import BucketType, CooldownMapping, MaxConcurrency # no need to getattr, since slash cmds groups cant be created using a decorator @@ -1416,11 +1404,10 @@ async def _invoke(self, ctx: ApplicationContext) -> None: ctx.interaction.data = option await command.invoke(ctx) - async def invoke_autocomplete_callback(self, ctx: AutocompleteContext) -> None: - option = ctx.interaction.data["options"][0] + async def invoke_autocomplete_callback(self, interaction: AutocompleteInteraction) -> None: + option = interaction.data["options"][0] command = find(lambda x: x.name == option["name"], self.subcommands) - ctx.interaction.data = option - await command.invoke_autocomplete_callback(ctx) + await command.invoke_autocomplete_callback(interaction) async def call_before_hooks(self, ctx: ApplicationContext) -> None: # only call local hooks diff --git a/discord/commands/options.py b/discord/commands/options.py index a055022830..0bb5a30f95 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -33,24 +33,34 @@ from typing import ( TYPE_CHECKING, Any, + Generic, Literal, Optional, + Sequence, Type, - TypeVar, Union, get_args, + overload, ) +from typing_extensions import TypeAlias, TypeVar, override + +from discord.interactions import AutocompleteInteraction, Interaction + +from ..utils.private import maybe_awaitable + if sys.version_info >= (3, 12): from typing import TypeAliasType else: from typing_extensions import TypeAliasType -from ..abc import GuildChannel, Mentionable +from ..abc import Mentionable from ..channel import ( + BaseChannel, CategoryChannel, DMChannel, ForumChannel, + GuildChannel, MediaChannel, StageChannel, TextChannel, @@ -71,36 +81,26 @@ from ..user import User InputType = ( - Type[str] - | Type[bool] - | Type[int] - | Type[float] - | Type[GuildChannel] - | Type[Thread] - | Type[Member] - | Type[User] - | Type[Attachment] - | Type[Role] - | Type[Mentionable] + type[ + str | bool | int | float | GuildChannel | Thread | Member | User | Attachment | Role | Mentionable + # | Converter + ] | SlashCommandOptionType - | Converter - | Type[Converter] - | Type[Enum] - | Type[DiscordEnum] + # | Converter ) AutocompleteReturnType = Iterable["OptionChoice"] | Iterable[str] | Iterable[int] | Iterable[float] - T = TypeVar("T", bound=AutocompleteReturnType) - MaybeAwaitable = T | Awaitable[T] - AutocompleteFunction = ( - Callable[[AutocompleteContext], MaybeAwaitable[AutocompleteReturnType]] - | Callable[[Cog, AutocompleteContext], MaybeAwaitable[AutocompleteReturnType]] + AR_T = TypeVar("AR_T", bound=AutocompleteReturnType) + MaybeAwaitable = AR_T | Awaitable[AR_T] + AutocompleteFunction: TypeAlias = ( + Callable[[AutocompleteInteraction], MaybeAwaitable[AutocompleteReturnType]] + | Callable[[Any, AutocompleteInteraction], MaybeAwaitable[AutocompleteReturnType]] | Callable[ - [AutocompleteContext, Any], # pyright: ignore [reportExplicitAny] + [AutocompleteInteraction, Any], MaybeAwaitable[AutocompleteReturnType], ] | Callable[ - [Cog, AutocompleteContext, Any], # pyright: ignore [reportExplicitAny] + [Any, AutocompleteInteraction, Any], MaybeAwaitable[AutocompleteReturnType], ] ) @@ -110,7 +110,6 @@ "ThreadOption", "Option", "OptionChoice", - "option", ) CHANNEL_TYPE_MAP = { @@ -147,7 +146,21 @@ def __init__(self, thread_type: Literal["public", "private", "news"]): self._type = type_map[thread_type] -class Option: +T = TypeVar("T", bound="str | int | float", default="str") + + +class ApplicationCommandOptionAutocomplete: + def __init__(self, autocomplete_function: AutocompleteFunction) -> None: + self.autocomplete_function: AutocompleteFunction = autocomplete_function + self.self: Any | None = None + + async def __call__(self, interaction: AutocompleteInteraction) -> AutocompleteReturnType: + if self.self is not None: + return await maybe_awaitable(self.autocomplete_function(self.self, interaction)) + return await maybe_awaitable(self.autocomplete_function(interaction)) + + +class Option(Generic[T]): # TODO: Update docstring @Paillat-dev """Represents a selectable option for a slash command. Attributes @@ -211,198 +224,356 @@ async def hello( .. versionadded:: 2.0 """ - input_type: SlashCommandOptionType - converter: Converter | type[Converter] | None = None - - def __init__(self, input_type: InputType = str, /, description: str | None = None, **kwargs) -> None: - self.name: str | None = kwargs.pop("name", None) - if self.name is not None: - self.name = str(self.name) - self._parameter_name = self.name # default - input_type = self._parse_type_alias(input_type) - input_type = self._strip_none_type(input_type) - self._raw_type: InputType | tuple = input_type - - enum_choices = [] - input_type_is_class = isinstance(input_type, type) - if input_type_is_class and issubclass(input_type, (Enum, DiscordEnum)): - if description is None and input_type.__doc__ is not None: - description = inspect.cleandoc(input_type.__doc__) - if description and len(description) > 100: - description = description[:97] + "..." - _log.warning( - "Option %s's description was truncated due to Enum %s's docstring exceeding 100 characters.", - self.name, - input_type, - ) - enum_choices = [OptionChoice(e.name, e.value) for e in input_type] - value_class = enum_choices[0].value.__class__ - if value_class in SlashCommandOptionType.__members__ and all( - isinstance(elem.value, value_class) for elem in enum_choices - ): - input_type = SlashCommandOptionType.from_datatype(enum_choices[0].value.__class__) - else: - enum_choices = [OptionChoice(e.name, str(e.value)) for e in input_type] - input_type = SlashCommandOptionType.string - - self.description = description or "No description provided" - self.channel_types: list[ChannelType] = kwargs.pop("channel_types", []) - - if self.channel_types: - self.input_type = SlashCommandOptionType.channel - elif isinstance(input_type, SlashCommandOptionType): - self.input_type = input_type - else: - from ..ext.commands import Converter # noqa: PLC0415 - - if isinstance(input_type, tuple) and any(issubclass(op, ApplicationContext) for op in input_type): - input_type = next(op for op in input_type if issubclass(op, ApplicationContext)) - - if isinstance(input_type, Converter) or input_type_is_class and issubclass(input_type, Converter): - self.converter = input_type - self._raw_type = str - self.input_type = SlashCommandOptionType.string - else: - try: - self.input_type = SlashCommandOptionType.from_datatype(input_type) - except TypeError as exc: - from ..ext.commands.converter import CONVERTER_MAPPING # noqa: PLC0415 - - if input_type not in CONVERTER_MAPPING: - raise exc - self.converter = CONVERTER_MAPPING[input_type] - self._raw_type = str - self.input_type = SlashCommandOptionType.string - else: - if self.input_type == SlashCommandOptionType.channel: - if not isinstance(self._raw_type, tuple): - if hasattr(input_type, "__args__"): - self._raw_type = input_type.__args__ # type: ignore # Union.__args__ - else: - self._raw_type = (input_type,) - if not self.channel_types: - self.channel_types = [CHANNEL_TYPE_MAP[t] for t in self._raw_type if t is not GuildChannel] - self.required: bool = kwargs.pop("required", True) if "default" not in kwargs else False - self.default = kwargs.pop("default", None) - - self._autocomplete: AutocompleteFunction | None = None - self.autocomplete = kwargs.pop("autocomplete", None) - if len(enum_choices) > 25: - self.choices: list[OptionChoice] = [] - for e in enum_choices: - e.value = str(e.value) - self.autocomplete = basic_autocomplete(enum_choices) - self.input_type = SlashCommandOptionType.string - else: - self.choices: list[OptionChoice] = enum_choices or [ - o if isinstance(o, OptionChoice) else OptionChoice(o) for o in kwargs.pop("choices", []) - ] - - if self.input_type == SlashCommandOptionType.integer: - minmax_types = (int, type(None)) - minmax_typehint = Optional[int] # noqa: UP045 - elif self.input_type == SlashCommandOptionType.number: - minmax_types = (int, float, type(None)) - minmax_typehint = Optional[int | float] # noqa: UP045 - else: - minmax_types = (type(None),) - minmax_typehint = type(None) - - if self.input_type == SlashCommandOptionType.string: - minmax_length_types = (int, type(None)) - minmax_length_typehint = Optional[int] # noqa: UP045 - else: - minmax_length_types = (type(None),) - minmax_length_typehint = type(None) - - self.min_value: int | float | None = kwargs.pop("min_value", None) - self.max_value: int | float | None = kwargs.pop("max_value", None) - self.min_length: int | None = kwargs.pop("min_length", None) - self.max_length: int | None = kwargs.pop("max_length", None) - - if ( - self.input_type != SlashCommandOptionType.integer - and self.input_type != SlashCommandOptionType.number - and (self.min_value or self.max_value) - ): - raise AttributeError( - "Option does not take min_value or max_value if not of type " - "SlashCommandOptionType.integer or SlashCommandOptionType.number" - ) - if self.input_type != SlashCommandOptionType.string and (self.min_length or self.max_length): - raise AttributeError("Option does not take min_length or max_length if not of type str") + # Overload for options with choices (str, int, or float types) + @overload + def __init__( + self, + name: str, + input_type: type[T] = str, + *, + choices: Sequence[OptionChoice[T]], + description: str | None = None, + channel_types: None = None, + required: bool = ..., + default: Any | Undefined = ..., + min_value: None = None, + max_value: None = None, + min_length: None = None, + max_length: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... + + # Overload for channel options with optional channel_types filter + @overload + def __init__( + self, + name: str, + input_type: type[GuildChannel | Thread] + | Literal[SlashCommandOptionType.channel] = SlashCommandOptionType.channel, + *, + choices: None = None, + description: str | None = None, + channel_types: Sequence[ChannelType] | None = None, + required: bool = ..., + default: Any | Undefined = ..., + min_value: None = None, + max_value: None = None, + min_length: None = None, + max_length: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... + + # Overload for required string options with min_length/max_length constraints + @overload + def __init__( + self, + name: str, + input_type: type[str] | Literal[SlashCommandOptionType.string] = str, + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: Literal[True], + default: Undefined = MISSING, + min_length: int | None = None, + max_length: int | None = None, + min_value: None = None, + max_value: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... + + # Overload for optional string options with default value and min_length/max_length constraints + @overload + def __init__( + self, + name: str, + input_type: type[str] | Literal[SlashCommandOptionType.string] = str, + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: bool = False, + default: Any, + min_length: int | None = None, + max_length: int | None = None, + min_value: None = None, + max_value: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... + + # Overload for required integer options with min_value/max_value constraints (integers only) + @overload + def __init__( + self, + name: str, + input_type: type[int] | Literal[SlashCommandOptionType.integer], + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: Literal[True], + default: Undefined = MISSING, + min_value: int | None = None, + max_value: int | None = None, + min_length: None = None, + max_length: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... + + # Overload for optional integer options with default value and min_value/max_value constraints (integers only) + @overload + def __init__( + self, + name: str, + input_type: type[int] | Literal[SlashCommandOptionType.integer], + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: bool = False, + default: Any, + min_value: int | None = None, + max_value: int | None = None, + min_length: None = None, + max_length: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... + + # Overload for required float options with min_value/max_value constraints (integers or floats) + @overload + def __init__( + self, + name: str, + input_type: type[float] | Literal[SlashCommandOptionType.number], + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: Literal[True], + default: Undefined = MISSING, + min_value: int | float | None = None, + max_value: int | float | None = None, + min_length: None = None, + max_length: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... + + # Overload for optional float options with default value and min_value/max_value constraints (integers or floats) + @overload + def __init__( + self, + name: str, + input_type: type[float] | Literal[SlashCommandOptionType.number], + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: bool = False, + default: Any, + min_value: int | float | None = None, + max_value: int | float | None = None, + min_length: None = None, + max_length: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... + + # Overload for required options with autocomplete (no choices or min/max constraints allowed) + @overload + def __init__( + self, + name: str, + input_type: type[str | int | float] = str, + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: Literal[True], + default: Undefined = MISSING, + min_value: None = None, + max_value: None = None, + min_length: None = None, + max_length: None = None, + autocomplete: ApplicationCommandOptionAutocomplete, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + ) -> None: ... + + # Overload for optional options with autocomplete and default value (no choices or min/max constraints allowed) + @overload + def __init__( + self, + name: str, + input_type: type[str | int | float] = str, + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: bool = False, + default: Any, + min_value: None = None, + max_value: None = None, + min_length: None = None, + max_length: None = None, + autocomplete: ApplicationCommandOptionAutocomplete, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + ) -> None: ... + + # Overload for required options of other types (bool, User, Member, Role, Attachment, Mentionable, etc.) + @overload + def __init__( + self, + name: str, + input_type: type[T] = str, + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: Literal[True], + default: Undefined = MISSING, + min_value: None = None, + max_value: None = None, + min_length: None = None, + max_length: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... + + # Overload for optional options of other types with default value (bool, User, Member, Role, Attachment, Mentionable, etc.) + @overload + def __init__( + self, + name: str, + input_type: type[T] = str, + *, + description: str | None = None, + choices: None = None, + channel_types: None = None, + required: bool = False, + default: Any, + min_value: None = None, + max_value: None = None, + min_length: None = None, + max_length: None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: None = None, + ) -> None: ... - if self.min_value is not None and not isinstance(self.min_value, minmax_types): - raise TypeError(f'Expected {minmax_typehint} for min_value, got "{type(self.min_value).__name__}"') - if self.max_value is not None and not isinstance(self.max_value, minmax_types): - raise TypeError(f'Expected {minmax_typehint} for max_value, got "{type(self.max_value).__name__}"') + def __init__( + self, + name: str, + input_type: InputType | type[T] = str, + *, + description: str | None = None, + choices: Sequence[OptionChoice[T]] | None = None, + channel_types: Sequence[ChannelType] | None = None, + required: bool = True, + default: Any | Undefined = MISSING, + min_value: int | float | None = None, + max_value: int | float | None = None, + min_length: int | None = None, + max_length: int | None = None, + name_localizations: dict[str, str] | None = None, + description_localizations: dict[str, str] | None = None, + autocomplete: ApplicationCommandOptionAutocomplete | None = None, + ) -> None: + self.name: str = name + + self.description: str | None = description + + self.choices: list[OptionChoice[T]] | None = list(choices) if choices is not None else None + if self.choices is not None: + if len(self.choices) > 25: + raise ValueError("Option choices cannot exceed 25 items.") + if not issubclass(input_type, str | int | float): + raise TypeError("Option choices can only be used with str, int, or float input types.") + + self.channel_types: list[ChannelType] | None = list(channel_types) if channel_types is not None else None + + self.input_type: SlashCommandOptionType - if self.min_length is not None: - if not isinstance(self.min_length, minmax_length_types): - raise TypeError( - f'Expected {minmax_length_typehint} for min_length, got "{type(self.min_length).__name__}"' - ) - if self.min_length < 0 or self.min_length > 6000: - raise AttributeError("min_length must be between 0 and 6000 (inclusive)") - if self.max_length is not None: - if not isinstance(self.max_length, minmax_length_types): - raise TypeError( - f'Expected {minmax_length_typehint} for max_length, got "{type(self.max_length).__name__}"' - ) - if self.max_length < 1 or self.max_length > 6000: - raise AttributeError("max_length must between 1 and 6000 (inclusive)") - - self.name_localizations = kwargs.pop("name_localizations", MISSING) - self.description_localizations = kwargs.pop("description_localizations", MISSING) - - if input_type is None: - raise TypeError("input_type cannot be NoneType.") - - @staticmethod - def _parse_type_alias(input_type: InputType) -> InputType: - if isinstance(input_type, TypeAliasType): - return input_type.__value__ - return input_type - - @staticmethod - def _strip_none_type(input_type): if isinstance(input_type, SlashCommandOptionType): - return input_type + self.input_type = input_type + elif issubclass(input_type, str): + self.input_type = SlashCommandOptionType.string + elif issubclass(input_type, bool): + self.input_type = SlashCommandOptionType.boolean + elif issubclass(input_type, int): + self.input_type = SlashCommandOptionType.integer + elif issubclass(input_type, float): + self.input_type = SlashCommandOptionType.number + elif issubclass(input_type, Attachment): + self.input_type = SlashCommandOptionType.attachment + elif issubclass(input_type, User | Member): + self.input_type = SlashCommandOptionType.user + elif issubclass(input_type, Role): + self.input_type = SlashCommandOptionType.role + elif issubclass(input_type, GuildChannel | Thread): + self.input_type = SlashCommandOptionType.channel + elif issubclass(input_type, Mentionable): + self.input_type = SlashCommandOptionType.mentionable - if input_type is type(None): - raise TypeError("Option type cannot be only NoneType") + self.required: bool = required if default is MISSING else False + self.default: Any | Undefined = default - args = () - if isinstance(input_type, types.UnionType): - args = get_args(input_type) - elif getattr(input_type, "__origin__", None) is Union: - args = get_args(input_type) - elif isinstance(input_type, tuple): - args = input_type + self.autocomplete: ApplicationCommandOptionAutocomplete | None = autocomplete - if args: - filtered = tuple(t for t in args if t is not type(None)) - if not filtered: - raise TypeError("Option type cannot be only NoneType") - if len(filtered) == 1: - return filtered[0] + self.min_value: int | float | None = min_value + self.max_value: int | float | None = max_value + if self.input_type not in (SlashCommandOptionType.integer, SlashCommandOptionType.number) and ( + self.min_value is not None or self.max_value is not None + ): + raise TypeError( + f"min_value and max_value can only be used with int or float input types, not {self.input_type.name}" + ) + if self.input_type is not SlashCommandOptionType.integer and ( + isinstance(self.min_value, float) or isinstance(self.max_value, float) + ): + raise TypeError("min_value and max_value must be integers when input_type is integer") - return filtered + self.min_length: int | None = min_length + self.max_length: int | None = max_length + if self.input_type is not SlashCommandOptionType.string and ( + self.min_length is not None or self.max_length is not None + ): + raise TypeError( + f"min_length and max_length can only be used with str input type, not {self.input_type.name}" + ) - return input_type + self.name_localizations: dict[str, str] | None = name_localizations + self.description_localizations: dict[str, str] | None = description_localizations - def to_dict(self) -> dict: - as_dict = { + def to_dict(self) -> dict[str, Any]: + as_dict: dict[str, Any] = { "name": self.name, "description": self.description, "type": self.input_type.value, "required": self.required, - "choices": [c.to_dict() for c in self.choices], "autocomplete": bool(self.autocomplete), } - if self.name_localizations is not MISSING: + if self.choices: + as_dict["choices"] = [choice.to_dict() for choice in self.choices] + if self.name_localizations: as_dict["name_localizations"] = self.name_localizations - if self.description_localizations is not MISSING: + if self.description_localizations: as_dict["description_localizations"] = self.description_localizations if self.channel_types: as_dict["channel_types"] = [t.value for t in self.channel_types] @@ -417,46 +588,12 @@ def to_dict(self) -> dict: return as_dict + @override def __repr__(self): - return f"" - - @property - def autocomplete(self) -> AutocompleteFunction | None: - """ - The autocomplete handler for the option. Accepts a callable (sync or async) - that takes a single required argument of :class:`AutocompleteContext` or two arguments - of :class:`discord.Cog` (being the command's cog) and :class:`AutocompleteContext`. - The callable must return an iterable of :class:`str` or :class:`OptionChoice`. - Alternatively, :func:`discord.utils.basic_autocomplete` may be used in place of the callable. - - Returns - ------- - Optional[AutocompleteFunction] - - .. versionchanged:: 2.7 - - .. note:: - Does not validate the input value against the autocomplete results. - """ - return self._autocomplete - - @autocomplete.setter - def autocomplete(self, value: AutocompleteFunction | None) -> None: - self._autocomplete = value - # this is done here so it does not have to be computed every time the autocomplete is invoked - if self._autocomplete is not None: - self._autocomplete._is_instance_method = ( # pyright: ignore [reportFunctionMemberAccess] - sum( - 1 - for param in inspect.signature(self._autocomplete).parameters.values() - if param.default == param.empty # pyright: ignore[reportAny] - and param.kind not in (param.VAR_POSITIONAL, param.VAR_KEYWORD) - ) - == 2 - ) + return f"