4 Commits

12 changed files with 117 additions and 108 deletions

View File

@ -1434,7 +1434,7 @@ class Messageable:
components=components,
)
ret = state.store_message(channel=channel, data=data)
ret = state.create_message(channel=channel, data=data)
if view:
state.store_view(view, ret.id)
@ -1501,7 +1501,7 @@ class Messageable:
channel = await self._get_channel()
data = await self._state.http.get_message(channel.id, id)
return self._state.store_message(channel=channel, data=data)
return self._state.create_message(channel=channel, data=data)
async def pins(self) -> List[Message]:
"""|coro|
@ -1528,7 +1528,7 @@ class Messageable:
channel = await self._get_channel()
state = self._state
data = await state.http.pins_from(channel.id)
return [state.store_message(channel=channel, data=m) for m in data]
return [state.create_message(channel=channel, data=m) for m in data]
def history(
self,

View File

@ -53,7 +53,7 @@ from .widget import Widget
from .guild import Guild
from .emoji import Emoji
from .channel import _threaded_channel_factory, PartialMessageable
from .enums import ChannelType, StickerType
from .enums import ChannelType
from .mentions import AllowedMentions
from .errors import *
from .enums import Status, VoiceRegion
@ -76,8 +76,7 @@ from .threads import Thread
from .sticker import GuildSticker, StandardSticker, StickerPack, _sticker_factory
if TYPE_CHECKING:
from .abc import SnowflakeTime, PrivateChannel, GuildChannel as GuildChannelABC, Snowflake
from .guild import GuildChannel
from .abc import SnowflakeTime, PrivateChannel, GuildChannel, Snowflake
from .channel import DMChannel
from .message import Message
from .member import Member
@ -781,7 +780,7 @@ class Client:
"""List[:class:`~discord.User`]: Returns a list of all the users the bot can see."""
return list(self._connection._users.values())
def get_channel(self, id: int, /) -> Optional[Union[GuildChannelABC, Thread, PrivateChannel]]:
def get_channel(self, id: int, /) -> Optional[Union[GuildChannel, Thread, PrivateChannel]]:
"""Returns a channel or thread with the given ID.
Parameters
@ -934,7 +933,7 @@ class Client:
"""
return self._connection.get_sticker(id)
def get_all_channels(self) -> Generator[GuildChannelABC, None, None]:
def get_all_channels(self) -> Generator[GuildChannel, None, None]:
"""A generator that retrieves every :class:`.abc.GuildChannel` the client can 'access'.
This is equivalent to: ::
@ -1489,7 +1488,6 @@ class Client:
"""|coro|
Retrieves the bot's application information.
This will fill up :attr:`application_id` and :attr:`application_flags`.
Raises
-------
@ -1504,8 +1502,6 @@ class Client:
data = await self.http.application_info()
if "rpc_origins" not in data:
data["rpc_origins"] = None
self._connection.store_appinfo(data)
return AppInfo(self._connection, data)
async def fetch_user(self, user_id: int, /) -> User:
@ -1539,11 +1535,10 @@ class Client:
data = await self.http.get_user(user_id)
return User(state=self._connection, data=data)
async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannelABC, PrivateChannel, Thread]:
async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, PrivateChannel, Thread]:
"""|coro|
Retrieves a :class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, or :class:`.Thread` with the specified ID.
If found, will store the Channel in the internal cache, meaning :meth:``get_channel`` will succeed afterwards.
.. note::
@ -1575,18 +1570,14 @@ class Client:
if ch_type in (ChannelType.group, ChannelType.private):
# the factory will be a DMChannel or GroupChannel here
channel: PrivateChannel = factory(me=self.user, data=data, state=self._connection) # type: ignore
self._connection._add_private_channel(channel) # type: ignore
channel = factory(me=self.user, data=data, state=self._connection) # type: ignore
else:
# the factory can't be a DMChannel or GroupChannel here
guild_id = int(data["guild_id"]) # type: ignore
guild = self.get_guild(guild_id)
guild = self.get_guild(guild_id) or Object(id=guild_id)
# GuildChannels expect a Guild, we may be passing an Object
channel = factory(guild=guild, state=self._connection, data=data) # type: ignore
if guild is None:
return factory(guild=Object(guild_id), state=self._connection, data=data) # type: ignore
channel: GuildChannel = factory(guild=guild, state=self._connection, data=data) # type: ignore
guild._add_channel(channel)
return channel
async def fetch_webhook(self, webhook_id: int, /) -> Webhook:
@ -1615,7 +1606,6 @@ class Client:
"""|coro|
Retrieves a :class:`.Sticker` with the specified ID.
If found, will store the sticker in the internal cache, meaning :meth:``get_sticker`` will succeed afterwards.
.. versionadded:: 2.0
@ -1633,11 +1623,7 @@ class Client:
"""
data = await self.http.get_sticker(sticker_id)
cls, _ = _sticker_factory(data["type"]) # type: ignore
if data["type"] == StickerType.guild: # type: ignore
return self._connection.store_sticker(data) # type: ignore
else:
return cls(state=self._connection, data=data) # type: ignore
return cls(state=self._connection, data=data) # type: ignore
async def fetch_premium_sticker_packs(self) -> List[StickerPack]:
"""|coro|

View File

@ -465,7 +465,6 @@ class Context(discord.abc.Messageable, Generic[BotT]):
kwargs.pop("nonce", None)
kwargs.pop("stickers", None)
kwargs.pop("reference", None)
kwargs.pop("delete_after", None)
kwargs.pop("mention_author", None)
if not (

View File

@ -1694,9 +1694,7 @@ class Group(GroupMixin[CogT], Command[CogT, P, T]):
"name": self.name,
"type": int(not (nested - 1)) + 1,
"description": self.short_doc or "no description",
"options": [
cmd.to_application_command(nested=nested + 1) for cmd in sorted(self.commands, key=lambda x: x.name)
],
"options": [cmd.to_application_command(nested=nested + 1) for cmd in sorted(self.commands, key=lambda x: x.name)],
}

View File

@ -455,7 +455,7 @@ class BadInviteArgument(BadArgument):
This inherits from :exc:`BadArgument`
.. versionadded:: 1.5
Attributes
-----------
argument: :class:`str`

View File

@ -951,15 +951,6 @@ class MemberCacheFlags(BaseFlags):
"""
return 2
@flag_value
def fetched(self):
""":class:`bool`: Whether to cache members that are fetched via :meth:``Guild.fetch_member``
or :meth:``Guild.fetch_members``
Members that leave the guild are no longer cached.
"""
return 4
@classmethod
def from_intents(cls: Type[MemberCacheFlags], intents: Intents) -> MemberCacheFlags:
"""A factory method that creates a :class:`MemberCacheFlags` based on
@ -977,8 +968,6 @@ class MemberCacheFlags(BaseFlags):
"""
self = cls.none()
self.fetched = True
if intents.members:
self.joined = True
if intents.voice_states:

View File

@ -428,7 +428,7 @@ class Guild(Hashable):
self.mfa_level: MFALevel = guild.get("mfa_level")
self.emojis: Tuple[Emoji, ...] = tuple(map(lambda d: state.store_emoji(self, d), guild.get("emojis", [])))
self.stickers: Tuple[GuildSticker, ...] = tuple(
map(lambda d: state.store_sticker(d), guild.get("stickers", []))
map(lambda d: state.store_sticker(self, d), guild.get("stickers", []))
)
self.features: List[GuildFeature] = guild.get("features", [])
self._splash: Optional[str] = guild.get("splash")
@ -1594,7 +1594,6 @@ class Guild(Hashable):
"""|coro|
Retrieves all :class:`abc.GuildChannel` that the guild has.
Will store the Channels in the internal cache, meaning :meth:``get_channel`` will succeed afterwards.
.. note::
@ -1617,12 +1616,11 @@ class Guild(Hashable):
data = await self._state.http.get_all_guild_channels(self.id)
def convert(d):
factory, _ = _guild_channel_factory(d["type"])
factory, ch_type = _guild_channel_factory(d["type"])
if factory is None:
raise InvalidData("Unknown channel type {type} for channel ID {id}.".format_map(d))
channel = factory(guild=self, state=self._state, data=d)
self._add_channel(channel)
return channel
return [convert(d) for d in data]
@ -1714,8 +1712,6 @@ class Guild(Hashable):
"""|coro|
Retrieves a :class:`Member` from a guild ID, and a member ID.
If found, will store the Member in the internal cache, filling up :attr:`members`
and meaning :meth:``get_member`` will succeed afterwards.
.. note::
@ -1741,11 +1737,7 @@ class Guild(Hashable):
The member from the member ID.
"""
data = await self._state.http.get_member(self.id, member_id)
member = Member(data=data, state=self._state, guild=self)
if self._state.member_cache_flags.fetched:
self._add_member(member)
return member
return Member(data=data, state=self._state, guild=self)
async def try_member(self, member_id: int, /) -> Optional[Member]:
"""|coro|
@ -2265,7 +2257,7 @@ class Guild(Hashable):
payload["tags"] = emoji
data = await self._state.http.create_guild_sticker(self.id, payload, file, reason)
return self._state.store_sticker(data)
return self._state.store_sticker(self, data)
async def delete_sticker(self, sticker: Snowflake, *, reason: Optional[str] = None) -> None:
"""|coro|

View File

@ -137,7 +137,7 @@ class Interaction:
self.message: Optional[Message]
try:
self.message = self._state.store_message(channel=self.channel, data=data["message"]) # type: ignore
self.message = Message(state=self._state, channel=self.channel, data=data["message"]) # type: ignore
except KeyError:
self.message = None
@ -337,7 +337,7 @@ class Interaction:
self._state.store_view(view, message.id)
return message
async def delete_original_message(self) -> None:
async def delete_original_message(self, delay: Optional[float] = None) -> None:
"""|coro|
Deletes the original interaction response message.
@ -345,6 +345,14 @@ class Interaction:
This is a lower level interface to :meth:`InteractionMessage.delete` in case
you do not want to fetch the message and save an HTTP request.
Parameters
------------
delay: Optional[:class:`float`]
If provided, the number of seconds to wait before deleting the message.
The waiting is done in the background and deletion failures are ignored.
.. versionadded:: 2.0
Raises
-------
HTTPException
@ -352,6 +360,8 @@ class Interaction:
Forbidden
Deleted a message that is not yours.
"""
if delay is not None:
await asyncio.sleep(delay)
adapter = async_context.get()
await adapter.delete_original_interaction_response(
self.application_id,
@ -460,6 +470,7 @@ class InteractionResponse:
view: View = MISSING,
tts: bool = False,
ephemeral: bool = False,
delete_after: Optional[float] = None,
) -> None:
"""|coro|
@ -483,6 +494,9 @@ class InteractionResponse:
Indicates if the message should only be visible to the user who started the interaction.
If a view is sent with an ephemeral message and it has no timeout set then the timeout
is set to 15 minutes.
delete_after: Optional[:class:`float`]
The amount of seconds the bot should wait before deleting the message sent.
.. versionadded:: 2.0
Raises
-------
@ -539,6 +553,8 @@ class InteractionResponse:
self._parent._state.store_view(view)
self.responded_at = utils.utcnow()
if delete_after is not None:
self._parent.delete_original_message(delay=delete_after)
async def edit_message(
self,
@ -733,7 +749,7 @@ class InteractionMessage(Message):
allowed_mentions=allowed_mentions,
)
async def delete(self, *, delay: Optional[float] = None) -> None:
async def delete(self, *, delay: Optional[float] = None, silent: bool = False) -> None:
"""|coro|
Deletes the message.
@ -743,6 +759,12 @@ class InteractionMessage(Message):
delay: Optional[:class:`float`]
If provided, the number of seconds to wait before deleting the message.
The waiting is done in the background and deletion failures are ignored.
silent: :class:`bool`
If silent is set to ``True``, the error will not be raised, it will be ignored.
This defaults to ``False`
.. versionadded:: 2.0
Raises
------
@ -757,12 +779,15 @@ class InteractionMessage(Message):
if delay is not None:
async def inner_call(delay: float = delay):
await asyncio.sleep(delay)
try:
await self._state._interaction.delete_original_message()
await self._state._interaction.delete_original_message(delay=delay)
except HTTPException:
pass
asyncio.create_task(inner_call())
else:
await self._state._interaction.delete_original_message()
try:
await self._state._interaction.delete_original_message(delay=delay)
except Exception:
if not silent:
raise

View File

@ -42,12 +42,22 @@ __all__ = (
)
if TYPE_CHECKING:
from .types.member import MemberWithUser as MemberWithUserPayload
from .types.user import PartialUser as PartialUserPayload
from .types.audit_log import AuditLog as AuditLogPayload
from .types.message import Message as MessagePayload
from .types.threads import Thread as ThreadPayload
from .types.guild import Guild as GuildPayload
from .types.audit_log import (
AuditLog as AuditLogPayload,
)
from .types.guild import (
Guild as GuildPayload,
)
from .types.message import (
Message as MessagePayload,
)
from .types.user import (
PartialUser as PartialUserPayload,
)
from .types.threads import (
Thread as ThreadPayload,
)
from .member import Member
from .user import User
@ -344,7 +354,7 @@ class HistoryIterator(_AsyncIterator["Message"]):
channel = self.channel
for element in data:
await self.messages.put(self.state.store_message(channel=channel, data=element))
await self.messages.put(self.state.create_message(channel=channel, data=element))
async def _retrieve_messages(self, retrieve) -> List[Message]:
"""Retrieve messages and update next parameters."""
@ -605,18 +615,14 @@ class MemberIterator(_AsyncIterator["Member"]):
if isinstance(after, datetime.datetime):
after = Object(id=time_snowflake(after, high=True))
self.guild = guild
self.limit = limit
self.guild: Guild = guild
self.after = after or OLDEST_OBJECT
self.state = self.guild._state
self.get_members = self.state.http.get_members
self.members = asyncio.Queue()
self.create_member = (
self.create_member_cache if self.state.member_cache_flags.fetched else self.create_member_no_cache
)
async def next(self) -> Member:
if self.members.empty():
await self.fill_members()
@ -651,16 +657,11 @@ class MemberIterator(_AsyncIterator["Member"]):
for element in reversed(data):
await self.members.put(self.create_member(element))
def create_member_no_cache(self, data: MemberWithUserPayload) -> Member:
def create_member(self, data):
from .member import Member
return Member(data=data, guild=self.guild, state=self.state)
def create_member_cache(self, data: MemberWithUserPayload) -> Member:
member = self.create_member_no_cache(data)
self.guild._add_member(member)
return member
class ArchivedThreadIterator(_AsyncIterator["Thread"]):
def __init__(

View File

@ -1331,7 +1331,7 @@ class Message(Hashable):
payload["components"] = []
data = await self._state.http.edit_message(self.channel.id, self.id, **payload)
message = self._state.store_message(channel=self.channel, data=data)
message = Message(state=self._state, channel=self.channel, data=data)
if view and not view.is_finished():
self._state.store_view(view, self.id)
@ -1756,7 +1756,7 @@ class PartialMessage(Hashable):
"""
data = await self._state.http.get_message(self.channel.id, self.id)
return self._state.store_message(channel=self.channel, data=data)
return self._state.create_message(channel=self.channel, data=data)
async def edit(self, **fields: Any) -> Optional[Message]:
"""|coro|
@ -1873,7 +1873,7 @@ class PartialMessage(Hashable):
if fields:
# data isn't unbound
msg = self._state.store_message(channel=self.channel, data=data) # type: ignore
msg = self._state.create_message(channel=self.channel, data=data) # type: ignore
if view and not view.is_finished():
self._state.store_view(view, self.id)
return msg

View File

@ -27,6 +27,7 @@ from __future__ import annotations
import asyncio
from collections import deque, OrderedDict
import copy
import datetime
import itertools
import logging
from typing import Dict, Optional, TYPE_CHECKING, Union, Callable, Any, List, TypeVar, Coroutine, Sequence, Tuple, Deque
@ -74,7 +75,6 @@ if TYPE_CHECKING:
from .types.sticker import GuildSticker as GuildStickerPayload
from .types.guild import Guild as GuildPayload
from .types.message import Message as MessagePayload
from .types.appinfo import AppInfo as AppInfoPayload
T = TypeVar("T")
CS = TypeVar("CS", bound="ConnectionState")
@ -323,13 +323,6 @@ class ConnectionState:
for vc in self.voice_clients:
vc.main_ws = ws # type: ignore
def store_message(self, channel: MessageableChannel, data: MessagePayload) -> Message:
message = Message(state=self, channel=channel, data=data)
if self._messages is not None:
self._messages.append(message)
return message
def store_user(self, data: UserPayload) -> User:
user_id = int(data["id"])
try:
@ -360,20 +353,11 @@ class ConnectionState:
self._emojis[emoji_id] = emoji = Emoji(guild=guild, state=self, data=data)
return emoji
def store_sticker(self, data: GuildStickerPayload) -> GuildSticker:
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
def store_appinfo(self, data: AppInfoPayload):
self.application_id = utils._get_as_snowflake(data, "id")
flags = data.get("flags")
if flags is not None:
self.application_flags = ApplicationFlags._from_value(flags)
return data
def store_view(self, view: View, message_id: Optional[int] = None) -> None:
self._view_store.add_view(view, message_id)
@ -579,7 +563,9 @@ class ConnectionState:
except KeyError:
pass
else:
self.store_appinfo(application)
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)
@ -595,7 +581,8 @@ class ConnectionState:
# channel would be the correct type here
message = Message(channel=channel, data=data, state=self) # type: ignore
self.dispatch("message", message)
self.store_message(channel, data)
if self._messages is not None:
self._messages.append(message)
# we ensure that the channel is either a TextChannel or Thread
if channel and channel.__class__ in (TextChannel, Thread):
channel.last_message_id = message.id # type: ignore
@ -1045,7 +1032,7 @@ class ConnectionState:
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(d), data["stickers"])) # type: ignore
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):
@ -1416,6 +1403,11 @@ class ConnectionState:
if channel is not None:
return channel
def create_message(
self, *, channel: Union[TextChannel, Thread, DMChannel, GroupChannel, PartialMessageable], data: MessagePayload
) -> Message:
return Message(state=self, channel=channel, data=data)
class AutoShardedConnectionState(ConnectionState):
def __init__(self, *args: Any, **kwargs: Any) -> None:

View File

@ -717,7 +717,7 @@ class WebhookMessage(Message):
allowed_mentions=allowed_mentions,
)
async def delete(self, *, delay: Optional[float] = None) -> None:
async def delete(self, *, delay: Optional[float] = None, silent: bool = False) -> None:
"""|coro|
Deletes the message.
@ -727,6 +727,12 @@ class WebhookMessage(Message):
delay: Optional[:class:`float`]
If provided, the number of seconds to wait before deleting the message.
The waiting is done in the background and deletion failures are ignored.
silent: :class:`bool`
If silent is set to ``True``, the error will not be raised, it will be ignored.
This defaults to ``False`
.. versionadded:: 2.0
Raises
------
@ -741,15 +747,18 @@ class WebhookMessage(Message):
if delay is not None:
async def inner_call(delay: float = delay):
await asyncio.sleep(delay)
try:
await self._state._webhook.delete_message(self.id)
await self._state._webhook.delete_message(self.id, delay)
except HTTPException:
pass
asyncio.create_task(inner_call())
else:
await self._state._webhook.delete_message(self.id)
try:
await self._state._webhook.delete_message(self.id, delay)
except Exception:
if not silent:
raise
class BaseWebhook(Hashable):
@ -1270,6 +1279,7 @@ class Webhook(BaseWebhook):
view: View = MISSING,
thread: Snowflake = MISSING,
wait: bool = False,
delete_after: Optional[float] = None,
) -> Optional[WebhookMessage]:
"""|coro|
@ -1335,6 +1345,11 @@ class Webhook(BaseWebhook):
The thread to send this webhook to.
.. versionadded:: 2.0
delete_after: Optional[:class:`float`]
If provided, the number of seconds to wait before deleting the message.
The waiting is done in the background and deletion failures are ignored.
.. versionadded:: 2.0
Raises
--------
@ -1416,6 +1431,9 @@ class Webhook(BaseWebhook):
if view is not MISSING and not view.is_finished():
message_id = None if msg is None else msg.id
self._state.store_view(view, message_id)
if delete_after is not None and msg is not None:
await msg.delete(delay=delete_after)
return msg
@ -1570,7 +1588,7 @@ class Webhook(BaseWebhook):
self._state.store_view(view, message_id)
return message
async def delete_message(self, message_id: int, /) -> None:
async def delete_message(self, message_id: int, delay: Optional[float] = None, /) -> None:
"""|coro|
Deletes a message owned by this webhook.
@ -1584,6 +1602,12 @@ class Webhook(BaseWebhook):
------------
message_id: :class:`int`
The message ID to delete.
delay: Optional[:class:`float`]
If provided, the number of seconds to wait before deleting the message.
The waiting is done in the background and deletion failures are ignored.
.. versionadded:: 2.0
Raises
-------
@ -1595,6 +1619,9 @@ class Webhook(BaseWebhook):
if self.token is None:
raise InvalidArgument("This webhook does not have a token associated with it")
if delay is not None:
await asyncio.sleep(delay)
adapter = async_context.get()
await adapter.delete_webhook_message(
self.id,