stormbrigade_sheriff/sbsheriff/Lib/site-packages/disnake/invite.py

590 lines
21 KiB
Python

# SPDX-License-Identifier: MIT
from __future__ import annotations
from typing import TYPE_CHECKING, List, Optional, Union
from .appinfo import PartialAppInfo
from .asset import Asset
from .enums import ChannelType, InviteTarget, NSFWLevel, VerificationLevel, try_enum
from .guild_scheduled_event import GuildScheduledEvent
from .mixins import Hashable
from .object import Object
from .utils import _get_as_snowflake, parse_time, snowflake_time
from .welcome_screen import WelcomeScreen
__all__ = (
"PartialInviteChannel",
"PartialInviteGuild",
"Invite",
)
if TYPE_CHECKING:
import datetime
from typing_extensions import Self
from .abc import GuildChannel
from .guild import Guild
from .state import ConnectionState
from .types.channel import (
GroupInviteRecipient as GroupInviteRecipientPayload,
InviteChannel as InviteChannelPayload,
)
from .types.gateway import InviteCreateEvent, InviteDeleteEvent
from .types.guild import GuildFeature
from .types.invite import Invite as InvitePayload, InviteGuild as InviteGuildPayload
from .user import User
GatewayInvitePayload = Union[InviteCreateEvent, InviteDeleteEvent]
InviteGuildType = Union[Guild, "PartialInviteGuild", Object]
InviteChannelType = Union[GuildChannel, "PartialInviteChannel", Object]
class PartialInviteChannel:
"""Represents a "partial" invite channel.
This model will be given when the user is not part of the
guild the :class:`Invite` resolves to.
.. container:: operations
.. describe:: x == y
Checks if two partial channels are the same.
.. describe:: x != y
Checks if two partial channels are not the same.
.. describe:: hash(x)
Return the partial channel's hash.
.. describe:: str(x)
Returns the partial channel's name.
.. versionchanged:: 2.5
if the channel is of type :attr:`ChannelType.group`,
returns the name that's rendered by the official client.
Attributes
----------
name: Optional[:class:`str`]
The partial channel's name.
id: :class:`int`
The partial channel's ID.
type: :class:`ChannelType`
The partial channel's type.
"""
__slots__ = (
"id",
"name",
"type",
"_recipients",
"_icon",
"_state",
)
def __init__(self, *, state: ConnectionState, data: InviteChannelPayload) -> None:
self._state = state
self.id: int = int(data["id"])
self.name: Optional[str] = data.get("name")
self.type: ChannelType = try_enum(ChannelType, data["type"])
if self.type is ChannelType.group:
self._recipients: List[GroupInviteRecipientPayload] = data.get("recipients", [])
else:
self._recipients = []
self._icon: Optional[str] = data.get("icon")
def __str__(self) -> str:
if self.name:
return self.name
if self.type is ChannelType.group:
return ", ".join([recipient["username"] for recipient in self._recipients]) or "Unnamed"
return ""
def __repr__(self) -> str:
return f"<PartialInviteChannel id={self.id} name={self.name} type={self.type!r}>"
@property
def mention(self) -> str:
""":class:`str`: The string that allows you to mention the channel."""
return f"<#{self.id}>"
@property
def created_at(self) -> datetime.datetime:
""":class:`datetime.datetime`: Returns the channel's creation time in UTC."""
return snowflake_time(self.id)
@property
def icon(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the channel's icon asset if available.
.. versionadded:: 2.6
"""
if self._icon is None:
return None
return Asset._from_icon(self._state, self.id, self._icon, path="channel")
class PartialInviteGuild:
"""Represents a "partial" invite guild.
This model will be given when the user is not part of the
guild the :class:`Invite` resolves to.
.. container:: operations
.. describe:: x == y
Checks if two partial guilds are the same.
.. describe:: x != y
Checks if two partial guilds are not the same.
.. describe:: hash(x)
Return the partial guild's hash.
.. describe:: str(x)
Returns the partial guild's name.
Attributes
----------
name: :class:`str`
The partial guild's name.
id: :class:`int`
The partial guild's ID.
description: Optional[:class:`str`]
The partial guild's description.
features: List[:class:`str`]
A list of features the partial guild has. See :attr:`Guild.features` for more information.
nsfw_level: :class:`NSFWLevel`
The partial guild's nsfw level.
.. versionadded:: 2.4
vanity_url_code: Optional[:class:`str`]
The partial guild's vanity url code, if any.
.. versionadded:: 2.4
verification_level: :class:`VerificationLevel`
The partial guild's verification level.
premium_subscription_count: :class:`int`
The number of "boosts" this guild currently has.
.. versionadded:: 2.5
"""
__slots__ = (
"_state",
"features",
"_icon",
"_banner",
"id",
"name",
"_splash",
"description",
"nsfw_level",
"vanity_url_code",
"verification_level",
"premium_subscription_count",
)
def __init__(self, state: ConnectionState, data: InviteGuildPayload, id: int) -> None:
self._state: ConnectionState = state
self.id: int = id
self.name: str = data["name"]
self.features: List[GuildFeature] = data.get("features", [])
self._icon: Optional[str] = data.get("icon")
self._banner: Optional[str] = data.get("banner")
self._splash: Optional[str] = data.get("splash")
self.nsfw_level: NSFWLevel = try_enum(NSFWLevel, data.get("nsfw_level", 0))
self.vanity_url_code: Optional[str] = data.get("vanity_url_code")
self.verification_level: VerificationLevel = try_enum(
VerificationLevel, data.get("verification_level")
)
self.description: Optional[str] = data.get("description")
self.premium_subscription_count: int = data.get("premium_subscription_count") or 0
def __str__(self) -> str:
return self.name
def __repr__(self) -> str:
return (
f"<{self.__class__.__name__} id={self.id} name={self.name!r} features={self.features} "
f"description={self.description!r}>"
)
@property
def created_at(self) -> datetime.datetime:
""":class:`datetime.datetime`: Returns the guild's creation time in UTC."""
return snowflake_time(self.id)
@property
def icon(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the guild's icon asset, if available."""
if self._icon is None:
return None
return Asset._from_guild_icon(self._state, self.id, self._icon)
@property
def banner(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the guild's banner asset, if available."""
if self._banner is None:
return None
return Asset._from_banner(self._state, self.id, self._banner)
@property
def splash(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the guild's invite splash asset, if available."""
if self._splash is None:
return None
return Asset._from_guild_image(self._state, self.id, self._splash, path="splashes")
class Invite(Hashable):
"""Represents a Discord :class:`Guild` or :class:`abc.GuildChannel` invite.
Depending on the way this object was created, some of the attributes can
have a value of ``None`` (see table below).
.. container:: operations
.. describe:: x == y
Checks if two invites are equal.
.. describe:: x != y
Checks if two invites are not equal.
.. describe:: hash(x)
Returns the invite hash.
.. describe:: str(x)
Returns the invite URL.
.. _invite_attr_table:
The following table illustrates what methods will obtain the attributes:
+------------------------------------+---------------------------------------------------------------------+
| Attribute | Method |
+====================================+=====================================================================+
| :attr:`max_age` | :meth:`abc.GuildChannel.invites`\\, :meth:`Guild.invites` |
+------------------------------------+---------------------------------------------------------------------+
| :attr:`max_uses` | :meth:`abc.GuildChannel.invites`\\, :meth:`Guild.invites` |
+------------------------------------+---------------------------------------------------------------------+
| :attr:`created_at` | :meth:`abc.GuildChannel.invites`\\, :meth:`Guild.invites` |
+------------------------------------+---------------------------------------------------------------------+
| :attr:`temporary` | :meth:`abc.GuildChannel.invites`\\, :meth:`Guild.invites` |
+------------------------------------+---------------------------------------------------------------------+
| :attr:`uses` | :meth:`abc.GuildChannel.invites`\\, :meth:`Guild.invites` |
+------------------------------------+---------------------------------------------------------------------+
| :attr:`approximate_member_count` | :meth:`Client.fetch_invite` with ``with_counts`` enabled |
+------------------------------------+---------------------------------------------------------------------+
| :attr:`approximate_presence_count` | :meth:`Client.fetch_invite` with ``with_counts`` enabled |
+------------------------------------+---------------------------------------------------------------------+
| :attr:`expires_at` | :meth:`Client.fetch_invite` with ``with_expiration`` enabled |
+------------------------------------+---------------------------------------------------------------------+
| :attr:`guild_scheduled_event` | :meth:`Client.fetch_invite` with valid ``guild_scheduled_event_id`` |
| | or valid event ID in URL or invite object |
+------------------------------------+---------------------------------------------------------------------+
If something is not in the table above, then it's available by all methods.
Attributes
----------
code: :class:`str`
The URL fragment used for the invite.
guild: Optional[Union[:class:`Guild`, :class:`Object`, :class:`PartialInviteGuild`]]
The guild the invite is for. Can be ``None`` if it's from a group direct message.
max_age: Optional[:class:`int`]
How long before the invite expires in seconds.
A value of ``0`` indicates that it doesn't expire.
Optional according to the :ref:`table <invite_attr_table>` above.
max_uses: Optional[:class:`int`]
How many times the invite can be used.
A value of ``0`` indicates that it has unlimited uses.
Optional according to the :ref:`table <invite_attr_table>` above.
created_at: Optional[:class:`datetime.datetime`]
An aware UTC datetime object denoting the time the invite was created.
Optional according to the :ref:`table <invite_attr_table>` above.
temporary: Optional[:class:`bool`]
Whether the invite grants temporary membership.
If ``True``, members who joined via this invite will be kicked upon disconnect.
Optional according to the :ref:`table <invite_attr_table>` above.
uses: Optional[:class:`int`]
How many times the invite has been used.
Optional according to the :ref:`table <invite_attr_table>` above.
approximate_member_count: Optional[:class:`int`]
The approximate number of members in the guild.
Optional according to the :ref:`table <invite_attr_table>` above.
approximate_presence_count: Optional[:class:`int`]
The approximate number of members currently active in the guild.
This includes idle, dnd, online, and invisible members. Offline members are excluded.
Optional according to the :ref:`table <invite_attr_table>` above.
expires_at: Optional[:class:`datetime.datetime`]
The expiration date of the invite. If the value is ``None`` when received through
:meth:`Client.fetch_invite` with ``with_expiration`` enabled, the invite will never expire.
.. versionadded:: 2.0
inviter: Optional[:class:`User`]
The user who created the invite, if any.
This is ``None`` in vanity invites, for example.
channel: Optional[Union[:class:`abc.GuildChannel`, :class:`Object`, :class:`PartialInviteChannel`]]
The channel the invite is for.
target_type: :class:`InviteTarget`
The type of target for the voice channel invite.
.. versionadded:: 2.0
target_user: Optional[:class:`User`]
The user whose stream to display for this invite, if any.
.. versionadded:: 2.0
target_application: Optional[:class:`PartialAppInfo`]
The embedded application the invite targets, if any.
.. versionadded:: 2.0
guild_scheduled_event: Optional[:class:`GuildScheduledEvent`]
The guild scheduled event included in the invite, if any.
.. versionadded:: 2.3
guild_welcome_screen: Optional[:class:`WelcomeScreen`]
The partial guild's welcome screen, if any.
.. versionadded:: 2.5
"""
__slots__ = (
"max_age",
"code",
"guild",
"created_at",
"uses",
"temporary",
"max_uses",
"inviter",
"channel",
"target_user",
"target_type",
"approximate_member_count",
"approximate_presence_count",
"target_application",
"expires_at",
"guild_scheduled_event",
"guild_welcome_screen",
"_state",
)
BASE = "https://discord.gg"
def __init__(
self,
*,
state: ConnectionState,
data: Union[InvitePayload, GatewayInvitePayload],
guild: Optional[Union[PartialInviteGuild, Guild]] = None,
channel: Optional[Union[PartialInviteChannel, GuildChannel]] = None,
) -> None:
self._state: ConnectionState = state
self.code: str = data["code"]
self.guild: Optional[InviteGuildType] = self._resolve_guild(data.get("guild"), guild)
self.max_age: Optional[int] = data.get("max_age")
self.max_uses: Optional[int] = data.get("max_uses")
self.created_at: Optional[datetime.datetime] = parse_time(data.get("created_at"))
self.temporary: Optional[bool] = data.get("temporary")
self.uses: Optional[int] = data.get("uses")
self.approximate_presence_count: Optional[int] = data.get("approximate_presence_count")
self.approximate_member_count: Optional[int] = data.get("approximate_member_count")
expires_at = data.get("expires_at", None)
self.expires_at: Optional[datetime.datetime] = (
parse_time(expires_at) if expires_at else None
)
inviter_data = data.get("inviter")
self.inviter: Optional[User] = None if inviter_data is None else self._state.create_user(inviter_data) # type: ignore
self.channel: Optional[InviteChannelType] = self._resolve_channel(
data.get("channel"), channel
)
# this is stored here due to disnake.Guild not storing a welcome screen
# if it was stored on the Guild object, we would be throwing away this data from the api request
if (
self.guild is not None
and (guild_data := data.get("guild"))
and "welcome_screen" in guild_data
):
self.guild_welcome_screen: Optional[WelcomeScreen] = WelcomeScreen(
state=self._state,
data=guild_data["welcome_screen"],
guild=self.guild, # type: ignore
)
else:
self.guild_welcome_screen: Optional[WelcomeScreen] = None
target_user_data = data.get("target_user")
self.target_user: Optional[User] = None if target_user_data is None else self._state.create_user(target_user_data) # type: ignore
self.target_type: InviteTarget = try_enum(InviteTarget, data.get("target_type", 0))
application = data.get("target_application")
self.target_application: Optional[PartialAppInfo] = (
PartialAppInfo(data=application, state=state) if application else None
)
if scheduled_event := data.get("guild_scheduled_event"):
self.guild_scheduled_event: Optional[GuildScheduledEvent] = GuildScheduledEvent(
state=state, data=scheduled_event
)
else:
self.guild_scheduled_event: Optional[GuildScheduledEvent] = None
@classmethod
def from_incomplete(cls, *, state: ConnectionState, data: InvitePayload) -> Self:
guild: Optional[Union[Guild, PartialInviteGuild]]
try:
guild_data = data["guild"]
except KeyError:
# If we're here, then this is a group DM
guild = None
else:
guild_id = int(guild_data["id"])
guild = 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)
# todo: this is no longer true
# As far as I know, invites always need a channel
# So this should never raise.
channel: Union[PartialInviteChannel, GuildChannel] = PartialInviteChannel(
data=data["channel"], state=state
)
if guild is not None and not isinstance(guild, PartialInviteGuild):
# Upgrade the partial data if applicable
channel = guild.get_channel(channel.id) or channel
return cls(state=state, data=data, guild=guild, channel=channel)
@classmethod
def from_gateway(cls, *, state: ConnectionState, data: GatewayInvitePayload) -> Self:
guild_id: Optional[int] = _get_as_snowflake(data, "guild_id")
guild: Optional[Union[Guild, Object]] = 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)
else:
guild = Object(id=guild_id) if guild_id is not None else None
channel = Object(id=channel_id)
return cls(
state=state,
data=data,
# objects may be partial due to missing cache
guild=guild, # type: ignore
channel=channel, # type: ignore
)
def _resolve_guild(
self,
data: Optional[InviteGuildPayload],
guild: Optional[Union[Guild, PartialInviteGuild]] = None,
) -> Optional[InviteGuildType]:
if guild is not None:
return guild
if data is None:
return None
guild_id = int(data["id"])
return PartialInviteGuild(self._state, data, guild_id)
def _resolve_channel(
self,
data: Optional[InviteChannelPayload],
channel: Optional[Union[PartialInviteChannel, GuildChannel]] = None,
) -> Optional[InviteChannelType]:
if channel is not None:
return channel
if data is None:
return None
return PartialInviteChannel(data=data, state=self._state)
def __str__(self) -> str:
return self.url
def __repr__(self) -> str:
return (
f"<Invite code={self.code!r} guild={self.guild!r} "
f"online={self.approximate_presence_count} "
f"members={self.approximate_member_count}>"
)
def __hash__(self) -> int:
return hash(self.code)
@property
def id(self) -> str:
""":class:`str`: Returns the proper code portion of the invite."""
return self.code
@property
def url(self) -> str:
""":class:`str`: A property that retrieves the invite URL."""
url = f"{self.BASE}/{self.code}"
if self.guild_scheduled_event:
url += f"?event={self.guild_scheduled_event.id}"
return url
async def delete(self, *, reason: Optional[str] = None) -> None:
"""|coro|
Revokes the instant invite.
You must have :attr:`~Permissions.manage_channels` permission to do this.
Parameters
----------
reason: Optional[:class:`str`]
The reason for deleting this invite. Shows up on the audit log.
Raises
------
Forbidden
You do not have permissions to revoke invites.
NotFound
The invite is invalid or expired.
HTTPException
Revoking the invite failed.
"""
await self._state.http.delete_invite(self.code, reason=reason)