You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5299 lines
182 KiB
5299 lines
182 KiB
"""
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2015-present Rapptz
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
DEALINGS IN THE SOFTWARE.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
from datetime import datetime
|
|
from operator import attrgetter
|
|
import unicodedata
|
|
from typing import (
|
|
Any,
|
|
AsyncIterator,
|
|
ClassVar,
|
|
Collection,
|
|
Coroutine,
|
|
Dict,
|
|
List,
|
|
Mapping,
|
|
NamedTuple,
|
|
Sequence,
|
|
Set,
|
|
Literal,
|
|
Optional,
|
|
TYPE_CHECKING,
|
|
Tuple,
|
|
Union,
|
|
overload,
|
|
)
|
|
import warnings
|
|
|
|
from . import utils, abc
|
|
from .role import Role
|
|
from .member import Member, VoiceState
|
|
from .emoji import Emoji
|
|
from .errors import ClientException, InvalidData
|
|
from .permissions import PermissionOverwrite
|
|
from .colour import Colour
|
|
from .errors import ClientException
|
|
from .channel import *
|
|
from .channel import _guild_channel_factory, _threaded_guild_channel_factory
|
|
from .enums import (
|
|
AuditLogAction,
|
|
VideoQualityMode,
|
|
ChannelType,
|
|
EntityType,
|
|
HubType,
|
|
PrivacyLevel,
|
|
try_enum,
|
|
VerificationLevel,
|
|
ContentFilter,
|
|
NotificationLevel,
|
|
NSFWLevel,
|
|
MFALevel,
|
|
Locale,
|
|
AutoModRuleEventType,
|
|
ForumOrderType,
|
|
ForumLayoutType,
|
|
ReadStateType,
|
|
)
|
|
from .mixins import Hashable
|
|
from .user import User
|
|
from .invite import Invite
|
|
from .widget import Widget
|
|
from .asset import Asset
|
|
from .flags import SystemChannelFlags
|
|
from .integrations import Integration, _integration_factory
|
|
from .scheduled_event import ScheduledEvent
|
|
from .stage_instance import StageInstance
|
|
from .threads import Thread
|
|
from .sticker import GuildSticker
|
|
from .file import File
|
|
from .audit_logs import AuditLogEntry
|
|
from .object import OLDEST_OBJECT, Object
|
|
from .profile import MemberProfile
|
|
from .partial_emoji import PartialEmoji
|
|
from .welcome_screen import *
|
|
from .application import PartialApplication
|
|
from .guild_premium import PremiumGuildSubscription
|
|
from .entitlements import Entitlement
|
|
from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction
|
|
from .partial_emoji import _EmojiTag, PartialEmoji
|
|
|
|
if TYPE_CHECKING:
|
|
from .abc import Snowflake, SnowflakeTime
|
|
from .types.guild import (
|
|
BaseGuild as BaseGuildPayload,
|
|
Guild as GuildPayload,
|
|
RolePositionUpdate as RolePositionUpdatePayload,
|
|
UserGuild as UserGuildPayload,
|
|
)
|
|
from .types.threads import (
|
|
Thread as ThreadPayload,
|
|
)
|
|
from .types.voice import BaseVoiceState as VoiceStatePayload
|
|
from .permissions import Permissions
|
|
from .channel import VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel
|
|
from .template import Template
|
|
from .webhook import Webhook
|
|
from .state import ConnectionState
|
|
from .voice_client import VoiceProtocol
|
|
from .settings import GuildSettings
|
|
from .enums import ApplicationType
|
|
from .types.channel import (
|
|
GuildChannel as GuildChannelPayload,
|
|
TextChannel as TextChannelPayload,
|
|
NewsChannel as NewsChannelPayload,
|
|
VoiceChannel as VoiceChannelPayload,
|
|
CategoryChannel as CategoryChannelPayload,
|
|
StageChannel as StageChannelPayload,
|
|
ForumChannel as ForumChannelPayload,
|
|
DirectoryChannel as DirectoryChannelPayload,
|
|
)
|
|
from .types.embed import EmbedType
|
|
from .types.integration import IntegrationType
|
|
from .types.message import MessageSearchAuthorType, MessageSearchHasType
|
|
from .types.snowflake import SnowflakeList, Snowflake as _Snowflake
|
|
from .types.widget import EditWidgetSettings
|
|
from .types.audit_log import AuditLogEvent
|
|
from .types.oauth2 import OAuth2Guild as OAuth2GuildPayload
|
|
from .message import EmojiInputType, Message
|
|
from .read_state import ReadState
|
|
|
|
VocalGuildChannel = Union[VoiceChannel, StageChannel]
|
|
NonCategoryChannel = Union[VocalGuildChannel, ForumChannel, TextChannel, DirectoryChannel]
|
|
GuildChannel = Union[NonCategoryChannel, CategoryChannel]
|
|
ByCategoryItem = Tuple[Optional[CategoryChannel], List[NonCategoryChannel]]
|
|
|
|
MISSING = utils.MISSING
|
|
|
|
__all__ = (
|
|
'Guild',
|
|
'UserGuild',
|
|
'BanEntry',
|
|
'ApplicationCommandCounts',
|
|
)
|
|
|
|
|
|
class ApplicationCommandCounts(NamedTuple):
|
|
chat_input: int
|
|
user: int
|
|
message: int
|
|
|
|
|
|
class BanEntry(NamedTuple):
|
|
reason: Optional[str]
|
|
user: User
|
|
|
|
|
|
class _GuildLimit(NamedTuple):
|
|
emoji: int
|
|
stickers: int
|
|
bitrate: float
|
|
filesize: int
|
|
|
|
|
|
class UserGuild(Hashable):
|
|
"""Represents a partial joined guild.
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: x == y
|
|
|
|
Checks if two guilds are equal.
|
|
|
|
.. describe:: x != y
|
|
|
|
Checks if two guilds are not equal.
|
|
|
|
.. describe:: hash(x)
|
|
|
|
Returns the guild's hash.
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the guild's name.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Attributes
|
|
----------
|
|
id: :class:`int`
|
|
The guild's ID.
|
|
name: :class:`str`
|
|
The guild name.
|
|
features: List[:class:`str`]
|
|
A list of features that the guild has. The features that a guild can have are
|
|
subject to arbitrary change by Discord. Incomplete when retrieved from :attr:`OAuth2Authorization.guilds`.
|
|
owner: :class:`bool`
|
|
Whether the current user is the owner of the guild. Inaccurate when retrieved from :attr:`OAuth2Authorization.guilds`.
|
|
mfa_level: :class:`MFALevel`
|
|
The guild's Multi-Factor Authentication requirement level. Only available from :attr:`OAuth2Authorization.guilds`.
|
|
|
|
.. versionadded:: 2.1
|
|
approximate_member_count: Optional[:class:`int`]
|
|
The approximate number of members in the guild. Only available using
|
|
using :meth:`Client.fetch_guilds` with ``with_counts=True``.
|
|
approximate_presence_count: Optional[:class:`int`]
|
|
The approximate number of members currently active in the guild.
|
|
Offline members are excluded. Only available using
|
|
:meth:`Client.fetch_guilds` with ``with_counts=True``.
|
|
"""
|
|
|
|
__slots__ = (
|
|
'id',
|
|
'name',
|
|
'_icon',
|
|
'owner',
|
|
'_permissions',
|
|
'mfa_level',
|
|
'features',
|
|
'approximate_member_count',
|
|
'approximate_presence_count',
|
|
'_state',
|
|
)
|
|
|
|
def __init__(self, *, state: ConnectionState, data: Union[UserGuildPayload, OAuth2GuildPayload]):
|
|
self._state: ConnectionState = state
|
|
self.id: int = int(data['id'])
|
|
self.name: str = data['name']
|
|
self._icon: Optional[str] = data.get('icon')
|
|
self.owner: bool = data.get('owner', False)
|
|
self._permissions: int = int(data.get('permissions', 0))
|
|
self.mfa_level: MFALevel = try_enum(MFALevel, data.get('mfa_level', 0))
|
|
self.features: List[str] = data.get('features', [])
|
|
self.approximate_member_count: Optional[int] = data.get('approximate_member_count')
|
|
self.approximate_presence_count: Optional[int] = data.get('approximate_presence_count')
|
|
|
|
def __str__(self) -> str:
|
|
return self.name or ''
|
|
|
|
def __repr__(self) -> str:
|
|
return f'<UserGuild id={self.id} name={self.name!r}>'
|
|
|
|
@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 permissions(self) -> Permissions:
|
|
""":class:`Permissions`: Returns the calculated permissions the current user has in the guild."""
|
|
return Permissions(self._permissions)
|
|
|
|
def is_joined(self) -> bool:
|
|
"""Returns whether you are a member of this guild.
|
|
|
|
Always returns ``True``.
|
|
|
|
Returns
|
|
-------
|
|
:class:`bool`
|
|
Whether you are a member of this guild.
|
|
"""
|
|
return True
|
|
|
|
async def leave(self) -> None:
|
|
"""|coro|
|
|
|
|
Leaves the guild.
|
|
|
|
.. note::
|
|
|
|
You cannot leave a guild that you own, you must delete it instead
|
|
via :meth:`delete`.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
--------
|
|
HTTPException
|
|
Leaving the guild failed.
|
|
"""
|
|
await self._state.http.leave_guild(self.id, lurking=False)
|
|
|
|
async def delete(self) -> None:
|
|
"""|coro|
|
|
|
|
Deletes the guild. You must be the guild owner to delete the
|
|
guild.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
--------
|
|
HTTPException
|
|
Deleting the guild failed.
|
|
Forbidden
|
|
You do not have permissions to delete the guild.
|
|
"""
|
|
await self._state.http.delete_guild(self.id)
|
|
|
|
|
|
class Guild(Hashable):
|
|
"""Represents a Discord guild.
|
|
|
|
This is referred to as a "server" in the official Discord UI.
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: x == y
|
|
|
|
Checks if two guilds are equal.
|
|
|
|
.. describe:: x != y
|
|
|
|
Checks if two guilds are not equal.
|
|
|
|
.. describe:: hash(x)
|
|
|
|
Returns the guild's hash.
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the guild's name.
|
|
|
|
Attributes
|
|
----------
|
|
name: :class:`str`
|
|
The guild name.
|
|
emojis: Tuple[:class:`Emoji`, ...]
|
|
All emojis that the guild owns.
|
|
stickers: Tuple[:class:`GuildSticker`, ...]
|
|
All stickers that the guild owns.
|
|
|
|
.. versionadded:: 2.0
|
|
afk_timeout: :class:`int`
|
|
The timeout to get sent to the AFK channel.
|
|
id: :class:`int`
|
|
The guild's ID.
|
|
owner_id: :class:`int`
|
|
The guild owner's ID.
|
|
application_id: Optional[:class:`int`]
|
|
The application ID of the guild owner (if applicable).
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. versionchanged:: 2.1
|
|
|
|
Renamed from ``owner_application_id`` to ``application_id``.
|
|
unavailable: :class:`bool`
|
|
Indicates if the guild is unavailable. If this is ``True`` then the
|
|
reliability of other attributes outside of :attr:`Guild.id` is slim and they might
|
|
all be ``None``. It is best to not do anything with the guild if it is unavailable.
|
|
|
|
Check the :func:`on_guild_unavailable` and :func:`on_guild_available` events.
|
|
max_presences: Optional[:class:`int`]
|
|
The maximum amount of presences for the guild.
|
|
max_members: Optional[:class:`int`]
|
|
The maximum amount of members for the guild.
|
|
max_video_channel_users: Optional[:class:`int`]
|
|
The maximum amount of users in a video channel.
|
|
|
|
.. versionadded:: 1.4
|
|
description: Optional[:class:`str`]
|
|
The guild's description.
|
|
verification_level: :class:`VerificationLevel`
|
|
The guild's verification level.
|
|
vanity_url_code: Optional[:class:`str`]
|
|
The guild's vanity url code, if any
|
|
|
|
.. versionadded:: 2.0
|
|
explicit_content_filter: :class:`ContentFilter`
|
|
The guild's explicit content filter.
|
|
default_notifications: :class:`NotificationLevel`
|
|
The guild's notification settings.
|
|
features: List[:class:`str`]
|
|
A list of features that the guild has. The features that a guild can have are
|
|
subject to arbitrary change by Discord.
|
|
premium_subscription_count: :class:`int`
|
|
The number of "boosts" this guild currently has.
|
|
preferred_locale: :class:`Locale`
|
|
The preferred locale for the guild. Used when filtering Server Discovery
|
|
results to a specific language.
|
|
|
|
.. versionchanged:: 2.0
|
|
This field is now an enum instead of a :class:`str`.
|
|
nsfw_level: :class:`NSFWLevel`
|
|
The guild's NSFW level.
|
|
|
|
.. versionadded:: 2.0
|
|
mfa_level: :class:`MFALevel`
|
|
The guild's Multi-Factor Authentication requirement level.
|
|
|
|
.. versionchanged:: 2.0
|
|
This field is now an enum instead of an :class:`int`.
|
|
application_command_counts: Optional[:class:`ApplicationCommandCounts`]
|
|
A namedtuple representing the number of application commands in the guild, separated by type.
|
|
|
|
.. versionadded:: 2.0
|
|
approximate_member_count: Optional[:class:`int`]
|
|
The approximate number of members in the guild. This is ``None`` unless the guild is obtained
|
|
using :meth:`Client.fetch_guild` with ``with_counts=True``.
|
|
|
|
.. versionadded:: 2.0
|
|
approximate_presence_count: Optional[:class:`int`]
|
|
The approximate number of members currently active in the guild.
|
|
Offline members are excluded. This is ``None`` unless the guild is obtained using
|
|
:meth:`Client.fetch_guild` with ``with_counts=True``.
|
|
|
|
.. versionadded:: 2.0
|
|
premium_progress_bar_enabled: :class:`bool`
|
|
Indicates if the guild has the premium (server boost) progress bar enabled.
|
|
|
|
.. versionadded:: 2.0
|
|
widget_enabled: :class:`bool`
|
|
Indicates if the guild has widget enabled.
|
|
|
|
.. versionadded:: 2.0
|
|
hub_type: Optional[:class:`HubType`]
|
|
The type of Student Hub the guild is, if applicable.
|
|
|
|
.. versionadded:: 2.1
|
|
"""
|
|
|
|
__slots__ = (
|
|
'afk_timeout',
|
|
'name',
|
|
'id',
|
|
'unavailable',
|
|
'owner_id',
|
|
'emojis',
|
|
'stickers',
|
|
'features',
|
|
'verification_level',
|
|
'explicit_content_filter',
|
|
'default_notifications',
|
|
'description',
|
|
'max_presences',
|
|
'max_members',
|
|
'max_video_channel_users',
|
|
'_premium_tier',
|
|
'premium_subscription_count',
|
|
'preferred_locale',
|
|
'nsfw_level',
|
|
'mfa_level',
|
|
'vanity_url_code',
|
|
'application_id',
|
|
'widget_enabled',
|
|
'_widget_channel_id',
|
|
'_members',
|
|
'_channels',
|
|
'_icon',
|
|
'_banner',
|
|
'_state',
|
|
'_roles',
|
|
'_member_count',
|
|
'_large',
|
|
'_splash',
|
|
'_voice_states',
|
|
'_afk_channel_id',
|
|
'_system_channel_id',
|
|
'_system_channel_flags',
|
|
'_discovery_splash',
|
|
'_rules_channel_id',
|
|
'_public_updates_channel_id',
|
|
'_stage_instances',
|
|
'_scheduled_events',
|
|
'_threads',
|
|
'approximate_member_count',
|
|
'approximate_presence_count',
|
|
'premium_progress_bar_enabled',
|
|
'_presence_count',
|
|
'_true_online_count',
|
|
'_chunked',
|
|
'_member_list',
|
|
'keywords',
|
|
'primary_category_id',
|
|
'application_command_counts',
|
|
'hub_type',
|
|
'_joined_at',
|
|
'_cs_joined',
|
|
)
|
|
|
|
_PREMIUM_GUILD_LIMITS: ClassVar[Dict[Optional[int], _GuildLimit]] = {
|
|
None: _GuildLimit(emoji=50, stickers=5, bitrate=96e3, filesize=utils.DEFAULT_FILE_SIZE_LIMIT_BYTES),
|
|
0: _GuildLimit(emoji=50, stickers=5, bitrate=96e3, filesize=utils.DEFAULT_FILE_SIZE_LIMIT_BYTES),
|
|
1: _GuildLimit(emoji=100, stickers=15, bitrate=128e3, filesize=utils.DEFAULT_FILE_SIZE_LIMIT_BYTES),
|
|
2: _GuildLimit(emoji=150, stickers=30, bitrate=256e3, filesize=52428800),
|
|
3: _GuildLimit(emoji=250, stickers=60, bitrate=384e3, filesize=104857600),
|
|
}
|
|
|
|
def __init__(self, *, data: Union[BaseGuildPayload, GuildPayload], state: ConnectionState) -> None:
|
|
self._chunked = False
|
|
self._cs_joined: Optional[bool] = None
|
|
self._roles: Dict[int, Role] = {}
|
|
self._channels: Dict[int, GuildChannel] = {}
|
|
self._members: Dict[int, Member] = {}
|
|
self._member_list: List[Optional[Member]] = []
|
|
self._voice_states: Dict[int, VoiceState] = {}
|
|
self._threads: Dict[int, Thread] = {}
|
|
self._stage_instances: Dict[int, StageInstance] = {}
|
|
self._scheduled_events: Dict[int, ScheduledEvent] = {}
|
|
self._state: ConnectionState = state
|
|
self.application_command_counts: Optional[ApplicationCommandCounts] = None
|
|
self._member_count: Optional[int] = None
|
|
self._presence_count: Optional[int] = None
|
|
self._large: Optional[bool] = None
|
|
self._from_data(data)
|
|
|
|
def _add_channel(self, channel: GuildChannel, /) -> None:
|
|
self._channels[channel.id] = channel
|
|
|
|
def _remove_channel(self, channel: Snowflake, /) -> None:
|
|
self._channels.pop(channel.id, None)
|
|
|
|
def _voice_state_for(self, user_id: int, /) -> Optional[VoiceState]:
|
|
return self._voice_states.get(user_id)
|
|
|
|
def _add_member(self, member: Member, /) -> None:
|
|
self._members[member.id] = member
|
|
if member._presence:
|
|
self._state.store_presence(member.id, member._presence, self.id)
|
|
member._presence = None
|
|
|
|
def _store_thread(self, payload: ThreadPayload, /) -> Thread:
|
|
thread = Thread(guild=self, state=self._state, data=payload)
|
|
self._threads[thread.id] = thread
|
|
return thread
|
|
|
|
def _remove_member(self, member: Snowflake, /) -> None:
|
|
self._members.pop(member.id, None)
|
|
self._state.remove_presence(member.id, self.id)
|
|
|
|
def _add_thread(self, thread: Thread, /) -> None:
|
|
self._threads[thread.id] = thread
|
|
|
|
def _remove_thread(self, thread: Snowflake, /) -> None:
|
|
self._threads.pop(thread.id, None)
|
|
|
|
def _remove_threads_by_channel(self, channel_id: int) -> None:
|
|
to_remove = [k for k, t in self._threads.items() if t.parent_id == channel_id]
|
|
for k in to_remove:
|
|
del self._threads[k]
|
|
|
|
def _filter_threads(self, channel_ids: Set[int]) -> Dict[int, Thread]:
|
|
return {k: t for k, t in self._threads.items() if t.parent_id in channel_ids}
|
|
|
|
def __str__(self) -> str:
|
|
return self.name or ''
|
|
|
|
def __repr__(self) -> str:
|
|
attrs = (
|
|
('id', self.id),
|
|
('name', self.name),
|
|
('chunked', self.chunked),
|
|
('member_count', self.member_count),
|
|
)
|
|
inner = ' '.join('%s=%r' % t for t in attrs)
|
|
return f'<Guild {inner}>'
|
|
|
|
def _update_voice_state(
|
|
self, data: VoiceStatePayload, channel_id: Optional[int]
|
|
) -> Tuple[Optional[Member], VoiceState, VoiceState]:
|
|
cache_flags = self._state.member_cache_flags
|
|
user_id = int(data['user_id'])
|
|
channel: Optional[VocalGuildChannel] = self.get_channel(channel_id) # type: ignore # this will always be a voice channel
|
|
try:
|
|
# Check if we should remove the voice state from cache
|
|
if channel is None:
|
|
after = self._voice_states.pop(user_id)
|
|
else:
|
|
after = self._voice_states[user_id]
|
|
|
|
before = copy.copy(after)
|
|
after._update(data, channel)
|
|
except KeyError:
|
|
# If we're here then add it into the cache
|
|
after = VoiceState(data=data, channel=channel)
|
|
before = VoiceState(data=data, channel=None)
|
|
self._voice_states[user_id] = after
|
|
|
|
member = self.get_member(user_id)
|
|
if member is None:
|
|
try:
|
|
member = Member(data=data['member'], state=self._state, guild=self)
|
|
except KeyError:
|
|
member = None
|
|
|
|
if member is not None and cache_flags.voice:
|
|
self._add_member(member)
|
|
|
|
return member, before, after
|
|
|
|
def _add_role(self, role: Role, /) -> None:
|
|
for r in self._roles.values():
|
|
r.position += not r.is_default()
|
|
|
|
self._roles[role.id] = role
|
|
|
|
def _remove_role(self, role_id: int, /) -> Role:
|
|
role = self._roles.pop(role_id)
|
|
|
|
for r in self._roles.values():
|
|
r.position -= r.position > role.position
|
|
|
|
return role
|
|
|
|
@classmethod
|
|
def _create_unavailable(cls, *, state: ConnectionState, guild_id: int) -> Guild:
|
|
return cls(state=state, data={'id': guild_id, 'unavailable': True}) # type: ignore
|
|
|
|
def _from_data(self, guild: Union[BaseGuildPayload, GuildPayload]) -> None:
|
|
try:
|
|
self._member_count: Optional[int] = guild['member_count'] # type: ignore # Handled below
|
|
except KeyError:
|
|
pass
|
|
|
|
self.id: int = int(guild['id'])
|
|
self.name: str = guild.get('name', '')
|
|
self.verification_level: VerificationLevel = try_enum(VerificationLevel, guild.get('verification_level'))
|
|
self.default_notifications: NotificationLevel = try_enum(
|
|
NotificationLevel, guild.get('default_message_notifications')
|
|
)
|
|
self.explicit_content_filter: ContentFilter = try_enum(ContentFilter, guild.get('explicit_content_filter', 0))
|
|
self.afk_timeout: int = guild.get('afk_timeout', 0)
|
|
self.hub_type: Optional[HubType] = (
|
|
try_enum(HubType, guild.get('hub_type')) if guild.get('hub_type') is not None else None
|
|
)
|
|
self.unavailable: bool = guild.get('unavailable', False)
|
|
if self.unavailable:
|
|
self._member_count = 0
|
|
|
|
state = self._state # Speed up attribute access
|
|
|
|
for r in guild.get('roles', []):
|
|
role = Role(guild=self, data=r, state=state)
|
|
self._roles[role.id] = role
|
|
|
|
for c in guild.get('channels', []):
|
|
factory, _ = _guild_channel_factory(c['type'])
|
|
if factory:
|
|
self._add_channel(factory(guild=self, data=c, state=state)) # type: ignore
|
|
|
|
for t in guild.get('threads', []):
|
|
self._add_thread(Thread(guild=self, state=self._state, data=t))
|
|
|
|
for s in guild.get('stage_instances', []):
|
|
stage_instance = StageInstance(guild=self, data=s, state=state)
|
|
self._stage_instances[stage_instance.id] = stage_instance
|
|
|
|
for s in guild.get('guild_scheduled_events', []):
|
|
scheduled_event = ScheduledEvent(data=s, state=state)
|
|
self._scheduled_events[scheduled_event.id] = scheduled_event
|
|
|
|
self.emojis: Tuple[Emoji, ...] = tuple(map(lambda d: state.store_emoji(self, d), guild.get('emojis', [])))
|
|
self.stickers: Tuple[GuildSticker, ...] = tuple(
|
|
map(lambda d: state.store_sticker(self, d), guild.get('stickers', []))
|
|
)
|
|
self.features: List[str] = guild.get('features', [])
|
|
self._icon: Optional[str] = guild.get('icon')
|
|
self._banner: Optional[str] = guild.get('banner')
|
|
self._splash: Optional[str] = guild.get('splash')
|
|
self._system_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'system_channel_id')
|
|
self.description: Optional[str] = guild.get('description')
|
|
self.max_presences: Optional[int] = guild.get('max_presences')
|
|
self.max_members: Optional[int] = guild.get('max_members')
|
|
self.max_video_channel_users: Optional[int] = guild.get('max_video_channel_users')
|
|
self._premium_tier = guild.get('premium_tier')
|
|
self.premium_subscription_count: int = guild.get('premium_subscription_count') or 0
|
|
self.vanity_url_code: Optional[str] = guild.get('vanity_url_code')
|
|
self.widget_enabled: bool = guild.get('widget_enabled', False)
|
|
self._widget_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'widget_channel_id')
|
|
self._system_channel_flags: int = guild.get('system_channel_flags', 0)
|
|
self.preferred_locale: Locale = try_enum(Locale, guild.get('preferred_locale', 'en-US'))
|
|
self._discovery_splash: Optional[str] = guild.get('discovery_splash')
|
|
self._rules_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'rules_channel_id')
|
|
self._public_updates_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'public_updates_channel_id')
|
|
self._afk_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'afk_channel_id')
|
|
self.nsfw_level: NSFWLevel = try_enum(NSFWLevel, guild.get('nsfw_level', 0))
|
|
self.mfa_level: MFALevel = try_enum(MFALevel, guild.get('mfa_level', 0))
|
|
self.approximate_presence_count: Optional[int] = guild.get('approximate_presence_count')
|
|
self.approximate_member_count: Optional[int] = guild.get('approximate_member_count')
|
|
self.owner_id: Optional[int] = utils._get_as_snowflake(guild, 'owner_id')
|
|
self.application_id: Optional[int] = utils._get_as_snowflake(guild, 'application_id')
|
|
self.premium_progress_bar_enabled: bool = guild.get('premium_progress_bar_enabled', False)
|
|
self._joined_at = guild.get('joined_at')
|
|
|
|
try:
|
|
self._large = guild['large'] # type: ignore
|
|
except KeyError:
|
|
pass
|
|
|
|
counts = guild.get('application_command_counts')
|
|
if counts:
|
|
self.application_command_counts = ApplicationCommandCounts(counts.get(1, 0), counts.get(2, 0), counts.get(3, 0))
|
|
|
|
for vs in guild.get('voice_states', []):
|
|
self._update_voice_state(vs, int(vs['channel_id']))
|
|
|
|
cache_flags = state.member_cache_flags
|
|
for mdata in guild.get('members', []):
|
|
member = Member(data=mdata, guild=self, state=state)
|
|
if cache_flags.joined or member.id == state.self_id or (cache_flags.voice and member.id in self._voice_states):
|
|
self._add_member(member)
|
|
|
|
for presence in guild.get('presences', []):
|
|
user_id = int(presence['user']['id'])
|
|
presence = state.create_presence(presence)
|
|
state.store_presence(user_id, presence, self.id)
|
|
|
|
@property
|
|
def channels(self) -> Sequence[GuildChannel]:
|
|
"""Sequence[:class:`abc.GuildChannel`]: A list of channels that belongs to this guild."""
|
|
return utils.SequenceProxy(self._channels.values())
|
|
|
|
@property
|
|
def threads(self) -> Sequence[Thread]:
|
|
"""Sequence[:class:`Thread`]: A list of active threads that you have permission to view.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
return utils.SequenceProxy(self._threads.values())
|
|
|
|
@property
|
|
def large(self) -> bool:
|
|
""":class:`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:
|
|
if self._member_count is not None:
|
|
return self._member_count >= 250
|
|
return len(self._members) >= 250
|
|
return self._large
|
|
|
|
@property
|
|
def _offline_members_hidden(self) -> bool:
|
|
return (self._member_count or 0) > 1000
|
|
|
|
def is_hub(self) -> bool:
|
|
""":class:`bool`: Whether the guild is a Student Hub.
|
|
|
|
.. versionadded:: 2.1
|
|
"""
|
|
return 'HUB' in self.features
|
|
|
|
@property
|
|
def voice_channels(self) -> List[VoiceChannel]:
|
|
"""List[:class:`VoiceChannel`]: A list of voice channels that belongs to this guild.
|
|
|
|
This is sorted by the position and are in UI order from top to bottom.
|
|
"""
|
|
r = [ch for ch in self._channels.values() if isinstance(ch, VoiceChannel)]
|
|
r.sort(key=attrgetter('position', 'id'))
|
|
return r
|
|
|
|
@property
|
|
def stage_channels(self) -> List[StageChannel]:
|
|
"""List[:class:`StageChannel`]: A list of stage channels that belongs to this guild.
|
|
|
|
.. versionadded:: 1.7
|
|
|
|
This is sorted by the position and are in UI order from top to bottom.
|
|
"""
|
|
r = [ch for ch in self._channels.values() if isinstance(ch, StageChannel)]
|
|
r.sort(key=attrgetter('position', 'id'))
|
|
return r
|
|
|
|
@property
|
|
def me(self) -> Optional[Member]:
|
|
"""Optional[:class:`Member`]: Similar to :attr:`Client.user` except an instance of :class:`Member`.
|
|
This is essentially used to get the member version of yourself.
|
|
|
|
.. versionchanged:: 2.0
|
|
|
|
The type has been updated to be optional, which properly reflects cases where the current user
|
|
is not a member of the guild, or the current user's member object is not cached.
|
|
"""
|
|
self_id = self._state.self_id
|
|
return self.get_member(self_id) # type: ignore
|
|
|
|
def is_joined(self) -> bool:
|
|
"""Returns whether you are a full member of this guild.
|
|
|
|
May not be accurate for :class:`Guild` s fetched over HTTP.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Returns
|
|
-------
|
|
:class:`bool`
|
|
Whether you are a member of this guild.
|
|
"""
|
|
if self._cs_joined is not None:
|
|
return self._cs_joined
|
|
if (self.me and self.me.joined_at) or self.joined_at:
|
|
return True
|
|
return self._state.is_guild_evicted(self)
|
|
|
|
@property
|
|
def joined_at(self) -> Optional[datetime]:
|
|
""":class:`datetime.datetime`: Returns when you joined the guild.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
return utils.parse_time(self._joined_at)
|
|
|
|
@property
|
|
def voice_client(self) -> Optional[VoiceProtocol]:
|
|
"""Optional[:class:`VoiceProtocol`]: Returns the :class:`VoiceProtocol` associated with this guild, if any."""
|
|
return self._state._get_voice_client(self.id)
|
|
|
|
@property
|
|
def notification_settings(self) -> GuildSettings:
|
|
""":class:`GuildSettings`: Returns the notification settings for the guild.
|
|
|
|
If not found, an instance is created with defaults applied. This follows Discord behaviour.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
state = self._state
|
|
return state.guild_settings.get(self.id, state.default_guild_settings(self.id))
|
|
|
|
@property
|
|
def text_channels(self) -> List[TextChannel]:
|
|
"""List[:class:`TextChannel`]: A list of text channels that belongs to this guild.
|
|
|
|
This is sorted by the position and are in UI order from top to bottom.
|
|
"""
|
|
r = [ch for ch in self._channels.values() if isinstance(ch, TextChannel)]
|
|
r.sort(key=attrgetter('position', 'id'))
|
|
return r
|
|
|
|
@property
|
|
def categories(self) -> List[CategoryChannel]:
|
|
"""List[:class:`CategoryChannel`]: A list of categories that belongs to this guild.
|
|
|
|
This is sorted by the position and are in UI order from top to bottom.
|
|
"""
|
|
r = [ch for ch in self._channels.values() if isinstance(ch, CategoryChannel)]
|
|
r.sort(key=attrgetter('position', 'id'))
|
|
return r
|
|
|
|
@property
|
|
def forums(self) -> List[ForumChannel]:
|
|
"""List[:class:`ForumChannel`]: A list of forum channels that belongs to this guild.
|
|
|
|
This is sorted by the position and are in UI order from top to bottom.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
r = [ch for ch in self._channels.values() if isinstance(ch, ForumChannel)]
|
|
r.sort(key=attrgetter('position', 'id'))
|
|
return r
|
|
|
|
@property
|
|
def directory_channels(self) -> List[DirectoryChannel]:
|
|
"""List[:class:`DirectoryChannel`]: A list of directory channels that belongs to this guild.
|
|
|
|
This is sorted by the position and are in UI order from top to bottom.
|
|
|
|
.. versionadded:: 2.1
|
|
"""
|
|
r = [ch for ch in self._channels.values() if isinstance(ch, DirectoryChannel)]
|
|
r.sort(key=attrgetter('position', 'id'))
|
|
return r
|
|
|
|
@property
|
|
def directories(self) -> List[DirectoryChannel]:
|
|
"""List[:class:`DirectoryChannel`]: A list of directory channels that belongs to this guild.
|
|
|
|
An alias for :attr:`Guild.directory_channels`.
|
|
|
|
.. versionadded:: 2.1
|
|
"""
|
|
return self.directory_channels
|
|
|
|
def by_category(self) -> List[ByCategoryItem]:
|
|
"""Returns every :class:`CategoryChannel` and their associated channels.
|
|
|
|
These channels and categories are sorted in the official Discord UI order.
|
|
|
|
If the channels do not have a category, then the first element of the tuple is
|
|
``None``.
|
|
|
|
Returns
|
|
--------
|
|
List[Tuple[Optional[:class:`CategoryChannel`], List[:class:`abc.GuildChannel`]]]:
|
|
The categories and their associated channels.
|
|
"""
|
|
grouped: Dict[Optional[int], List[NonCategoryChannel]] = {}
|
|
for channel in self._channels.values():
|
|
if isinstance(channel, CategoryChannel):
|
|
grouped.setdefault(channel.id, [])
|
|
continue
|
|
|
|
try:
|
|
grouped[channel.category_id].append(channel)
|
|
except KeyError:
|
|
grouped[channel.category_id] = [channel]
|
|
|
|
def key(t: ByCategoryItem) -> Tuple[Tuple[int, int], List[NonCategoryChannel]]:
|
|
k, v = t
|
|
return ((k.position, k.id) if k else (-1, -1), v)
|
|
|
|
_get = self._channels.get
|
|
as_list: List[ByCategoryItem] = [(_get(k), v) for k, v in grouped.items()] # type: ignore
|
|
as_list.sort(key=key)
|
|
for _, channels in as_list:
|
|
channels.sort(key=attrgetter('_sorting_bucket', 'position', 'id'))
|
|
return as_list
|
|
|
|
def _resolve_channel(self, id: Optional[int], /) -> Optional[Union[GuildChannel, Thread]]:
|
|
if id is None:
|
|
return
|
|
|
|
return self._channels.get(id) or self._threads.get(id)
|
|
|
|
def get_channel_or_thread(self, channel_id: int, /) -> Optional[Union[Thread, GuildChannel]]:
|
|
"""Returns a channel or thread with the given ID.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
channel_id: :class:`int`
|
|
The ID to search for.
|
|
|
|
Returns
|
|
--------
|
|
Optional[Union[:class:`Thread`, :class:`.abc.GuildChannel`]]
|
|
The returned channel, thread, or ``None`` if not found.
|
|
"""
|
|
return self._channels.get(channel_id) or self._threads.get(channel_id)
|
|
|
|
def get_channel(self, channel_id: int, /) -> Optional[GuildChannel]:
|
|
"""Returns a channel with the given ID.
|
|
|
|
.. note::
|
|
|
|
This does *not* search for threads.
|
|
|
|
.. versionchanged:: 2.0
|
|
|
|
``channel_id`` parameter is now positional-only.
|
|
|
|
Parameters
|
|
-----------
|
|
channel_id: :class:`int`
|
|
The ID to search for.
|
|
|
|
Returns
|
|
--------
|
|
Optional[:class:`.abc.GuildChannel`]
|
|
The returned channel or ``None`` if not found.
|
|
"""
|
|
return self._channels.get(channel_id)
|
|
|
|
def get_thread(self, thread_id: int, /) -> Optional[Thread]:
|
|
"""Returns a thread with the given ID.
|
|
|
|
.. note::
|
|
|
|
This does not always retrieve archived threads, as they are not retained in the internal
|
|
cache. Use :func:`fetch_channel` instead.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
thread_id: :class:`int`
|
|
The ID to search for.
|
|
|
|
Returns
|
|
--------
|
|
Optional[:class:`Thread`]
|
|
The returned thread or ``None`` if not found.
|
|
"""
|
|
return self._threads.get(thread_id)
|
|
|
|
def get_emoji(self, emoji_id: int, /) -> Optional[Emoji]:
|
|
"""Returns an emoji with the given ID.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
----------
|
|
emoji_id: int
|
|
The ID to search for.
|
|
|
|
Returns
|
|
--------
|
|
Optional[:class:`Emoji`]
|
|
The returned Emoji or ``None`` if not found.
|
|
"""
|
|
emoji = self._state.get_emoji(emoji_id)
|
|
if emoji and emoji.guild == self:
|
|
return emoji
|
|
return None
|
|
|
|
@property
|
|
def system_channel(self) -> Optional[TextChannel]:
|
|
"""Optional[:class:`TextChannel`]: Returns the guild's channel used for system messages.
|
|
|
|
If no channel is set, then this returns ``None``.
|
|
"""
|
|
channel_id = self._system_channel_id
|
|
return channel_id and self._channels.get(channel_id) # type: ignore
|
|
|
|
@property
|
|
def system_channel_flags(self) -> SystemChannelFlags:
|
|
""":class:`SystemChannelFlags`: Returns the guild's system channel settings."""
|
|
return SystemChannelFlags._from_value(self._system_channel_flags)
|
|
|
|
@property
|
|
def rules_channel(self) -> Optional[TextChannel]:
|
|
"""Optional[:class:`TextChannel`]: Return's the guild's channel used for the rules.
|
|
The guild must be a Community guild.
|
|
|
|
If no channel is set, then this returns ``None``.
|
|
|
|
.. versionadded:: 1.3
|
|
"""
|
|
channel_id = self._rules_channel_id
|
|
return channel_id and self._channels.get(channel_id) # type: ignore
|
|
|
|
@property
|
|
def public_updates_channel(self) -> Optional[TextChannel]:
|
|
"""Optional[:class:`TextChannel`]: Return's the guild's channel where admins and
|
|
moderators of the guilds receive notices from Discord. The guild must be a
|
|
Community guild.
|
|
|
|
If no channel is set, then this returns ``None``.
|
|
|
|
.. versionadded:: 1.4
|
|
"""
|
|
channel_id = self._public_updates_channel_id
|
|
return channel_id and self._channels.get(channel_id) # type: ignore
|
|
|
|
@property
|
|
def afk_channel(self) -> Optional[VocalGuildChannel]:
|
|
"""Optional[:class:`VoiceChannel`]: Returns the guild channel AFK users are moved to.
|
|
|
|
If no channel is set, then this returns ``None``.
|
|
"""
|
|
channel_id = self._afk_channel_id
|
|
return channel_id and self._channels.get(channel_id) # type: ignore
|
|
|
|
@property
|
|
def widget_channel(self) -> Optional[Union[TextChannel, ForumChannel, VoiceChannel, StageChannel]]:
|
|
"""Optional[Union[:class:`TextChannel`, :class:`ForumChannel`, :class:`VoiceChannel`, :class:`StageChannel`]]: Returns
|
|
the widget channel of the guild.
|
|
|
|
If no channel is set, then this returns ``None``.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
channel_id = self._widget_channel_id
|
|
return channel_id and self._channels.get(channel_id) # type: ignore
|
|
|
|
@property
|
|
def emoji_limit(self) -> int:
|
|
""":class:`int`: The maximum number of emoji slots this guild has."""
|
|
more_emoji = 200 if 'MORE_EMOJI' in self.features else 50
|
|
return max(more_emoji, self._PREMIUM_GUILD_LIMITS[self.premium_tier].emoji)
|
|
|
|
@property
|
|
def sticker_limit(self) -> int:
|
|
""":class:`int`: The maximum number of sticker slots this guild has.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
more_stickers = 60 if 'MORE_STICKERS' in self.features else 0
|
|
return max(more_stickers, self._PREMIUM_GUILD_LIMITS[self.premium_tier].stickers)
|
|
|
|
@property
|
|
def bitrate_limit(self) -> float:
|
|
""":class:`float`: The maximum bitrate for voice channels this guild can have."""
|
|
vip_guild = self._PREMIUM_GUILD_LIMITS[1].bitrate if 'VIP_REGIONS' in self.features else 96e3
|
|
return max(vip_guild, self._PREMIUM_GUILD_LIMITS[self.premium_tier].bitrate)
|
|
|
|
@property
|
|
def filesize_limit(self) -> int:
|
|
""":class:`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) -> Sequence[Member]:
|
|
"""Sequence[:class:`Member`]: A list of members that belong to this guild."""
|
|
return utils.SequenceProxy(self._members.values())
|
|
|
|
def get_member(self, user_id: int, /) -> Optional[Member]:
|
|
"""Returns a member with the given ID.
|
|
|
|
.. versionchanged:: 2.0
|
|
|
|
``user_id`` parameter is now positional-only.
|
|
|
|
Parameters
|
|
-----------
|
|
user_id: :class:`int`
|
|
The ID to search for.
|
|
|
|
Returns
|
|
--------
|
|
Optional[:class:`Member`]
|
|
The member or ``None`` if not found.
|
|
"""
|
|
return self._members.get(user_id)
|
|
|
|
@property
|
|
def premium_tier(self) -> int:
|
|
""":class:`int`: The premium tier for this guild. Corresponds to "Server Boost Level" in the official UI.
|
|
The number goes from 0 to 3 inclusive.
|
|
"""
|
|
tier = self._premium_tier
|
|
if tier is not None:
|
|
return tier
|
|
if 'PREMIUM_TIER_3_OVERRIDE' in self.features:
|
|
return 3
|
|
|
|
# Fallback to calculating by the number of boosts
|
|
count = self.premium_subscription_count
|
|
if count < 2:
|
|
return 0
|
|
elif count < 7:
|
|
return 1
|
|
elif count < 14:
|
|
return 2
|
|
else:
|
|
return 3
|
|
|
|
@property
|
|
def premium_subscribers(self) -> List[Member]:
|
|
"""List[:class:`Member`]: A list of members who have subscribed to (boosted) this guild."""
|
|
return [member for member in self.members if member.premium_since is not None]
|
|
|
|
@property
|
|
def roles(self) -> Sequence[Role]:
|
|
"""Sequence[:class:`Role`]: Returns a sequence of the guild's roles in hierarchy order.
|
|
|
|
The first element of this sequence will be the lowest role in the
|
|
hierarchy.
|
|
"""
|
|
return utils.SequenceProxy(self._roles.values(), sorted=True)
|
|
|
|
def get_role(self, role_id: int, /) -> Optional[Role]:
|
|
"""Returns a role with the given ID.
|
|
|
|
.. versionchanged:: 2.0
|
|
|
|
``role_id`` parameter is now positional-only.
|
|
|
|
Parameters
|
|
-----------
|
|
role_id: :class:`int`
|
|
The ID to search for.
|
|
|
|
Returns
|
|
--------
|
|
Optional[:class:`Role`]
|
|
The role or ``None`` if not found.
|
|
"""
|
|
return self._roles.get(role_id)
|
|
|
|
@property
|
|
def default_role(self) -> Role:
|
|
""":class:`Role`: Gets the @everyone role that all members have by default."""
|
|
# The @everyone role is *always* given
|
|
return self.get_role(self.id) # type: ignore
|
|
|
|
@property
|
|
def premium_subscriber_role(self) -> Optional[Role]:
|
|
"""Optional[:class:`Role`]: Gets the premium subscriber role, AKA "boost" role, in this guild.
|
|
|
|
.. versionadded:: 1.6
|
|
"""
|
|
for role in self._roles.values():
|
|
if role.is_premium_subscriber():
|
|
return role
|
|
return None
|
|
|
|
@property
|
|
def stage_instances(self) -> Sequence[StageInstance]:
|
|
"""Sequence[:class:`StageInstance`]: Returns a sequence of the guild's stage instances that
|
|
are currently running.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
return utils.SequenceProxy(self._stage_instances.values())
|
|
|
|
def get_stage_instance(self, stage_instance_id: int, /) -> Optional[StageInstance]:
|
|
"""Returns a stage instance with the given ID.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
stage_instance_id: :class:`int`
|
|
The ID to search for.
|
|
|
|
Returns
|
|
--------
|
|
Optional[:class:`StageInstance`]
|
|
The stage instance or ``None`` if not found.
|
|
"""
|
|
return self._stage_instances.get(stage_instance_id)
|
|
|
|
@property
|
|
def scheduled_events(self) -> Sequence[ScheduledEvent]:
|
|
"""Sequence[:class:`ScheduledEvent`]: Returns a sequence of the guild's scheduled events.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
return utils.SequenceProxy(self._scheduled_events.values())
|
|
|
|
@property
|
|
def scheduled_events_read_state(self) -> ReadState:
|
|
""":class:`ReadState`: Returns the read state representing the guild's scheduled events.
|
|
|
|
.. versionadded:: 2.1
|
|
"""
|
|
return self._state.get_read_state(self.id, ReadStateType.scheduled_events)
|
|
|
|
@property
|
|
def acked_scheduled_event(self) -> Optional[ScheduledEvent]:
|
|
"""Optional[:class:`ScheduledEvent`]: Returns the last scheduled event that the user has acknowledged.
|
|
|
|
.. versionadded:: 2.1
|
|
"""
|
|
return self._scheduled_events.get(self.scheduled_events_read_state.last_acked_id)
|
|
|
|
def get_scheduled_event(self, scheduled_event_id: int, /) -> Optional[ScheduledEvent]:
|
|
"""Returns a scheduled event with the given ID.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
scheduled_event_id: :class:`int`
|
|
The ID to search for.
|
|
|
|
Returns
|
|
--------
|
|
Optional[:class:`ScheduledEvent`]
|
|
The scheduled event or ``None`` if not found.
|
|
"""
|
|
return self._scheduled_events.get(scheduled_event_id)
|
|
|
|
@property
|
|
def owner(self) -> Optional[Member]:
|
|
"""Optional[:class:`Member`]: The member that owns the guild."""
|
|
return self.get_member(self.owner_id) # type: ignore
|
|
|
|
@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_guild_image(self._state, self.id, self._banner, path='banners')
|
|
|
|
@property
|
|
def splash(self) -> Optional[Asset]:
|
|
"""Optional[:class:`Asset`]: Returns the guild's invite splash asset, if available."""
|
|
if self._splash is None:
|
|
return None
|
|
return Asset._from_guild_image(self._state, self.id, self._splash, path='splashes')
|
|
|
|
@property
|
|
def discovery_splash(self) -> Optional[Asset]:
|
|
"""Optional[:class:`Asset`]: Returns the guild's discovery splash asset, if available."""
|
|
if self._discovery_splash is None:
|
|
return None
|
|
return Asset._from_guild_image(self._state, self.id, self._discovery_splash, path='discovery-splashes')
|
|
|
|
@property
|
|
def member_count(self) -> Optional[int]:
|
|
"""Optional[:class:`int`]: Returns the member count if available.
|
|
|
|
.. warning::
|
|
|
|
Due to a Discord limitation, this may not always be up-to-date and accurate.
|
|
"""
|
|
return self._member_count if self._member_count is not None else self.approximate_member_count
|
|
|
|
@property
|
|
def online_count(self) -> Optional[int]:
|
|
"""Optional[:class:`int`]: Returns the online member count.
|
|
|
|
.. versionadded:: 1.9
|
|
|
|
.. warning::
|
|
|
|
Due to a Discord limitation, this may not always be up-to-date and accurate.
|
|
"""
|
|
return self._presence_count
|
|
|
|
@property
|
|
def application_command_count(self) -> Optional[int]:
|
|
"""Optional[:class:`int`]: Returns the application command count if available.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
counts = self.application_command_counts
|
|
if counts:
|
|
sum(counts)
|
|
|
|
@property
|
|
def chunked(self) -> bool:
|
|
""":class:`bool`: Returns a boolean indicating if the guild is "chunked".
|
|
|
|
A chunked guild means that :attr:`member_count` is equal to the
|
|
number of members stored in the internal :attr:`members` cache.
|
|
|
|
If this value returns ``False``, then you should request for
|
|
offline members.
|
|
"""
|
|
return self._chunked
|
|
|
|
@property
|
|
def created_at(self) -> datetime:
|
|
""":class:`datetime.datetime`: Returns the guild's creation time in UTC."""
|
|
return utils.snowflake_time(self.id)
|
|
|
|
def get_member_named(self, name: str, /) -> Optional[Member]:
|
|
"""Returns the first member found that matches the name provided.
|
|
|
|
The name is looked up in the following order:
|
|
|
|
- Username#Discriminator (deprecated)
|
|
- Username#0 (deprecated, only gets users that migrated from their discriminator)
|
|
- Nickname
|
|
- Global name
|
|
- Username
|
|
|
|
If no member is found, ``None`` is returned.
|
|
|
|
.. versionchanged:: 2.0
|
|
|
|
``name`` parameter is now positional-only.
|
|
|
|
.. deprecated:: 2.1
|
|
|
|
Looking up users via discriminator due to Discord API change.
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The name of the member to lookup.
|
|
|
|
Returns
|
|
--------
|
|
Optional[:class:`Member`]
|
|
The member in this guild with the associated name. If not found
|
|
then ``None`` is returned.
|
|
"""
|
|
|
|
members = self.members
|
|
|
|
username, _, discriminator = name.rpartition('#')
|
|
|
|
# If # isn't found then "discriminator" actually has the username
|
|
if not username:
|
|
discriminator, username = username, discriminator
|
|
|
|
if discriminator == '0' or (len(discriminator) == 4 and discriminator.isdigit()):
|
|
return utils.find(lambda m: m.name == username and m.discriminator == discriminator, members)
|
|
|
|
def pred(m: Member) -> bool:
|
|
return m.nick == name or m.global_name == name or m.name == name
|
|
|
|
return utils.find(pred, members)
|
|
|
|
@overload
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: Literal[ChannelType.text],
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
|
|
category: Optional[Snowflake] = ...,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, TextChannelPayload]:
|
|
...
|
|
|
|
@overload
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: Literal[ChannelType.voice],
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
|
|
category: Optional[Snowflake] = ...,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, VoiceChannelPayload]:
|
|
...
|
|
|
|
@overload
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: Literal[ChannelType.stage_voice],
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
|
|
category: Optional[Snowflake] = ...,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, StageChannelPayload]:
|
|
...
|
|
|
|
@overload
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: Literal[ChannelType.category],
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
|
|
category: Optional[Snowflake] = ...,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, CategoryChannelPayload]:
|
|
...
|
|
|
|
@overload
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: Literal[ChannelType.news],
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
|
|
category: Optional[Snowflake] = ...,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, NewsChannelPayload]:
|
|
...
|
|
|
|
@overload
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: Literal[ChannelType.news, ChannelType.text],
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
|
|
category: Optional[Snowflake] = ...,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, Union[TextChannelPayload, NewsChannelPayload]]:
|
|
...
|
|
|
|
@overload
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: Literal[ChannelType.forum],
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
|
|
category: Optional[Snowflake] = ...,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, ForumChannelPayload]:
|
|
...
|
|
|
|
@overload
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: Literal[ChannelType.directory],
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
|
|
category: Optional[Snowflake] = ...,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, DirectoryChannelPayload]:
|
|
...
|
|
|
|
@overload
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: ChannelType,
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ...,
|
|
category: Optional[Snowflake] = ...,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, GuildChannelPayload]:
|
|
...
|
|
|
|
def _create_channel(
|
|
self,
|
|
name: str,
|
|
channel_type: ChannelType,
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING,
|
|
category: Optional[Snowflake] = None,
|
|
**options: Any,
|
|
) -> Coroutine[Any, Any, GuildChannelPayload]:
|
|
if overwrites is MISSING:
|
|
overwrites = {}
|
|
elif not isinstance(overwrites, Mapping):
|
|
raise TypeError('overwrites parameter expects a dict')
|
|
|
|
perms = []
|
|
for target, perm in overwrites.items():
|
|
if not isinstance(perm, PermissionOverwrite):
|
|
raise TypeError(f'Expected PermissionOverwrite received {perm.__class__.__name__}')
|
|
|
|
allow, deny = perm.pair()
|
|
payload = {'allow': allow.value, 'deny': deny.value, 'id': target.id}
|
|
|
|
if isinstance(target, Role):
|
|
payload['type'] = abc._Overwrites.ROLE
|
|
else:
|
|
payload['type'] = abc._Overwrites.MEMBER
|
|
|
|
perms.append(payload)
|
|
|
|
parent_id = category.id if category else None
|
|
return self._state.http.create_channel(
|
|
self.id, channel_type.value, name=name, parent_id=parent_id, permission_overwrites=perms, **options
|
|
)
|
|
|
|
async def create_text_channel(
|
|
self,
|
|
name: str,
|
|
*,
|
|
reason: Optional[str] = None,
|
|
category: Optional[CategoryChannel] = None,
|
|
news: bool = False,
|
|
position: int = MISSING,
|
|
topic: str = MISSING,
|
|
slowmode_delay: int = MISSING,
|
|
nsfw: bool = MISSING,
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING,
|
|
default_auto_archive_duration: int = MISSING,
|
|
default_thread_slowmode_delay: int = MISSING,
|
|
) -> TextChannel:
|
|
"""|coro|
|
|
|
|
Creates a :class:`TextChannel` for the guild.
|
|
|
|
Note that you must have :attr:`~Permissions.manage_channels` to create the channel.
|
|
|
|
The ``overwrites`` parameter can be used to create a 'secret'
|
|
channel upon creation. This parameter expects a :class:`dict` of
|
|
overwrites with the target (either a :class:`Member` or a :class:`Role`)
|
|
as the key and a :class:`PermissionOverwrite` as the value.
|
|
|
|
.. note::
|
|
|
|
Creating a channel of a specified position will not update the position of
|
|
other channels to follow suit. A follow-up call to :meth:`~TextChannel.edit`
|
|
will be required to update the position of the channel in the channel list.
|
|
|
|
.. versionchanged:: 2.0
|
|
This function will now raise :exc:`TypeError` instead of
|
|
``InvalidArgument``.
|
|
|
|
Examples
|
|
----------
|
|
|
|
Creating a basic channel:
|
|
|
|
.. code-block:: python3
|
|
|
|
channel = await guild.create_text_channel('cool-channel')
|
|
|
|
Creating a "secret" channel:
|
|
|
|
.. code-block:: python3
|
|
|
|
overwrites = {
|
|
guild.default_role: discord.PermissionOverwrite(read_messages=False),
|
|
guild.me: discord.PermissionOverwrite(read_messages=True)
|
|
}
|
|
|
|
channel = await guild.create_text_channel('secret', overwrites=overwrites)
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The channel's name.
|
|
overwrites: Dict[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`]
|
|
A :class:`dict` of target (either a role or a member) to
|
|
:class:`PermissionOverwrite` to apply upon creation of a channel.
|
|
Useful for creating secret channels.
|
|
category: Optional[:class:`CategoryChannel`]
|
|
The category to place the newly created channel under.
|
|
The permissions will be automatically synced to category if no
|
|
overwrites are provided.
|
|
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.
|
|
topic: :class:`str`
|
|
The new channel's topic.
|
|
slowmode_delay: :class:`int`
|
|
Specifies the slowmode rate limit for user in this channel, in seconds.
|
|
The maximum value possible is ``21600``.
|
|
nsfw: :class:`bool`
|
|
To mark the channel as NSFW or not.
|
|
news: :class:`bool`
|
|
Whether to create the text channel as a news channel.
|
|
|
|
.. versionadded:: 2.0
|
|
default_auto_archive_duration: :class:`int`
|
|
The default auto archive duration for threads created in the text channel (in minutes).
|
|
Must be one of ``60``, ``1440``, ``4320``, or ``10080``.
|
|
|
|
.. versionadded:: 2.0
|
|
default_thread_slowmode_delay: :class:`int`
|
|
The default slowmode delay in seconds for threads created in the text channel.
|
|
|
|
.. versionadded:: 2.0
|
|
reason: Optional[:class:`str`]
|
|
The reason for creating this channel. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have the proper permissions to create this channel.
|
|
HTTPException
|
|
Creating the channel failed.
|
|
TypeError
|
|
The permission overwrite information is not in proper form.
|
|
|
|
Returns
|
|
-------
|
|
:class:`TextChannel`
|
|
The channel that was just created.
|
|
"""
|
|
|
|
options = {}
|
|
if position is not MISSING:
|
|
options['position'] = position
|
|
|
|
if topic is not MISSING:
|
|
options['topic'] = topic
|
|
|
|
if slowmode_delay is not MISSING:
|
|
options['rate_limit_per_user'] = slowmode_delay
|
|
|
|
if nsfw is not MISSING:
|
|
options['nsfw'] = nsfw
|
|
|
|
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_rate_limit_per_user'] = default_thread_slowmode_delay
|
|
|
|
data = await self._create_channel(
|
|
name,
|
|
overwrites=overwrites,
|
|
channel_type=ChannelType.news if news else ChannelType.text,
|
|
category=category,
|
|
reason=reason,
|
|
**options,
|
|
)
|
|
channel = TextChannel(state=self._state, guild=self, data=data)
|
|
|
|
# temporarily add to the cache
|
|
self._channels[channel.id] = channel
|
|
return channel
|
|
|
|
async def create_voice_channel(
|
|
self,
|
|
name: str,
|
|
*,
|
|
reason: Optional[str] = None,
|
|
category: Optional[CategoryChannel] = None,
|
|
position: int = MISSING,
|
|
bitrate: int = MISSING,
|
|
user_limit: int = MISSING,
|
|
rtc_region: Optional[str] = MISSING,
|
|
video_quality_mode: VideoQualityMode = MISSING,
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING,
|
|
) -> VoiceChannel:
|
|
"""|coro|
|
|
|
|
This is similar to :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead.
|
|
|
|
.. versionchanged:: 2.0
|
|
This function will now raise :exc:`TypeError` instead of
|
|
``InvalidArgument``.
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The channel's name.
|
|
overwrites: Dict[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`]
|
|
A :class:`dict` of target (either a role or a member) to
|
|
:class:`PermissionOverwrite` to apply upon creation of a channel.
|
|
Useful for creating secret channels.
|
|
category: Optional[:class:`CategoryChannel`]
|
|
The category to place the newly created channel under.
|
|
The permissions will be automatically synced to category if no
|
|
overwrites are provided.
|
|
position: :class:`int`
|
|
The position in the channel list. This is a number that starts
|
|
at 0. e.g. the top channel is position 0.
|
|
bitrate: :class:`int`
|
|
The channel's preferred audio bitrate in bits per second.
|
|
user_limit: :class:`int`
|
|
The channel's limit for number of members that can be in a voice channel.
|
|
rtc_region: Optional[:class:`str`]
|
|
The region for the voice channel's voice communication.
|
|
A value of ``None`` indicates automatic voice region detection.
|
|
|
|
.. versionadded:: 1.7
|
|
video_quality_mode: :class:`VideoQualityMode`
|
|
The camera video quality for the voice channel's participants.
|
|
|
|
.. versionadded:: 2.0
|
|
reason: Optional[:class:`str`]
|
|
The reason for creating this channel. Shows up on the audit log.
|
|
|
|
Raises
|
|
------
|
|
Forbidden
|
|
You do not have the proper permissions to create this channel.
|
|
HTTPException
|
|
Creating the channel failed.
|
|
TypeError
|
|
The permission overwrite information is not in proper form.
|
|
|
|
Returns
|
|
-------
|
|
:class:`VoiceChannel`
|
|
The channel that was just created.
|
|
"""
|
|
options = {}
|
|
if position is not MISSING:
|
|
options['position'] = position
|
|
|
|
if bitrate is not MISSING:
|
|
options['bitrate'] = bitrate
|
|
|
|
if user_limit is not MISSING:
|
|
options['user_limit'] = user_limit
|
|
|
|
if rtc_region is not MISSING:
|
|
options['rtc_region'] = None if rtc_region is None else rtc_region
|
|
|
|
if video_quality_mode is not MISSING:
|
|
if not isinstance(video_quality_mode, VideoQualityMode):
|
|
raise TypeError('video_quality_mode must be of type VideoQualityMode')
|
|
options['video_quality_mode'] = video_quality_mode.value
|
|
|
|
data = await self._create_channel(
|
|
name, overwrites=overwrites, channel_type=ChannelType.voice, category=category, reason=reason, **options
|
|
)
|
|
channel = VoiceChannel(state=self._state, guild=self, data=data)
|
|
|
|
# temporarily add to the cache
|
|
self._channels[channel.id] = channel
|
|
return channel
|
|
|
|
async def create_stage_channel(
|
|
self,
|
|
name: str,
|
|
*,
|
|
reason: Optional[str] = None,
|
|
category: Optional[CategoryChannel] = None,
|
|
position: int = MISSING,
|
|
bitrate: int = MISSING,
|
|
user_limit: int = MISSING,
|
|
rtc_region: Optional[str] = MISSING,
|
|
video_quality_mode: VideoQualityMode = MISSING,
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING,
|
|
) -> StageChannel:
|
|
"""|coro|
|
|
|
|
This is similar to :meth:`create_text_channel` except makes a :class:`StageChannel` instead.
|
|
|
|
.. versionadded:: 1.7
|
|
|
|
.. versionchanged:: 2.0
|
|
This function will now raise :exc:`TypeError` instead of
|
|
``InvalidArgument``.
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The channel's name.
|
|
overwrites: Dict[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`]
|
|
A :class:`dict` of target (either a role or a member) to
|
|
:class:`PermissionOverwrite` to apply upon creation of a channel.
|
|
Useful for creating secret channels.
|
|
category: Optional[:class:`CategoryChannel`]
|
|
The category to place the newly created channel under.
|
|
The permissions will be automatically synced to category if no
|
|
overwrites are provided.
|
|
position: :class:`int`
|
|
The position in the channel list. This is a number that starts
|
|
at 0. e.g. the top channel is position 0.
|
|
bitrate: :class:`int`
|
|
The channel's preferred audio bitrate in bits per second.
|
|
|
|
.. versionadded:: 2.0
|
|
user_limit: :class:`int`
|
|
The channel's limit for number of members that can be in a voice channel.
|
|
|
|
.. versionadded:: 2.0
|
|
rtc_region: Optional[:class:`str`]
|
|
The region for the voice channel's voice communication.
|
|
A value of ``None`` indicates automatic voice region detection.
|
|
|
|
.. versionadded:: 2.0
|
|
video_quality_mode: :class:`VideoQualityMode`
|
|
The camera video quality for the voice channel's participants.
|
|
|
|
.. versionadded:: 2.0
|
|
reason: Optional[:class:`str`]
|
|
The reason for creating this channel. Shows up on the audit log.
|
|
|
|
Raises
|
|
------
|
|
Forbidden
|
|
You do not have the proper permissions to create this channel.
|
|
HTTPException
|
|
Creating the channel failed.
|
|
TypeError
|
|
The permission overwrite information is not in proper form.
|
|
|
|
Returns
|
|
-------
|
|
:class:`StageChannel`
|
|
The channel that was just created.
|
|
"""
|
|
|
|
options = {}
|
|
if position is not MISSING:
|
|
options['position'] = position
|
|
|
|
if bitrate is not MISSING:
|
|
options['bitrate'] = bitrate
|
|
|
|
if user_limit is not MISSING:
|
|
options['user_limit'] = user_limit
|
|
|
|
if rtc_region is not MISSING:
|
|
options['rtc_region'] = None if rtc_region is None else rtc_region
|
|
|
|
if video_quality_mode is not MISSING:
|
|
if not isinstance(video_quality_mode, VideoQualityMode):
|
|
raise TypeError('video_quality_mode must be of type VideoQualityMode')
|
|
options['video_quality_mode'] = video_quality_mode.value
|
|
|
|
data = await self._create_channel(
|
|
name, overwrites=overwrites, channel_type=ChannelType.stage_voice, category=category, reason=reason, **options
|
|
)
|
|
channel = StageChannel(state=self._state, guild=self, data=data)
|
|
|
|
# temporarily add to the cache
|
|
self._channels[channel.id] = channel
|
|
return channel
|
|
|
|
async def create_category(
|
|
self,
|
|
name: str,
|
|
*,
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING,
|
|
reason: Optional[str] = None,
|
|
position: int = MISSING,
|
|
) -> CategoryChannel:
|
|
"""|coro|
|
|
|
|
Same as :meth:`create_text_channel` except makes a :class:`CategoryChannel` instead.
|
|
|
|
.. note::
|
|
|
|
The ``category`` parameter is not supported in this function since categories
|
|
cannot have categories.
|
|
|
|
.. versionchanged:: 2.0
|
|
This function will now raise :exc:`TypeError` instead of
|
|
``InvalidArgument``.
|
|
|
|
Raises
|
|
------
|
|
Forbidden
|
|
You do not have the proper permissions to create this channel.
|
|
HTTPException
|
|
Creating the channel failed.
|
|
TypeError
|
|
The permission overwrite information is not in proper form.
|
|
|
|
Returns
|
|
-------
|
|
:class:`CategoryChannel`
|
|
The channel that was just created.
|
|
"""
|
|
options: Dict[str, Any] = {}
|
|
if position is not MISSING:
|
|
options['position'] = position
|
|
|
|
data = await self._create_channel(
|
|
name, overwrites=overwrites, channel_type=ChannelType.category, reason=reason, **options
|
|
)
|
|
channel = CategoryChannel(state=self._state, guild=self, data=data)
|
|
|
|
# temporarily add to the cache
|
|
self._channels[channel.id] = channel
|
|
return channel
|
|
|
|
create_category_channel = create_category
|
|
|
|
async def create_directory(
|
|
self,
|
|
name: str,
|
|
*,
|
|
reason: Optional[str] = None,
|
|
category: Optional[CategoryChannel] = None,
|
|
position: int = MISSING,
|
|
topic: str = MISSING,
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING,
|
|
) -> DirectoryChannel:
|
|
"""|coro|
|
|
|
|
This is similar to :meth:`create_text_channel` except makes a :class:`DirectoryChannel` instead.
|
|
|
|
The ``overwrites`` parameter can be used to create a 'secret'
|
|
channel upon creation. This parameter expects a :class:`dict` of
|
|
overwrites with the target (either a :class:`Member` or a :class:`Role`)
|
|
as the key and a :class:`PermissionOverwrite` as the value.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The channel's name.
|
|
overwrites: Dict[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`]
|
|
A :class:`dict` of target (either a role or a member) to
|
|
:class:`PermissionOverwrite` to apply upon creation of a channel.
|
|
Useful for creating secret channels.
|
|
category: Optional[:class:`CategoryChannel`]
|
|
The category to place the newly created channel under.
|
|
The permissions will be automatically synced to category if no
|
|
overwrites are provided.
|
|
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.
|
|
topic: :class:`str`
|
|
The new channel's topic.
|
|
reason: Optional[:class:`str`]
|
|
The reason for creating this channel. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have the proper permissions to create this channel.
|
|
HTTPException
|
|
Creating the channel failed.
|
|
TypeError
|
|
The permission overwrite information is not in proper form.
|
|
|
|
Returns
|
|
-------
|
|
:class:`TextChannel`
|
|
The channel that was just created.
|
|
"""
|
|
options = {}
|
|
if position is not MISSING:
|
|
options['position'] = position
|
|
if topic is not MISSING:
|
|
options['topic'] = topic
|
|
|
|
data = await self._create_channel(
|
|
name,
|
|
overwrites=overwrites,
|
|
channel_type=ChannelType.directory,
|
|
category=category,
|
|
reason=reason,
|
|
**options,
|
|
)
|
|
channel = DirectoryChannel(state=self._state, guild=self, data=data)
|
|
|
|
# temporarily add to the cache
|
|
self._channels[channel.id] = channel
|
|
return channel
|
|
|
|
create_directory_channel = create_directory
|
|
|
|
async def create_forum(
|
|
self,
|
|
name: str,
|
|
*,
|
|
topic: str = MISSING,
|
|
position: int = MISSING,
|
|
category: Optional[CategoryChannel] = None,
|
|
slowmode_delay: int = MISSING,
|
|
nsfw: bool = MISSING,
|
|
overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING,
|
|
reason: Optional[str] = None,
|
|
default_auto_archive_duration: int = MISSING,
|
|
default_thread_slowmode_delay: int = MISSING,
|
|
default_sort_order: ForumOrderType = MISSING,
|
|
default_reaction_emoji: EmojiInputType = MISSING,
|
|
default_layout: ForumLayoutType = MISSING,
|
|
available_tags: Sequence[ForumTag] = MISSING,
|
|
) -> ForumChannel:
|
|
"""|coro|
|
|
|
|
Similar to :meth:`create_text_channel` except makes a :class:`ForumChannel` instead.
|
|
|
|
The ``overwrites`` parameter can be used to create a 'secret'
|
|
channel upon creation. This parameter expects a :class:`dict` of
|
|
overwrites with the target (either a :class:`Member` or a :class:`Role`)
|
|
as the key and a :class:`PermissionOverwrite` as the value.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The channel's name.
|
|
overwrites: Dict[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`]
|
|
A :class:`dict` of target (either a role or a member) to
|
|
:class:`PermissionOverwrite` to apply upon creation of a channel.
|
|
Useful for creating secret channels.
|
|
topic: :class:`str`
|
|
The channel's topic.
|
|
category: Optional[:class:`CategoryChannel`]
|
|
The category to place the newly created channel under.
|
|
The permissions will be automatically synced to category if no
|
|
overwrites are provided.
|
|
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.
|
|
nsfw: :class:`bool`
|
|
To mark the channel as NSFW or not.
|
|
slowmode_delay: :class:`int`
|
|
Specifies the slowmode rate limit for users in this channel, in seconds.
|
|
The maximum possible value is ``21600``.
|
|
reason: Optional[:class:`str`]
|
|
The reason for creating this channel. Shows up in the audit log.
|
|
default_auto_archive_duration: :class:`int`
|
|
The default auto archive duration for threads created in the forum channel (in minutes).
|
|
Must be one of ``60``, ``1440``, ``4320``, or ``10080``.
|
|
default_thread_slowmode_delay: :class:`int`
|
|
The default slowmode delay in seconds for threads created in this forum.
|
|
default_sort_order: :class:`ForumOrderType`
|
|
The default sort order for posts in this forum channel.
|
|
default_reaction_emoji: Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]
|
|
The default reaction emoji for threads created in this forum to show in the
|
|
add reaction button.
|
|
default_layout: :class:`ForumLayoutType`
|
|
The default layout for posts in this forum.
|
|
available_tags: Sequence[:class:`ForumTag`]
|
|
The available tags for this forum channel.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have the proper permissions to create this channel.
|
|
HTTPException
|
|
Creating the channel failed.
|
|
TypeError
|
|
The permission overwrite information is not in proper form.
|
|
|
|
Returns
|
|
-------
|
|
:class:`ForumChannel`
|
|
The channel that was just created.
|
|
"""
|
|
options = {}
|
|
|
|
if position is not MISSING:
|
|
options['position'] = position
|
|
|
|
if topic is not MISSING:
|
|
options['topic'] = topic
|
|
|
|
if slowmode_delay is not MISSING:
|
|
options['rate_limit_per_user'] = slowmode_delay
|
|
|
|
if nsfw is not MISSING:
|
|
options['nsfw'] = nsfw
|
|
|
|
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_rate_limit_per_user'] = default_thread_slowmode_delay
|
|
|
|
if default_sort_order is not MISSING:
|
|
if not isinstance(default_sort_order, ForumOrderType):
|
|
raise TypeError(
|
|
f'default_sort_order parameter must be a ForumOrderType not {default_sort_order.__class__.__name__}'
|
|
)
|
|
|
|
options['default_sort_order'] = default_sort_order.value
|
|
|
|
if default_reaction_emoji is not MISSING:
|
|
if isinstance(default_reaction_emoji, _EmojiTag):
|
|
options['default_reaction_emoji'] = default_reaction_emoji._to_partial()._to_forum_tag_payload()
|
|
elif isinstance(default_reaction_emoji, str):
|
|
options['default_reaction_emoji'] = PartialEmoji.from_str(default_reaction_emoji)._to_forum_tag_payload()
|
|
else:
|
|
raise ValueError(f'default_reaction_emoji parameter must be either Emoji, PartialEmoji, or str')
|
|
|
|
if default_layout is not MISSING:
|
|
if not isinstance(default_layout, ForumLayoutType):
|
|
raise TypeError(
|
|
f'default_layout parameter must be a ForumLayoutType not {default_layout.__class__.__name__}'
|
|
)
|
|
|
|
options['default_forum_layout'] = default_layout.value
|
|
|
|
if available_tags is not MISSING:
|
|
options['available_tags'] = [t.to_dict() for t in available_tags]
|
|
|
|
data = await self._create_channel(
|
|
name=name, overwrites=overwrites, channel_type=ChannelType.forum, category=category, reason=reason, **options
|
|
)
|
|
|
|
channel = ForumChannel(state=self._state, guild=self, data=data)
|
|
|
|
# temporarily add to the cache
|
|
self._channels[channel.id] = channel
|
|
return channel
|
|
|
|
create_forum_channel = create_forum
|
|
|
|
async def leave(self) -> None:
|
|
"""|coro|
|
|
|
|
Leaves the guild.
|
|
|
|
.. note::
|
|
|
|
You cannot leave a guild that you own, you must delete it instead
|
|
via :meth:`delete`.
|
|
|
|
Raises
|
|
--------
|
|
HTTPException
|
|
Leaving the guild failed.
|
|
"""
|
|
await self._state.http.leave_guild(self.id, lurking=not self.is_joined())
|
|
|
|
async def delete(self) -> None:
|
|
"""|coro|
|
|
|
|
Deletes the guild. You must be the guild owner to delete the
|
|
guild.
|
|
|
|
Raises
|
|
--------
|
|
HTTPException
|
|
Deleting the guild failed.
|
|
Forbidden
|
|
You do not have permissions to delete the guild.
|
|
"""
|
|
|
|
await self._state.http.delete_guild(self.id)
|
|
|
|
async def edit(
|
|
self,
|
|
*,
|
|
reason: Optional[str] = MISSING,
|
|
name: str = MISSING,
|
|
description: Optional[str] = MISSING,
|
|
icon: Optional[bytes] = MISSING,
|
|
banner: Optional[bytes] = MISSING,
|
|
splash: Optional[bytes] = MISSING,
|
|
discovery_splash: Optional[bytes] = MISSING,
|
|
community: bool = MISSING,
|
|
afk_channel: Optional[VoiceChannel] = MISSING,
|
|
owner: Snowflake = MISSING,
|
|
afk_timeout: int = MISSING,
|
|
default_notifications: NotificationLevel = MISSING,
|
|
verification_level: VerificationLevel = MISSING,
|
|
explicit_content_filter: ContentFilter = MISSING,
|
|
vanity_code: str = MISSING,
|
|
system_channel: Optional[TextChannel] = MISSING,
|
|
system_channel_flags: SystemChannelFlags = MISSING,
|
|
preferred_locale: Locale = MISSING,
|
|
rules_channel: Optional[TextChannel] = MISSING,
|
|
public_updates_channel: Optional[TextChannel] = MISSING,
|
|
premium_progress_bar_enabled: bool = MISSING,
|
|
discoverable: bool = MISSING,
|
|
invites_disabled: bool = MISSING,
|
|
widget_enabled: bool = MISSING,
|
|
widget_channel: Optional[Snowflake] = MISSING,
|
|
mfa_level: MFALevel = MISSING,
|
|
) -> Guild:
|
|
r"""|coro|
|
|
|
|
Edits the guild.
|
|
|
|
You must have :attr:`~Permissions.manage_guild` to edit the guild.
|
|
|
|
.. versionchanged:: 2.0
|
|
The newly updated guild is returned.
|
|
|
|
.. versionchanged:: 2.0
|
|
The ``region`` keyword parameter has been removed.
|
|
|
|
.. versionchanged:: 2.0
|
|
This function will now raise :exc:`TypeError` or
|
|
:exc:`ValueError` instead of ``InvalidArgument``.
|
|
|
|
Parameters
|
|
----------
|
|
name: :class:`str`
|
|
The new name of the guild.
|
|
description: Optional[:class:`str`]
|
|
The new description of the guild. Could be ``None`` for no description.
|
|
icon: :class:`bytes`
|
|
A :term:`py:bytes-like object` representing the icon. Only PNG/JPEG is supported.
|
|
GIF is only available to guilds that contain ``ANIMATED_ICON`` in :attr:`Guild.features`.
|
|
Could be ``None`` to denote removal of the icon.
|
|
banner: :class:`bytes`
|
|
A :term:`py:bytes-like object` representing the banner. Only PNG/JPEG is supported.
|
|
GIF is only available to guilds that contain ``ANIMATED_BANNER`` in :attr:`Guild.features`.
|
|
Could be ``None`` to denote removal of the banner.
|
|
This is only available to guilds that contain ``BANNER`` in :attr:`Guild.features`.
|
|
splash: :class:`bytes`
|
|
A :term:`py:bytes-like object` representing the invite splash.
|
|
Only PNG/JPEG supported. Could be ``None`` to denote removing the
|
|
splash. This is only available to guilds that contain ``INVITE_SPLASH``
|
|
in :attr:`Guild.features`.
|
|
discovery_splash: :class:`bytes`
|
|
A :term:`py:bytes-like object` representing the discovery splash.
|
|
Only PNG/JPEG supported. Could be ``None`` to denote removing the
|
|
splash. This is only available to guilds that contain ``DISCOVERABLE``
|
|
in :attr:`Guild.features`.
|
|
|
|
.. versionadded:: 2.0
|
|
community: :class:`bool`
|
|
Whether the guild should be a Community guild. If set to ``True``\, both ``rules_channel``
|
|
and ``public_updates_channel`` parameters are required.
|
|
|
|
.. versionadded:: 2.0
|
|
afk_channel: Optional[:class:`VoiceChannel`]
|
|
The new channel that is the AFK channel. Could be ``None`` for no AFK channel.
|
|
afk_timeout: :class:`int`
|
|
The number of seconds until someone is moved to the AFK channel.
|
|
owner: :class:`Member`
|
|
The new owner of the guild to transfer ownership to. Note that you must
|
|
be owner of the guild to do this.
|
|
verification_level: :class:`VerificationLevel`
|
|
The new verification level for the guild.
|
|
default_notifications: :class:`NotificationLevel`
|
|
The new default notification level for the guild.
|
|
explicit_content_filter: :class:`ContentFilter`
|
|
The new explicit content filter for the guild.
|
|
vanity_code: :class:`str`
|
|
The new vanity code for the guild.
|
|
system_channel: Optional[:class:`TextChannel`]
|
|
The new channel that is used for the system channel. Could be ``None`` for no system channel.
|
|
system_channel_flags: :class:`SystemChannelFlags`
|
|
The new system channel settings to use with the new system channel.
|
|
preferred_locale: :class:`Locale`
|
|
The new preferred locale for the guild. Used as the primary language in the guild.
|
|
|
|
.. versionchanged:: 2.0
|
|
|
|
Now accepts an enum instead of :class:`str`.
|
|
rules_channel: Optional[:class:`TextChannel`]
|
|
The new channel that is used for rules. This is only available to
|
|
guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no rules
|
|
channel.
|
|
|
|
.. versionadded:: 1.4
|
|
public_updates_channel: Optional[:class:`TextChannel`]
|
|
The new channel that is used for public updates from Discord. This is only available to
|
|
guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no
|
|
public updates channel.
|
|
|
|
.. versionadded:: 1.4
|
|
premium_progress_bar_enabled: :class:`bool`
|
|
Whether the premium AKA server boost level progress bar should be enabled for the guild.
|
|
|
|
.. versionadded:: 2.0
|
|
discoverable: :class:`bool`
|
|
Whether server discovery is enabled for this guild.
|
|
|
|
.. versionadded:: 2.0
|
|
invites_disabled: :class:`bool`
|
|
Whether joining via invites should be disabled for the guild.
|
|
|
|
.. versionadded:: 2.0
|
|
widget_enabled: :class:`bool`
|
|
Whether to enable the widget for the guild.
|
|
|
|
.. versionadded:: 2.0
|
|
widget_channel: Optional[:class:`abc.Snowflake`]
|
|
The new widget channel. ``None`` removes the widget channel.
|
|
|
|
.. versionadded:: 2.0
|
|
mfa_level: :class:`MFALevel`
|
|
The guild's new Multi-Factor Authentication requirement level.
|
|
Note that you must be owner of the guild to do this.
|
|
|
|
.. versionadded:: 2.0
|
|
reason: Optional[:class:`str`]
|
|
The reason for editing this guild. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to edit the guild.
|
|
HTTPException
|
|
Editing the guild failed.
|
|
ValueError
|
|
The image format passed in to ``icon`` is invalid. It must be
|
|
PNG or JPG. This is also raised if you are not the owner of the
|
|
guild and request an ownership transfer.
|
|
TypeError
|
|
The type passed to the ``default_notifications``, ``verification_level``,
|
|
``explicit_content_filter``, ``system_channel_flags``, or ``mfa_level`` parameter was
|
|
of the incorrect type.
|
|
|
|
Returns
|
|
--------
|
|
:class:`Guild`
|
|
The newly updated guild. Note that this has the same limitations as
|
|
mentioned in :meth:`Client.fetch_guild` and may not have full data.
|
|
"""
|
|
http = self._state.http
|
|
|
|
if vanity_code is not MISSING:
|
|
await http.change_vanity_code(self.id, vanity_code, reason=reason)
|
|
|
|
fields: Dict[str, Any] = {}
|
|
if name is not MISSING:
|
|
fields['name'] = name
|
|
|
|
if description is not MISSING:
|
|
fields['description'] = description
|
|
|
|
if preferred_locale is not MISSING:
|
|
fields['preferred_locale'] = str(preferred_locale)
|
|
|
|
if afk_timeout is not MISSING:
|
|
fields['afk_timeout'] = afk_timeout
|
|
|
|
if icon is not MISSING:
|
|
if icon is None:
|
|
fields['icon'] = icon
|
|
else:
|
|
fields['icon'] = utils._bytes_to_base64_data(icon)
|
|
|
|
if banner is not MISSING:
|
|
if banner is None:
|
|
fields['banner'] = banner
|
|
else:
|
|
fields['banner'] = utils._bytes_to_base64_data(banner)
|
|
|
|
if splash is not MISSING:
|
|
if splash is None:
|
|
fields['splash'] = splash
|
|
else:
|
|
fields['splash'] = utils._bytes_to_base64_data(splash)
|
|
|
|
if discovery_splash is not MISSING:
|
|
if discovery_splash is None:
|
|
fields['discovery_splash'] = discovery_splash
|
|
else:
|
|
fields['discovery_splash'] = utils._bytes_to_base64_data(discovery_splash)
|
|
|
|
if default_notifications is not MISSING:
|
|
if not isinstance(default_notifications, NotificationLevel):
|
|
raise TypeError('default_notifications field must be of type NotificationLevel')
|
|
fields['default_message_notifications'] = default_notifications.value
|
|
|
|
if afk_channel is not MISSING:
|
|
if afk_channel is None:
|
|
fields['afk_channel_id'] = afk_channel
|
|
else:
|
|
fields['afk_channel_id'] = afk_channel.id
|
|
|
|
if system_channel is not MISSING:
|
|
if system_channel is None:
|
|
fields['system_channel_id'] = system_channel
|
|
else:
|
|
fields['system_channel_id'] = system_channel.id
|
|
|
|
if rules_channel is not MISSING:
|
|
if rules_channel is None:
|
|
fields['rules_channel_id'] = rules_channel
|
|
else:
|
|
fields['rules_channel_id'] = rules_channel.id
|
|
|
|
if public_updates_channel is not MISSING:
|
|
if public_updates_channel is None:
|
|
fields['public_updates_channel_id'] = public_updates_channel
|
|
else:
|
|
fields['public_updates_channel_id'] = public_updates_channel.id
|
|
|
|
if owner is not MISSING:
|
|
if self.owner_id != self._state.self_id:
|
|
raise ValueError('To transfer ownership you must be the owner of the guild')
|
|
|
|
fields['owner_id'] = owner.id
|
|
|
|
if verification_level is not MISSING:
|
|
if not isinstance(verification_level, VerificationLevel):
|
|
raise TypeError('verification_level field must be of type VerificationLevel')
|
|
|
|
fields['verification_level'] = verification_level.value
|
|
|
|
if explicit_content_filter is not MISSING:
|
|
if not isinstance(explicit_content_filter, ContentFilter):
|
|
raise TypeError('explicit_content_filter field must be of type ContentFilter')
|
|
|
|
fields['explicit_content_filter'] = explicit_content_filter.value
|
|
|
|
if system_channel_flags is not MISSING:
|
|
if not isinstance(system_channel_flags, SystemChannelFlags):
|
|
raise TypeError('system_channel_flags field must be of type SystemChannelFlags')
|
|
|
|
fields['system_channel_flags'] = system_channel_flags.value
|
|
|
|
if any(feat is not MISSING for feat in (community, discoverable, invites_disabled)):
|
|
features = set(self.features)
|
|
|
|
if community is not MISSING:
|
|
if community:
|
|
if 'rules_channel_id' in fields and 'public_updates_channel_id' in fields:
|
|
features.add('COMMUNITY')
|
|
else:
|
|
raise ValueError(
|
|
'community field requires both rules_channel and public_updates_channel fields to be provided'
|
|
)
|
|
else:
|
|
features.discard('COMMUNITY')
|
|
|
|
if discoverable is not MISSING:
|
|
if discoverable:
|
|
features.add('DISCOVERABLE')
|
|
else:
|
|
features.discard('DISCOVERABLE')
|
|
|
|
if invites_disabled is not MISSING:
|
|
if invites_disabled:
|
|
features.add('INVITES_DISABLED')
|
|
else:
|
|
features.discard('INVITES_DISABLED')
|
|
|
|
fields['features'] = list(features)
|
|
|
|
if premium_progress_bar_enabled is not MISSING:
|
|
fields['premium_progress_bar_enabled'] = premium_progress_bar_enabled
|
|
|
|
widget_payload: EditWidgetSettings = {}
|
|
if widget_channel is not MISSING:
|
|
widget_payload['channel_id'] = None if widget_channel is None else widget_channel.id
|
|
if widget_enabled is not MISSING:
|
|
widget_payload['enabled'] = widget_enabled
|
|
|
|
if widget_payload:
|
|
await self._state.http.edit_widget(self.id, payload=widget_payload, reason=reason)
|
|
|
|
if mfa_level is not MISSING:
|
|
if not isinstance(mfa_level, MFALevel):
|
|
raise TypeError('mfa_level must be of type MFALevel')
|
|
|
|
await http.edit_guild_mfa_level(self.id, mfa_level=mfa_level.value)
|
|
|
|
data = await http.edit_guild(self.id, reason=reason, **fields)
|
|
return Guild(data=data, state=self._state)
|
|
|
|
async def top_channels(self) -> List[Union[TextChannel, VoiceChannel, StageChannel, PartialMessageable]]:
|
|
"""|coro|
|
|
|
|
Retrieves the top 10 most read channels in the guild. Channels are returned in order of descending usage.
|
|
|
|
.. note::
|
|
|
|
For guilds without many members, this may return an empty list.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Retrieving the top channels failed.
|
|
|
|
Returns
|
|
--------
|
|
List[Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`StageChannel`, :class:`PartialMessageable`]]
|
|
The top 10 most read channels. Falls back to :class:`PartialMessageable` if the channel is not found in cache.
|
|
"""
|
|
state = self._state
|
|
data = await state.http.get_top_guild_channels(self.id)
|
|
return [self.get_channel(int(c)) or PartialMessageable(id=int(c), state=state, guild_id=self.id) for c in data] # type: ignore
|
|
|
|
async def webhook_channels(self) -> List[Union[TextChannel, VoiceChannel, StageChannel, PartialMessageable]]:
|
|
"""|coro|
|
|
|
|
Retrieves the channels that the current user can create webhooks in for the guild.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Retrieving the webhook channels failed.
|
|
|
|
Returns
|
|
--------
|
|
List[Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`StageChannel`, :class:`PartialMessageable`]]
|
|
The channels that the current user can create webhooks in. Falls back to :class:`PartialMessageable` if the channel is not found in cache.
|
|
Any :class:`PartialMessageable` will have its :attr:`PartialMessageable.name` filled in.
|
|
"""
|
|
state = self._state
|
|
data = await state.http.get_guild_webhook_channels(self.id)
|
|
return [self.get_channel(int(c['id'])) or PartialMessageable._from_webhook_channel(self, c) for c in data] # type: ignore
|
|
|
|
async def fetch_channels(self) -> Sequence[GuildChannel]:
|
|
"""|coro|
|
|
|
|
Retrieves all :class:`abc.GuildChannel` that the guild has.
|
|
|
|
.. note::
|
|
|
|
This method is an API call. For general usage, consider :attr:`channels` instead.
|
|
|
|
.. versionadded:: 1.2
|
|
|
|
Raises
|
|
-------
|
|
InvalidData
|
|
An unknown channel type was received from Discord.
|
|
HTTPException
|
|
Retrieving the channels failed.
|
|
|
|
Returns
|
|
-------
|
|
Sequence[:class:`abc.GuildChannel`]
|
|
All channels in the guild.
|
|
"""
|
|
data = await self._state.http.get_all_guild_channels(self.id)
|
|
|
|
def convert(d):
|
|
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)
|
|
return channel
|
|
|
|
return [convert(d) for d in data]
|
|
|
|
async def fetch_member(self, member_id: int, /) -> Member:
|
|
"""|coro|
|
|
|
|
Retrieves a :class:`Member` from a guild ID, and a member ID.
|
|
|
|
.. note::
|
|
|
|
This method is an API call. If you have member cache, consider :meth:`get_member` instead.
|
|
|
|
.. versionchanged:: 2.0
|
|
|
|
``member_id`` parameter is now positional-only.
|
|
|
|
Parameters
|
|
-----------
|
|
member_id: :class:`int`
|
|
The member's ID to fetch from.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have access to the guild.
|
|
HTTPException
|
|
Fetching the member failed.
|
|
NotFound
|
|
The member could not be found.
|
|
|
|
Returns
|
|
--------
|
|
:class:`Member`
|
|
The member from the member ID.
|
|
"""
|
|
data = await self._state.http.get_member(self.id, member_id)
|
|
return Member(data=data, state=self._state, guild=self)
|
|
|
|
async def fetch_member_profile(
|
|
self,
|
|
member_id: int,
|
|
/,
|
|
*,
|
|
with_mutual_guilds: bool = True,
|
|
with_mutual_friends_count: bool = False,
|
|
with_mutual_friends: bool = True,
|
|
) -> MemberProfile:
|
|
"""|coro|
|
|
|
|
Retrieves a :class:`.MemberProfile` from a guild ID, and a member ID.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
------------
|
|
member_id: :class:`int`
|
|
The ID of the member to fetch their profile for.
|
|
with_mutual_guilds: :class:`bool`
|
|
Whether to fetch mutual guilds.
|
|
This fills in :attr:`.MemberProfile.mutual_guilds`.
|
|
with_mutual_friends_count: :class:`bool`
|
|
Whether to fetch the number of mutual friends.
|
|
This fills in :attr:`.MemberProfile.mutual_friends_count`.
|
|
with_mutual_friends: :class:`bool`
|
|
Whether to fetch mutual friends.
|
|
This fills in :attr:`.MemberProfile.mutual_friends` and :attr:`.MemberProfile.mutual_friends_count`,
|
|
but requires an extra API call.
|
|
|
|
Raises
|
|
-------
|
|
NotFound
|
|
A user with this ID does not exist.
|
|
Forbidden
|
|
You do not have a mutual with this user, and and the user is not a bot.
|
|
HTTPException
|
|
Fetching the profile failed.
|
|
InvalidData
|
|
The member is not in this guild or has blocked you.
|
|
|
|
Returns
|
|
--------
|
|
:class:`.MemberProfile`
|
|
The profile of the member.
|
|
"""
|
|
state = self._state
|
|
data = await state.http.get_user_profile(
|
|
member_id, self.id, with_mutual_guilds=with_mutual_guilds, with_mutual_friends_count=with_mutual_friends_count
|
|
)
|
|
if 'guild_member_profile' not in data:
|
|
raise InvalidData('Member is not in this guild')
|
|
if 'guild_member' not in data:
|
|
raise InvalidData('Member has blocked you')
|
|
mutual_friends = None
|
|
if with_mutual_friends and not data['user'].get('bot', False):
|
|
mutual_friends = await state.http.get_mutual_friends(member_id)
|
|
|
|
return MemberProfile(state=state, data=data, mutual_friends=mutual_friends, guild=self)
|
|
|
|
async def fetch_ban(self, user: Snowflake) -> BanEntry:
|
|
"""|coro|
|
|
|
|
Retrieves the :class:`BanEntry` for a user.
|
|
|
|
You must have :attr:`~Permissions.ban_members` to get this information.
|
|
|
|
Parameters
|
|
-----------
|
|
user: :class:`abc.Snowflake`
|
|
The user to get ban information from.
|
|
|
|
Raises
|
|
------
|
|
Forbidden
|
|
You do not have proper permissions to get the information.
|
|
NotFound
|
|
This user is not banned.
|
|
HTTPException
|
|
An error occurred while fetching the information.
|
|
|
|
Returns
|
|
-------
|
|
:class:`BanEntry`
|
|
The :class:`BanEntry` object for the specified user.
|
|
"""
|
|
data = await self._state.http.get_ban(user.id, self.id)
|
|
return BanEntry(user=User(state=self._state, data=data['user']), reason=data['reason'])
|
|
|
|
async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, Thread]:
|
|
"""|coro|
|
|
|
|
Retrieves a :class:`.abc.GuildChannel` or :class:`.Thread` with the specified ID.
|
|
|
|
.. note::
|
|
|
|
This method is an API call. For general usage, consider :meth:`get_channel_or_thread` instead.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Raises
|
|
-------
|
|
InvalidData
|
|
An unknown channel type was received from Discord
|
|
or the guild the channel belongs to is not the same
|
|
as the one in this object points to.
|
|
HTTPException
|
|
Retrieving the channel failed.
|
|
NotFound
|
|
Invalid Channel ID.
|
|
Forbidden
|
|
You do not have permission to fetch this channel.
|
|
|
|
Returns
|
|
--------
|
|
Union[:class:`.abc.GuildChannel`, :class:`.Thread`]
|
|
The channel from the ID.
|
|
"""
|
|
data = await self._state.http.get_channel(channel_id)
|
|
|
|
factory, ch_type = _threaded_guild_channel_factory(data['type'])
|
|
if factory is None:
|
|
raise InvalidData('Unknown channel type {type} for channel ID {id}.'.format_map(data))
|
|
|
|
if ch_type in (ChannelType.group, ChannelType.private):
|
|
raise InvalidData('Channel ID resolved to a private channel')
|
|
|
|
guild_id = int(data['guild_id']) # type: ignore # channel won't be a private channel
|
|
if self.id != guild_id:
|
|
raise InvalidData('Guild ID resolved to a different guild')
|
|
|
|
channel: GuildChannel = factory(guild=self, state=self._state, data=data) # type: ignore # channel won't be a private channel
|
|
return channel
|
|
|
|
async def bans(
|
|
self,
|
|
*,
|
|
limit: Optional[int] = 1000,
|
|
before: Snowflake = MISSING,
|
|
after: Snowflake = MISSING,
|
|
paginate: bool = True,
|
|
) -> AsyncIterator[BanEntry]:
|
|
"""Retrieves an :term:`asynchronous iterator` of the users that are banned from the guild as a :class:`BanEntry`.
|
|
|
|
You must have :attr:`~Permissions.ban_members` to get this information.
|
|
|
|
.. versionchanged:: 2.0
|
|
Due to a breaking change in Discord's API, this now returns a paginated iterator instead of a list.
|
|
|
|
Examples
|
|
---------
|
|
|
|
Usage ::
|
|
|
|
async for entry in guild.bans(limit=150):
|
|
print(entry.user, entry.reason)
|
|
|
|
Flattening into a list ::
|
|
|
|
bans = [entry async for entry in guild.bans(limit=2000)]
|
|
# bans is now a list of BanEntry...
|
|
|
|
All parameters are optional.
|
|
|
|
Parameters
|
|
-----------
|
|
limit: Optional[:class:`int`]
|
|
The number of bans to retrieve. If ``None``, it retrieves every ban in
|
|
the guild. Note, however, that this would make it a slow operation.
|
|
Defaults to ``1000``.
|
|
before: :class:`.abc.Snowflake`
|
|
Retrieves bans before this user.
|
|
after: :class:`.abc.Snowflake`
|
|
Retrieve bans after this user.
|
|
paginate: :class:`bool`
|
|
Whether to paginate the results. If ``False``, all bans are fetched with a single request and yielded,
|
|
``limit`` is ignored, and ``before`` and ``after`` must not be provided.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have proper permissions to get the information.
|
|
HTTPException
|
|
An error occurred while fetching the information.
|
|
TypeError
|
|
Both ``after`` and ``before`` were provided, as Discord does not
|
|
support this type of pagination.
|
|
|
|
Yields
|
|
--------
|
|
:class:`BanEntry`
|
|
The ban entry of the banned user.
|
|
"""
|
|
if before is not MISSING and after is not MISSING:
|
|
raise TypeError('bans pagination does not support both before and after')
|
|
|
|
# This endpoint paginates in ascending order
|
|
_state = self._state
|
|
endpoint = _state.http.get_bans
|
|
|
|
if not paginate:
|
|
# For user accounts, not providing a limit will return *every* ban,
|
|
# as they were too lazy to implement proper pagination in the client
|
|
# However, pagination may be wanted for guilds with massive ban lists
|
|
data = await endpoint(self.id)
|
|
for entry in data:
|
|
yield BanEntry(user=User(state=_state, data=entry['user']), reason=entry['reason'])
|
|
return
|
|
|
|
async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]):
|
|
before_id = before.id if before else None
|
|
data = await endpoint(self.id, limit=retrieve, before=before_id)
|
|
|
|
if data:
|
|
if limit is not None:
|
|
limit -= len(data)
|
|
|
|
before = Object(id=int(data[0]['user']['id']))
|
|
|
|
return data, before, limit
|
|
|
|
async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[int]):
|
|
after_id = after.id if after else None
|
|
data = await endpoint(self.id, limit=retrieve, after=after_id)
|
|
|
|
if data:
|
|
if limit is not None:
|
|
limit -= len(data)
|
|
|
|
after = Object(id=int(data[-1]['user']['id']))
|
|
|
|
return data, after, limit
|
|
|
|
if before:
|
|
strategy, state = _before_strategy, before
|
|
else:
|
|
strategy, state = _after_strategy, after
|
|
|
|
while True:
|
|
retrieve = 1000 if limit is None else min(limit, 1000)
|
|
if retrieve < 1:
|
|
return
|
|
|
|
data, state, limit = await strategy(retrieve, state, limit)
|
|
|
|
# Terminate loop on next iteration; there's no data left after this
|
|
if len(data) < 1000:
|
|
limit = 0
|
|
|
|
for e in data:
|
|
yield BanEntry(user=User(state=_state, data=e['user']), reason=e['reason'])
|
|
|
|
def search(
|
|
self,
|
|
content: str = MISSING,
|
|
*,
|
|
limit: Optional[int] = 25,
|
|
offset: int = 0,
|
|
before: SnowflakeTime = MISSING,
|
|
after: SnowflakeTime = MISSING,
|
|
include_nsfw: bool = MISSING,
|
|
channels: Collection[Snowflake] = MISSING,
|
|
authors: Collection[Snowflake] = MISSING,
|
|
author_types: Collection[MessageSearchAuthorType] = MISSING,
|
|
mentions: Collection[Snowflake] = MISSING,
|
|
mention_everyone: bool = MISSING,
|
|
pinned: bool = MISSING,
|
|
has: Collection[MessageSearchHasType] = MISSING,
|
|
embed_types: Collection[EmbedType] = MISSING,
|
|
embed_providers: Collection[str] = MISSING,
|
|
link_hostnames: Collection[str] = MISSING,
|
|
attachment_filenames: Collection[str] = MISSING,
|
|
attachment_extensions: Collection[str] = MISSING,
|
|
application_commands: Collection[Snowflake] = MISSING,
|
|
oldest_first: bool = False,
|
|
most_relevant: bool = False,
|
|
) -> AsyncIterator[Message]:
|
|
"""Returns an :term:`asynchronous iterator` that enables searching the guild's messages.
|
|
|
|
You must have :attr:`~Permissions.read_message_history` to do this.
|
|
|
|
.. note::
|
|
|
|
Due to a limitation with the Discord API, the :class:`.Message`
|
|
objects returned by this method do not contain complete
|
|
:attr:`.Message.reactions` data.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Examples
|
|
---------
|
|
|
|
Usage ::
|
|
|
|
counter = 0
|
|
async for message in guild.search('hi', limit=200):
|
|
if message.author == client.user:
|
|
counter += 1
|
|
|
|
Flattening into a list: ::
|
|
|
|
messages = [message async for message in guild.search('test', limit=123)]
|
|
# messages is now a list of Message...
|
|
|
|
All parameters are optional.
|
|
|
|
Parameters
|
|
-----------
|
|
content: :class:`str`
|
|
The message content to search for.
|
|
limit: Optional[:class:`int`]
|
|
The number of messages to retrieve.
|
|
If ``None``, retrieves every message in the results. Note, however,
|
|
that this would make it a slow operation. Additionally, note that the
|
|
search API has a maximum pagination offset of 5000 (subject to change),
|
|
so a limit of over 5000 or ``None`` may eventually raise an exception.
|
|
offset: :class:`int`
|
|
The pagination offset to start at.
|
|
before: Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]
|
|
Retrieve messages before this date or message.
|
|
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
|
If the datetime is naive, it is assumed to be local time.
|
|
after: Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]
|
|
Retrieve messages after this date or message.
|
|
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
|
If the datetime is naive, it is assumed to be local time.
|
|
nsfw_allowed: :class:`bool`
|
|
Whether to include messages from NSFW channels. Defaults to :attr:`~discord.ClientUser.nsfw_allowed`.
|
|
channels: List[Union[:class:`abc.GuildChannel`, :class:`abc.PrivateChannel`, :class:`Thread`]]
|
|
The channels to filter by.
|
|
authors: List[:class:`User`]
|
|
The authors to filter by.
|
|
author_types: List[:class:`str`]
|
|
The author types to filter by. Can be one of ``user``, ``bot``, or ``webhook``.
|
|
These can be negated by prefixing with ``-``, which will exclude them.
|
|
mentions: List[:class:`User`]
|
|
The mentioned users to filter by.
|
|
mention_everyone: :class:`bool`
|
|
Whether to filter by messages that do or do not mention @everyone.
|
|
pinned: :class:`bool`
|
|
Whether to filter by messages that are or are not pinned.
|
|
has: List[:class:`str`]
|
|
The message attributes to filter by. Can be one of ``image``, ``sound``,
|
|
``video``, ``file``, ``sticker``, ``embed``, or ``link``. These can be
|
|
negated by prefixing with ``-``, which will exclude them.
|
|
embed_types: List[:class:`str`]
|
|
The embed types to filter by.
|
|
embed_providers: List[:class:`str`]
|
|
The embed providers to filter by (e.g. tenor).
|
|
link_hostnames: List[:class:`str`]
|
|
The link hostnames to filter by (e.g. google.com).
|
|
attachment_filenames: List[:class:`str`]
|
|
The attachment filenames to filter by.
|
|
attachment_extensions: List[:class:`str`]
|
|
The attachment extensions to filter by (e.g. txt).
|
|
application_commands: List[:class:`abc.ApplicationCommand`]
|
|
The used application commands to filter by.
|
|
oldest_first: :class:`bool`
|
|
Whether to return the oldest results first.
|
|
most_relevant: :class:`bool`
|
|
Whether to sort the results by relevance. Using this with ``oldest_first``
|
|
will return the least relevant results first.
|
|
|
|
Raises
|
|
------
|
|
Forbidden
|
|
You do not have permissions to search the channel's messages.
|
|
HTTPException
|
|
The request to search messages failed.
|
|
|
|
Yields
|
|
-------
|
|
:class:`Message`
|
|
The message with the message data parsed.
|
|
"""
|
|
return abc._handle_message_search(
|
|
self,
|
|
limit=limit,
|
|
offset=offset,
|
|
before=before,
|
|
after=after,
|
|
content=content,
|
|
include_nsfw=include_nsfw,
|
|
channels=channels,
|
|
authors=authors,
|
|
author_types=author_types,
|
|
mentions=mentions,
|
|
mention_everyone=mention_everyone,
|
|
pinned=pinned,
|
|
has=has,
|
|
embed_types=embed_types,
|
|
embed_providers=embed_providers,
|
|
link_hostnames=link_hostnames,
|
|
attachment_filenames=attachment_filenames,
|
|
attachment_extensions=attachment_extensions,
|
|
application_commands=application_commands,
|
|
oldest_first=oldest_first,
|
|
most_relevant=most_relevant,
|
|
)
|
|
|
|
async def prune_members(
|
|
self,
|
|
*,
|
|
days: int,
|
|
compute_prune_count: bool = True,
|
|
roles: Collection[Snowflake] = MISSING,
|
|
reason: Optional[str] = None,
|
|
) -> Optional[int]:
|
|
r"""|coro|
|
|
|
|
Prunes the guild from its inactive members.
|
|
|
|
The inactive members are denoted if they have not logged on in
|
|
``days`` number of days and they have no roles.
|
|
|
|
You must have :attr:`~Permissions.kick_members` to do this.
|
|
|
|
To check how many members you would prune without actually pruning,
|
|
see the :meth:`estimate_pruned_members` function.
|
|
|
|
To prune members that have specific roles see the ``roles`` parameter.
|
|
|
|
.. versionchanged:: 1.4
|
|
The ``roles`` keyword-only parameter was added.
|
|
|
|
.. versionchanged:: 2.0
|
|
This function will now raise :exc:`TypeError` instead of
|
|
``InvalidArgument``.
|
|
|
|
Parameters
|
|
-----------
|
|
days: :class:`int`
|
|
The number of days before counting as inactive.
|
|
reason: Optional[:class:`str`]
|
|
The reason for doing this action. Shows up on the audit log.
|
|
compute_prune_count: :class:`bool`
|
|
Whether to compute the prune count. This defaults to ``True``
|
|
which makes it prone to timeouts in very large guilds. In order
|
|
to prevent timeouts, you must set this to ``False``. If this is
|
|
set to ``False``\, then this function will always return ``None``.
|
|
roles: List[:class:`abc.Snowflake`]
|
|
A list of :class:`abc.Snowflake` that represent roles to include in the pruning process. If a member
|
|
has a role that is not specified, they'll be excluded.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to prune members.
|
|
HTTPException
|
|
An error occurred while pruning members.
|
|
TypeError
|
|
An integer was not passed for ``days``.
|
|
|
|
Returns
|
|
---------
|
|
Optional[:class:`int`]
|
|
The number of members pruned. If ``compute_prune_count`` is ``False``
|
|
then this returns ``None``.
|
|
"""
|
|
if not isinstance(days, int):
|
|
raise TypeError(f'Expected int for ``days``, received {days.__class__.__name__} instead')
|
|
|
|
if roles:
|
|
role_ids = [str(role.id) for role in roles]
|
|
else:
|
|
role_ids = []
|
|
|
|
data = await self._state.http.prune_members(
|
|
self.id, days, compute_prune_count=compute_prune_count, roles=role_ids, reason=reason
|
|
)
|
|
return data['pruned']
|
|
|
|
async def templates(self) -> List[Template]:
|
|
"""|coro|
|
|
|
|
Gets the list of templates from this guild.
|
|
|
|
You must have :attr:`~.Permissions.manage_guild` to do this.
|
|
|
|
.. versionadded:: 1.7
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You don't have permissions to get the templates.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`Template`]
|
|
The templates for this guild.
|
|
"""
|
|
from .template import Template
|
|
|
|
data = await self._state.http.guild_templates(self.id)
|
|
return [Template(data=d, state=self._state) for d in data]
|
|
|
|
async def webhooks(self) -> List[Webhook]:
|
|
"""|coro|
|
|
|
|
Gets the list of webhooks from this guild.
|
|
|
|
You must have :attr:`~.Permissions.manage_webhooks` to do this.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You don't have permissions to get the webhooks.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`Webhook`]
|
|
The webhooks for this guild.
|
|
"""
|
|
from .webhook import Webhook
|
|
|
|
data = await self._state.http.guild_webhooks(self.id)
|
|
return [Webhook.from_state(d, state=self._state) for d in data]
|
|
|
|
async def estimate_pruned_members(self, *, days: int, roles: Collection[Snowflake] = MISSING) -> Optional[int]:
|
|
"""|coro|
|
|
|
|
Similar to :meth:`prune_members` except instead of actually
|
|
pruning members, it returns how many members it would prune
|
|
from the guild had it been called.
|
|
|
|
.. versionchanged:: 2.0
|
|
The returned value can be ``None``.
|
|
|
|
.. versionchanged:: 2.0
|
|
This function will now raise :exc:`TypeError` instead of
|
|
``InvalidArgument``.
|
|
|
|
Parameters
|
|
-----------
|
|
days: :class:`int`
|
|
The number of days before counting as inactive.
|
|
roles: List[:class:`abc.Snowflake`]
|
|
A list of :class:`abc.Snowflake` that represent roles to include in the estimate. If a member
|
|
has a role that is not specified, they'll be excluded.
|
|
|
|
.. versionadded:: 1.7
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to prune members.
|
|
HTTPException
|
|
An error occurred while fetching the prune members estimate.
|
|
TypeError
|
|
An integer was not passed for ``days``.
|
|
|
|
Returns
|
|
---------
|
|
Optional[:class:`int`]
|
|
The number of members estimated to be pruned.
|
|
"""
|
|
if not isinstance(days, int):
|
|
raise TypeError(f'Expected int for ``days``, received {days.__class__.__name__} instead')
|
|
|
|
if roles:
|
|
role_ids = [str(role.id) for role in roles]
|
|
else:
|
|
role_ids = []
|
|
|
|
data = await self._state.http.estimate_pruned_members(self.id, days, role_ids)
|
|
return data['pruned']
|
|
|
|
async def invites(self) -> List[Invite]:
|
|
"""|coro|
|
|
|
|
Returns a list of all active instant invites from the guild.
|
|
|
|
You must have :attr:`~Permissions.manage_guild` to get this information.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have proper permissions to get the information.
|
|
HTTPException
|
|
An error occurred while fetching the information.
|
|
|
|
Returns
|
|
-------
|
|
List[:class:`Invite`]
|
|
The list of invites that are currently active.
|
|
"""
|
|
data = await self._state.http.invites_from(self.id)
|
|
result = []
|
|
for invite in data:
|
|
channel = self.get_channel(int(invite['channel']['id'])) # type: ignore
|
|
result.append(Invite(state=self._state, data=invite, guild=self, channel=channel))
|
|
|
|
return result
|
|
|
|
async def create_template(self, *, name: str, description: str = MISSING) -> Template:
|
|
"""|coro|
|
|
|
|
Creates a template for the guild.
|
|
|
|
You must have :attr:`~Permissions.manage_guild` to do this.
|
|
|
|
.. versionadded:: 1.7
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The name of the template.
|
|
description: :class:`str`
|
|
The description of the template.
|
|
"""
|
|
from .template import Template
|
|
|
|
payload = {'name': name}
|
|
|
|
if description:
|
|
payload['description'] = description
|
|
|
|
data = await self._state.http.create_template(self.id, payload)
|
|
|
|
return Template(state=self._state, data=data)
|
|
|
|
async def create_integration(self, *, type: IntegrationType, id: int, reason: Optional[str] = None) -> None:
|
|
"""|coro|
|
|
|
|
Attaches an integration to the guild. This "enables" an existing integration.
|
|
|
|
You must have :attr:`~Permissions.manage_guild` to do this.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
Parameters
|
|
-----------
|
|
type: :class:`str`
|
|
The integration type (e.g. Twitch).
|
|
id: :class:`int`
|
|
The integration ID.
|
|
reason: Optional[:class:`str`]
|
|
The reason for creating this integration. Shows up on the audit log.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permission to create the integration.
|
|
HTTPException
|
|
The account could not be found.
|
|
"""
|
|
await self._state.http.create_integration(self.id, type, id, reason=reason)
|
|
|
|
async def integrations(self, *, has_commands: bool = False) -> List[Integration]:
|
|
"""|coro|
|
|
|
|
Returns a list of all integrations attached to the guild.
|
|
|
|
You must have :attr:`~Permissions.manage_guild` to do this.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
Parameters
|
|
-----------
|
|
has_commands: :class:`bool`
|
|
Whether to only return integrations that have commands registered.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permission to create the integration.
|
|
HTTPException
|
|
Fetching the integrations failed.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`Integration`]
|
|
The list of integrations that are attached to the guild.
|
|
"""
|
|
data = await self._state.http.get_all_integrations(self.id, has_commands=has_commands)
|
|
|
|
def convert(d):
|
|
factory, _ = _integration_factory(d['type'])
|
|
return factory(guild=self, data=d)
|
|
|
|
return [convert(d) for d in data]
|
|
|
|
async def fetch_stickers(self) -> List[GuildSticker]:
|
|
r"""|coro|
|
|
|
|
Retrieves a list of all :class:`Sticker`\s for the guild.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. note::
|
|
|
|
This method is an API call. For general usage, consider :attr:`stickers` instead.
|
|
|
|
Raises
|
|
---------
|
|
HTTPException
|
|
An error occurred fetching the stickers.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`GuildSticker`]
|
|
The retrieved stickers.
|
|
"""
|
|
data = await self._state.http.get_all_guild_stickers(self.id)
|
|
return [GuildSticker(state=self._state, data=d) for d in data]
|
|
|
|
async def fetch_sticker(self, sticker_id: int, /) -> GuildSticker:
|
|
"""|coro|
|
|
|
|
Retrieves a custom :class:`Sticker` from the guild.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. note::
|
|
|
|
This method is an API call.
|
|
For general usage, consider iterating over :attr:`stickers` instead.
|
|
|
|
Parameters
|
|
-------------
|
|
sticker_id: :class:`int`
|
|
The sticker's ID.
|
|
|
|
Raises
|
|
---------
|
|
NotFound
|
|
The sticker requested could not be found.
|
|
HTTPException
|
|
An error occurred fetching the sticker.
|
|
|
|
Returns
|
|
--------
|
|
:class:`GuildSticker`
|
|
The retrieved sticker.
|
|
"""
|
|
data = await self._state.http.get_guild_sticker(self.id, sticker_id)
|
|
return GuildSticker(state=self._state, data=data)
|
|
|
|
async def create_sticker(
|
|
self,
|
|
*,
|
|
name: str,
|
|
description: str,
|
|
emoji: str,
|
|
file: File,
|
|
reason: Optional[str] = None,
|
|
) -> GuildSticker:
|
|
"""|coro|
|
|
|
|
Creates a :class:`Sticker` for the guild.
|
|
|
|
You must have :attr:`~Permissions.manage_emojis_and_stickers` to do this.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The sticker name. Must be at least 2 characters.
|
|
description: :class:`str`
|
|
The sticker's description.
|
|
emoji: :class:`str`
|
|
The name of a unicode emoji that represents the sticker's expression.
|
|
file: :class:`File`
|
|
The file of the sticker to upload.
|
|
reason: :class:`str`
|
|
The reason for creating this sticker. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You are not allowed to create stickers.
|
|
HTTPException
|
|
An error occurred creating a sticker.
|
|
|
|
Returns
|
|
--------
|
|
:class:`GuildSticker`
|
|
The created sticker.
|
|
"""
|
|
payload = {
|
|
'name': name,
|
|
'description': description or '',
|
|
}
|
|
|
|
try:
|
|
emoji = unicodedata.name(emoji)
|
|
except TypeError:
|
|
pass
|
|
else:
|
|
emoji = emoji.replace(' ', '_')
|
|
|
|
payload['tags'] = emoji
|
|
|
|
data = await self._state.http.create_guild_sticker(self.id, payload, file, reason)
|
|
return self._state.store_sticker(self, data)
|
|
|
|
async def delete_sticker(self, sticker: Snowflake, /, *, reason: Optional[str] = None) -> None:
|
|
"""|coro|
|
|
|
|
Deletes the custom :class:`Sticker` from the guild.
|
|
|
|
You must have :attr:`~Permissions.manage_emojis_and_stickers` to do this.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
sticker: :class:`abc.Snowflake`
|
|
The sticker you are deleting.
|
|
reason: Optional[:class:`str`]
|
|
The reason for deleting this sticker. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You are not allowed to delete stickers.
|
|
HTTPException
|
|
An error occurred deleting the sticker.
|
|
"""
|
|
await self._state.http.delete_guild_sticker(self.id, sticker.id, reason)
|
|
|
|
async def subscribed_scheduled_events(self) -> List[Union[ScheduledEvent, Object]]:
|
|
"""|coro|
|
|
|
|
Retrieves a list of all scheduled events that the current user is subscribed to.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Retrieving the subscribed scheduled events failed.
|
|
|
|
Returns
|
|
--------
|
|
List[Union[:class:`ScheduledEvent`, :class:`Object`]]
|
|
The subscribed scheduled events. Falls back to an object if the event is not found in cache.
|
|
"""
|
|
data = await self._state.http.get_subscribed_scheduled_events(self.id)
|
|
return [
|
|
self.get_scheduled_event(int(d['guild_scheduled_event_id'])) or Object(id=int(d['guild_scheduled_event_id']))
|
|
for d in data
|
|
]
|
|
|
|
async def fetch_scheduled_events(self, *, with_counts: bool = True) -> List[ScheduledEvent]:
|
|
"""|coro|
|
|
|
|
Retrieves a list of all scheduled events for the guild.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
------------
|
|
with_counts: :class:`bool`
|
|
Whether to include the number of users that are subscribed to the event.
|
|
Defaults to ``True``.
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Retrieving the scheduled events failed.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`ScheduledEvent`]
|
|
The scheduled events.
|
|
"""
|
|
data = await self._state.http.get_scheduled_events(self.id, with_counts)
|
|
return [ScheduledEvent(state=self._state, data=d) for d in data]
|
|
|
|
async def fetch_scheduled_event(self, scheduled_event_id: int, /, *, with_counts: bool = True) -> ScheduledEvent:
|
|
"""|coro|
|
|
|
|
Retrieves a scheduled event from the guild.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
------------
|
|
scheduled_event_id: :class:`int`
|
|
The scheduled event ID.
|
|
with_counts: :class:`bool`
|
|
Whether to include the number of users that are subscribed to the event.
|
|
Defaults to ``True``.
|
|
|
|
Raises
|
|
-------
|
|
NotFound
|
|
The scheduled event was not found.
|
|
HTTPException
|
|
Retrieving the scheduled event failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`ScheduledEvent`
|
|
The scheduled event.
|
|
"""
|
|
data = await self._state.http.get_scheduled_event(self.id, scheduled_event_id, with_counts)
|
|
return ScheduledEvent(state=self._state, data=data)
|
|
|
|
@overload
|
|
async def create_scheduled_event(
|
|
self,
|
|
*,
|
|
name: str,
|
|
start_time: datetime,
|
|
entity_type: Literal[EntityType.external] = ...,
|
|
privacy_level: PrivacyLevel = ...,
|
|
location: str = ...,
|
|
end_time: datetime = ...,
|
|
description: str = ...,
|
|
image: bytes = ...,
|
|
directory_broadcast: bool = ...,
|
|
reason: Optional[str] = ...,
|
|
) -> ScheduledEvent:
|
|
...
|
|
|
|
@overload
|
|
async def create_scheduled_event(
|
|
self,
|
|
*,
|
|
name: str,
|
|
start_time: datetime,
|
|
entity_type: Literal[EntityType.stage_instance, EntityType.voice] = ...,
|
|
privacy_level: PrivacyLevel = ...,
|
|
channel: Snowflake = ...,
|
|
end_time: datetime = ...,
|
|
description: str = ...,
|
|
image: bytes = ...,
|
|
directory_broadcast: bool = ...,
|
|
reason: Optional[str] = ...,
|
|
) -> ScheduledEvent:
|
|
...
|
|
|
|
@overload
|
|
async def create_scheduled_event(
|
|
self,
|
|
*,
|
|
name: str,
|
|
start_time: datetime,
|
|
privacy_level: PrivacyLevel = ...,
|
|
location: str = ...,
|
|
end_time: datetime = ...,
|
|
description: str = ...,
|
|
image: bytes = ...,
|
|
directory_broadcast: bool = ...,
|
|
reason: Optional[str] = ...,
|
|
) -> ScheduledEvent:
|
|
...
|
|
|
|
@overload
|
|
async def create_scheduled_event(
|
|
self,
|
|
*,
|
|
name: str,
|
|
start_time: datetime,
|
|
privacy_level: PrivacyLevel = ...,
|
|
channel: Union[VoiceChannel, StageChannel] = ...,
|
|
end_time: datetime = ...,
|
|
description: str = ...,
|
|
image: bytes = ...,
|
|
directory_broadcast: bool = ...,
|
|
reason: Optional[str] = ...,
|
|
) -> ScheduledEvent:
|
|
...
|
|
|
|
async def create_scheduled_event(
|
|
self,
|
|
*,
|
|
name: str,
|
|
start_time: datetime,
|
|
entity_type: EntityType = MISSING,
|
|
privacy_level: PrivacyLevel = PrivacyLevel.guild_only,
|
|
channel: Optional[Snowflake] = MISSING,
|
|
location: str = MISSING,
|
|
end_time: datetime = MISSING,
|
|
description: str = MISSING,
|
|
image: bytes = MISSING,
|
|
directory_broadcast: bool = False,
|
|
reason: Optional[str] = None,
|
|
) -> ScheduledEvent:
|
|
r"""|coro|
|
|
|
|
Creates a scheduled event for the guild.
|
|
|
|
You must have :attr:`~Permissions.manage_events` to do this.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
------------
|
|
name: :class:`str`
|
|
The name of the scheduled event.
|
|
description: :class:`str`
|
|
The description of the scheduled event.
|
|
channel: Optional[:class:`abc.Snowflake`]
|
|
The channel to send the scheduled event to. If the channel is
|
|
a :class:`StageInstance` or :class:`VoiceChannel` then
|
|
it automatically sets the entity type.
|
|
|
|
Required if ``entity_type`` is either :attr:`EntityType.voice` or
|
|
:attr:`EntityType.stage_instance`.
|
|
start_time: :class:`datetime.datetime`
|
|
The scheduled start time of the scheduled event. This must be a timezone-aware
|
|
datetime object. Consider using :func:`utils.utcnow`.
|
|
end_time: :class:`datetime.datetime`
|
|
The scheduled end time of the scheduled event. This must be a timezone-aware
|
|
datetime object. Consider using :func:`utils.utcnow`.
|
|
|
|
Required if the entity type is :attr:`EntityType.external`.
|
|
privacy_level: :class:`PrivacyLevel`
|
|
The privacy level of the scheduled event.
|
|
entity_type: :class:`EntityType`
|
|
The entity type of the scheduled event. If the channel is a
|
|
:class:`StageInstance` or :class:`VoiceChannel` then this is
|
|
automatically set to the appropriate entity type. If no channel
|
|
is passed then the entity type is assumed to be
|
|
:attr:`EntityType.external`
|
|
image: :class:`bytes`
|
|
The image of the scheduled event.
|
|
location: :class:`str`
|
|
The location of the scheduled event.
|
|
|
|
Required if the ``entity_type`` is :attr:`EntityType.external`.
|
|
directory_broadcast: :class:`bool`
|
|
Whether to broadcast the scheduled event to the directories the guild is in.
|
|
You should first check eligibility with :meth:`directory_broadcast_eligibility`.
|
|
|
|
.. versionadded:: 2.1
|
|
reason: Optional[:class:`str`]
|
|
The reason for creating this scheduled event. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
TypeError
|
|
``image`` was not a :term:`py:bytes-like object`, or ``privacy_level``
|
|
was not a :class:`PrivacyLevel`, or ``entity_type`` was not an
|
|
:class:`EntityType`, ``status`` was not an :class:`EventStatus`,
|
|
or an argument was provided that was incompatible with the provided
|
|
``entity_type``.
|
|
ValueError
|
|
``start_time`` or ``end_time`` was not a timezone-aware datetime object.
|
|
Forbidden
|
|
You are not allowed to create scheduled events.
|
|
HTTPException
|
|
Creating the scheduled event failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`ScheduledEvent`
|
|
The created scheduled event.
|
|
"""
|
|
payload: Dict[str, Any] = {
|
|
'name': name,
|
|
'privacy_level': int(privacy_level or PrivacyLevel.guild_only.value),
|
|
'broadcast_to_directory_channels': directory_broadcast,
|
|
}
|
|
metadata = {}
|
|
|
|
if start_time.tzinfo is None:
|
|
raise ValueError(
|
|
'start_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.'
|
|
)
|
|
payload['scheduled_start_time'] = start_time.isoformat()
|
|
|
|
if privacy_level:
|
|
if not isinstance(privacy_level, PrivacyLevel):
|
|
raise TypeError('privacy_level must be of type PrivacyLevel')
|
|
payload['privacy_level'] = (privacy_level or PrivacyLevel.guild_only).value
|
|
|
|
entity_type = entity_type or getattr(channel, '_scheduled_event_entity_type', MISSING)
|
|
if entity_type is MISSING:
|
|
if channel and isinstance(channel, Object):
|
|
if channel.type is VoiceChannel:
|
|
entity_type = EntityType.voice
|
|
elif channel.type is StageChannel:
|
|
entity_type = EntityType.stage_instance
|
|
elif location not in (MISSING, None):
|
|
entity_type = EntityType.external
|
|
else:
|
|
if not isinstance(entity_type, EntityType):
|
|
raise TypeError('entity_type must be of type EntityType')
|
|
|
|
payload['entity_type'] = entity_type.value
|
|
|
|
if entity_type is None:
|
|
raise TypeError(
|
|
f'invalid GuildChannel type passed; must be VoiceChannel or StageChannel not {channel.__class__.__name__}'
|
|
)
|
|
|
|
if description is not MISSING:
|
|
payload['description'] = description
|
|
|
|
if image is not MISSING:
|
|
image_as_str: str = utils._bytes_to_base64_data(image)
|
|
payload['image'] = image_as_str
|
|
|
|
if entity_type in (EntityType.stage_instance, EntityType.voice):
|
|
if channel in (MISSING, None):
|
|
raise TypeError('channel must be set when entity_type is voice or stage_instance')
|
|
|
|
payload['channel_id'] = channel.id
|
|
|
|
if location is not MISSING:
|
|
raise TypeError('location cannot be set when entity_type is voice or stage_instance')
|
|
else:
|
|
if channel is not MISSING:
|
|
raise TypeError('channel cannot be set when entity_type is external')
|
|
|
|
if location is MISSING or location is None:
|
|
raise TypeError('location must be set when entity_type is external')
|
|
|
|
metadata['location'] = location
|
|
|
|
if not end_time:
|
|
raise TypeError('end_time must be set when entity_type is external')
|
|
|
|
if end_time:
|
|
if end_time.tzinfo is None:
|
|
raise ValueError(
|
|
'end_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.'
|
|
)
|
|
payload['scheduled_end_time'] = end_time.isoformat()
|
|
|
|
if metadata:
|
|
payload['entity_metadata'] = metadata
|
|
|
|
data = await self._state.http.create_guild_scheduled_event(self.id, **payload, reason=reason)
|
|
return ScheduledEvent(state=self._state, data=data)
|
|
|
|
async def top_emojis(self) -> List[Union[Emoji, PartialEmoji]]:
|
|
"""|coro|
|
|
|
|
Retrieves the most used custom emojis in the guild. Emojis are returned in order of descending usage.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Retrieving the top emojis failed.
|
|
|
|
Returns
|
|
--------
|
|
List[Union[:class:`Emoji`, :class:`PartialEmoji`]]
|
|
The most used emojis. Falls back to a bare :class:`PartialEmoji` if the emoji is not found in cache.
|
|
"""
|
|
state = self._state
|
|
data = await state.http.get_top_emojis(self.id)
|
|
return [
|
|
self._state.get_emoji(int(e['emoji_id'])) or PartialEmoji.with_state(state, name='', id=int(e['emoji_id']))
|
|
for e in data['items']
|
|
]
|
|
|
|
async def fetch_emojis(self) -> List[Emoji]:
|
|
r"""|coro|
|
|
|
|
Retrieves all custom :class:`Emoji`\s from the guild.
|
|
|
|
.. note::
|
|
|
|
This method is an API call. For general usage, consider :attr:`emojis` instead.
|
|
|
|
Raises
|
|
---------
|
|
HTTPException
|
|
An error occurred fetching the emojis.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`Emoji`]
|
|
The retrieved emojis.
|
|
"""
|
|
data = await self._state.http.get_all_custom_emojis(self.id)
|
|
return [Emoji(guild=self, state=self._state, data=d) for d in data]
|
|
|
|
async def fetch_emoji(self, emoji_id: int, /) -> Emoji:
|
|
"""|coro|
|
|
|
|
Retrieves a custom :class:`Emoji` from the guild.
|
|
|
|
.. note::
|
|
|
|
This method is an API call.
|
|
For general usage, consider iterating over :attr:`emojis` instead.
|
|
|
|
.. versionchanged:: 2.0
|
|
|
|
``emoji_id`` parameter is now positional-only.
|
|
|
|
Parameters
|
|
-------------
|
|
emoji_id: :class:`int`
|
|
The emoji's ID.
|
|
|
|
Raises
|
|
---------
|
|
NotFound
|
|
The emoji requested could not be found.
|
|
HTTPException
|
|
An error occurred fetching the emoji.
|
|
|
|
Returns
|
|
--------
|
|
:class:`Emoji`
|
|
The retrieved emoji.
|
|
"""
|
|
data = await self._state.http.get_custom_emoji(self.id, emoji_id)
|
|
return Emoji(guild=self, state=self._state, data=data)
|
|
|
|
async def create_custom_emoji(
|
|
self,
|
|
*,
|
|
name: str,
|
|
image: bytes,
|
|
roles: Collection[Role] = MISSING,
|
|
reason: Optional[str] = None,
|
|
) -> Emoji:
|
|
r"""|coro|
|
|
|
|
Creates a custom :class:`Emoji` for the guild.
|
|
|
|
There is currently a limit of 50 static and animated emojis respectively per guild,
|
|
unless the guild has the ``MORE_EMOJI`` feature which extends the limit to 200.
|
|
|
|
You must have :attr:`~Permissions.manage_emojis` to do this.
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The emoji name. Must be at least 2 characters.
|
|
image: :class:`bytes`
|
|
The :term:`py:bytes-like object` representing the image data to use.
|
|
Only JPG, PNG and GIF images are supported.
|
|
roles: List[:class:`Role`]
|
|
A :class:`list` of :class:`Role`\s that can use this emoji. Leave empty to make it available to everyone.
|
|
reason: Optional[:class:`str`]
|
|
The reason for creating this emoji. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You are not allowed to create emojis.
|
|
HTTPException
|
|
An error occurred creating an emoji.
|
|
|
|
Returns
|
|
--------
|
|
:class:`Emoji`
|
|
The created emoji.
|
|
"""
|
|
img = utils._bytes_to_base64_data(image)
|
|
if roles:
|
|
role_ids: SnowflakeList = [role.id for role in roles]
|
|
else:
|
|
role_ids = []
|
|
|
|
data = await self._state.http.create_custom_emoji(self.id, name, img, roles=role_ids, reason=reason)
|
|
return self._state.store_emoji(self, data)
|
|
|
|
async def delete_emoji(self, emoji: Snowflake, /, *, reason: Optional[str] = None) -> None:
|
|
"""|coro|
|
|
|
|
Deletes the custom :class:`Emoji` from the guild.
|
|
|
|
You must have :attr:`~Permissions.manage_emojis` to do this.
|
|
|
|
.. versionchanged:: 2.0
|
|
|
|
``emoji`` parameter is now positional-only.
|
|
|
|
Parameters
|
|
-----------
|
|
emoji: :class:`abc.Snowflake`
|
|
The emoji you are deleting.
|
|
reason: Optional[:class:`str`]
|
|
The reason for deleting this emoji. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You are not allowed to delete emojis.
|
|
HTTPException
|
|
An error occurred deleting the emoji.
|
|
"""
|
|
await self._state.http.delete_custom_emoji(self.id, emoji.id, reason=reason)
|
|
|
|
async def fetch_roles(self) -> List[Role]:
|
|
"""|coro|
|
|
|
|
Retrieves all :class:`Role` that the guild has.
|
|
|
|
.. note::
|
|
|
|
This method is an API call. For general usage, consider :attr:`roles` instead.
|
|
|
|
.. versionadded:: 1.3
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Retrieving the roles failed.
|
|
|
|
Returns
|
|
-------
|
|
List[:class:`Role`]
|
|
All roles in the guild.
|
|
"""
|
|
data = await self._state.http.get_roles(self.id)
|
|
return [Role(guild=self, state=self._state, data=d) for d in data]
|
|
|
|
@overload
|
|
async def create_role(
|
|
self,
|
|
*,
|
|
reason: Optional[str] = ...,
|
|
name: str = ...,
|
|
permissions: Permissions = ...,
|
|
colour: Union[Colour, int] = ...,
|
|
hoist: bool = ...,
|
|
display_icon: Union[bytes, str] = MISSING,
|
|
mentionable: bool = ...,
|
|
icon: Optional[bytes] = ...,
|
|
emoji: Optional[PartialEmoji] = ...,
|
|
) -> Role:
|
|
...
|
|
|
|
@overload
|
|
async def create_role(
|
|
self,
|
|
*,
|
|
reason: Optional[str] = ...,
|
|
name: str = ...,
|
|
permissions: Permissions = ...,
|
|
color: Union[Colour, int] = ...,
|
|
hoist: bool = ...,
|
|
display_icon: Union[bytes, str] = MISSING,
|
|
mentionable: bool = ...,
|
|
) -> Role:
|
|
...
|
|
|
|
async def create_role(
|
|
self,
|
|
*,
|
|
name: str = MISSING,
|
|
permissions: Permissions = MISSING,
|
|
color: Union[Colour, int] = MISSING,
|
|
colour: Union[Colour, int] = MISSING,
|
|
hoist: bool = MISSING,
|
|
display_icon: Union[bytes, str] = MISSING,
|
|
mentionable: bool = MISSING,
|
|
icon: Optional[bytes] = MISSING,
|
|
emoji: Optional[PartialEmoji] = MISSING,
|
|
reason: Optional[str] = None,
|
|
) -> Role:
|
|
"""|coro|
|
|
|
|
Creates a :class:`Role` for the guild.
|
|
|
|
All fields are optional.
|
|
|
|
You must have :attr:`~Permissions.manage_roles` to do this.
|
|
|
|
.. versionchanged:: 1.6
|
|
Can now pass ``int`` to ``colour`` keyword-only parameter.
|
|
|
|
.. versionadded:: 2.0
|
|
The ``display_icon`` keyword-only parameter was added.
|
|
|
|
.. versionchanged:: 2.0
|
|
This function will now raise :exc:`TypeError` instead of
|
|
``InvalidArgument``.
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The role name. Defaults to 'new role'.
|
|
permissions: :class:`Permissions`
|
|
The permissions to have. Defaults to no permissions.
|
|
colour: Union[:class:`Colour`, :class:`int`]
|
|
The colour for the role. Defaults to :meth:`Colour.default`.
|
|
This is aliased to ``color`` as well.
|
|
hoist: :class:`bool`
|
|
Indicates if the role should be shown separately in the member list.
|
|
Defaults to ``False``.
|
|
display_icon: Union[:class:`bytes`, :class:`str`]
|
|
A :term:`py:bytes-like object` representing the icon
|
|
or :class:`str` representing unicode emoji that should be used as a role icon.
|
|
Only PNG/JPEG is supported.
|
|
This is only available to guilds that contain ``ROLE_ICONS`` in :attr:`features`.
|
|
mentionable: :class:`bool`
|
|
Indicates if the role should be mentionable by others.
|
|
Defaults to ``False``.
|
|
icon: Optional[:class:`bytes`]
|
|
A :term:`py:bytes-like object` representing the icon. Only PNG/JPEG is supported.
|
|
Could be ``None`` to denote removal of the icon.
|
|
emoji: Optional[:class:`PartialEmoji`]
|
|
An emoji to show next to the role. Only unicode emojis are supported.
|
|
reason: Optional[:class:`str`]
|
|
The reason for creating this role. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to create the role.
|
|
HTTPException
|
|
Creating the role failed.
|
|
TypeError
|
|
An invalid keyword argument was given.
|
|
ValueError
|
|
unicode_emoji was the wrong type.
|
|
|
|
Returns
|
|
--------
|
|
:class:`Role`
|
|
The newly created role.
|
|
"""
|
|
fields: Dict[str, Any] = {}
|
|
if permissions is not MISSING:
|
|
fields['permissions'] = str(permissions.value)
|
|
else:
|
|
fields['permissions'] = '0'
|
|
|
|
actual_colour = colour or color or Colour.default()
|
|
if isinstance(actual_colour, int):
|
|
fields['color'] = actual_colour
|
|
else:
|
|
fields['color'] = actual_colour.value
|
|
|
|
if hoist is not MISSING:
|
|
fields['hoist'] = hoist
|
|
|
|
if display_icon is not MISSING:
|
|
if isinstance(display_icon, bytes):
|
|
fields['icon'] = utils._bytes_to_base64_data(display_icon)
|
|
else:
|
|
fields['unicode_emoji'] = display_icon
|
|
|
|
if mentionable is not MISSING:
|
|
fields['mentionable'] = mentionable
|
|
|
|
if name is not MISSING:
|
|
fields['name'] = name
|
|
|
|
if icon is not MISSING:
|
|
if icon is None:
|
|
fields['icon'] = icon
|
|
else:
|
|
fields['icon'] = utils._bytes_to_base64_data(icon)
|
|
|
|
if emoji is not MISSING:
|
|
if emoji is None:
|
|
fields['unicode_emoji'] = None
|
|
elif emoji.id is not None:
|
|
raise ValueError('emoji only supports unicode emojis')
|
|
else:
|
|
fields['unicode_emoji'] = emoji.name
|
|
|
|
data = await self._state.http.create_role(self.id, reason=reason, **fields)
|
|
role = Role(guild=self, data=data, state=self._state)
|
|
|
|
# TODO: add to cache
|
|
return role
|
|
|
|
async def edit_role_positions(self, positions: Mapping[Snowflake, int], *, reason: Optional[str] = None) -> List[Role]:
|
|
"""|coro|
|
|
|
|
Bulk edits a list of :class:`Role` in the guild.
|
|
|
|
You must have :attr:`~Permissions.manage_roles` to do this.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
.. versionchanged:: 2.0
|
|
This function will now raise :exc:`TypeError` instead of
|
|
``InvalidArgument``.
|
|
|
|
Example
|
|
----------
|
|
|
|
.. code-block:: python3
|
|
|
|
positions = {
|
|
bots_role: 1, # penultimate role
|
|
tester_role: 2,
|
|
admin_role: 6
|
|
}
|
|
|
|
await guild.edit_role_positions(positions=positions)
|
|
|
|
Parameters
|
|
-----------
|
|
positions
|
|
A :class:`dict` of :class:`Role` to :class:`int` to change the positions
|
|
of each given role.
|
|
reason: Optional[:class:`str`]
|
|
The reason for editing the role positions. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to move the roles.
|
|
HTTPException
|
|
Moving the roles failed.
|
|
TypeError
|
|
An invalid keyword argument was given.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`Role`]
|
|
A list of all the roles in the guild.
|
|
"""
|
|
if not isinstance(positions, Mapping):
|
|
raise TypeError('positions parameter expects a dict')
|
|
|
|
role_positions = []
|
|
for role, position in positions.items():
|
|
payload: RolePositionUpdatePayload = {'id': role.id, 'position': position}
|
|
|
|
role_positions.append(payload)
|
|
|
|
data = await self._state.http.move_role_position(self.id, role_positions, reason=reason)
|
|
roles: List[Role] = []
|
|
for d in data:
|
|
role = Role(guild=self, data=d, state=self._state)
|
|
roles.append(role)
|
|
self._roles[role.id] = role
|
|
|
|
return roles
|
|
|
|
async def role_member_counts(self) -> Dict[Role, int]:
|
|
"""|coro|
|
|
|
|
Retrieves the number of members in each role the guild has.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to get the member counts.
|
|
HTTPException
|
|
Getting the member counts failed.
|
|
|
|
Returns
|
|
--------
|
|
Dict[:class:`Role`, :class:`int`]
|
|
A mapping of the role to the number of members in that role.
|
|
"""
|
|
data = await self._state.http.get_role_member_counts(self.id)
|
|
ret: Dict[Role, int] = {}
|
|
for k, v in data.items():
|
|
role = self.get_role(int(k))
|
|
if role is not None:
|
|
ret[role] = v
|
|
return ret
|
|
|
|
async def kick(self, user: Snowflake, *, reason: Optional[str] = None) -> None:
|
|
"""|coro|
|
|
|
|
Kicks a user from the guild.
|
|
|
|
The user must meet the :class:`abc.Snowflake` abc.
|
|
|
|
You must have :attr:`~Permissions.kick_members` to do this.
|
|
|
|
Parameters
|
|
-----------
|
|
user: :class:`abc.Snowflake`
|
|
The user to kick from their guild.
|
|
reason: Optional[:class:`str`]
|
|
The reason the user got kicked.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have the proper permissions to kick.
|
|
HTTPException
|
|
Kicking failed.
|
|
"""
|
|
await self._state.http.kick(user.id, self.id, reason=reason)
|
|
|
|
async def ban(
|
|
self,
|
|
user: Snowflake,
|
|
*,
|
|
reason: Optional[str] = None,
|
|
delete_message_days: int = MISSING,
|
|
delete_message_seconds: int = MISSING,
|
|
) -> None:
|
|
"""|coro|
|
|
|
|
Bans a user from the guild.
|
|
|
|
The user must meet the :class:`abc.Snowflake` abc.
|
|
|
|
You must have :attr:`~Permissions.ban_members` to do this.
|
|
|
|
Parameters
|
|
-----------
|
|
user: :class:`abc.Snowflake`
|
|
The user to ban from their guild.
|
|
delete_message_days: :class:`int`
|
|
The number of days worth of messages to delete from the user
|
|
in the guild. The minimum is 0 and the maximum is 7.
|
|
Defaults to 1 day if neither ``delete_message_days`` nor
|
|
``delete_message_seconds`` are passed.
|
|
|
|
.. deprecated:: 2.0
|
|
delete_message_seconds: :class:`int`
|
|
The number of seconds worth of messages to delete from the user
|
|
in the guild. The minimum is 0 and the maximum is 604800 (7 days).
|
|
Defaults to 1 day if neither ``delete_message_days`` nor
|
|
``delete_message_seconds`` are passed.
|
|
|
|
.. versionadded:: 2.0
|
|
reason: Optional[:class:`str`]
|
|
The reason the user got banned.
|
|
|
|
Raises
|
|
-------
|
|
NotFound
|
|
The requested user was not found.
|
|
Forbidden
|
|
You do not have the proper permissions to ban.
|
|
HTTPException
|
|
Banning failed.
|
|
TypeError
|
|
You specified both ``delete_message_days`` and ``delete_message_seconds``.
|
|
"""
|
|
if delete_message_days is not MISSING and delete_message_seconds is not MISSING:
|
|
raise TypeError('Cannot mix delete_message_days and delete_message_seconds keyword arguments.')
|
|
|
|
if delete_message_days is not MISSING:
|
|
msg = 'delete_message_days is deprecated, use delete_message_seconds instead'
|
|
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
delete_message_seconds = delete_message_days * 86400 # one day
|
|
|
|
if delete_message_seconds is MISSING:
|
|
delete_message_seconds = 86400 # one day
|
|
|
|
await self._state.http.ban(user.id, self.id, delete_message_seconds, reason=reason)
|
|
|
|
async def unban(self, user: Snowflake, *, reason: Optional[str] = None) -> None:
|
|
"""|coro|
|
|
|
|
Unbans a user from the guild.
|
|
|
|
The user must meet the :class:`abc.Snowflake` abc.
|
|
|
|
You must have :attr:`~Permissions.ban_members` to do this.
|
|
|
|
Parameters
|
|
-----------
|
|
user: :class:`abc.Snowflake`
|
|
The user to unban.
|
|
reason: Optional[:class:`str`]
|
|
The reason for doing this action. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
NotFound
|
|
The requested unban was not found.
|
|
Forbidden
|
|
You do not have the proper permissions to unban.
|
|
HTTPException
|
|
Unbanning failed.
|
|
"""
|
|
await self._state.http.unban(user.id, self.id, reason=reason)
|
|
|
|
@property
|
|
def vanity_url(self) -> Optional[str]:
|
|
"""Optional[:class:`str`]: The Discord vanity invite URL for this guild, if available.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
if self.vanity_url_code is None:
|
|
return None
|
|
return f'{Invite.BASE}/{self.vanity_url_code}'
|
|
|
|
async def vanity_invite(self) -> Optional[Invite]:
|
|
"""|coro|
|
|
|
|
Returns the guild's special vanity invite.
|
|
|
|
The guild must have ``VANITY_URL`` in :attr:`~Guild.features`.
|
|
|
|
You must have :attr:`~Permissions.manage_guild` to do this.as well.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have the proper permissions to get this.
|
|
HTTPException
|
|
Retrieving the vanity invite failed.
|
|
|
|
Returns
|
|
--------
|
|
Optional[:class:`Invite`]
|
|
The special vanity invite. If ``None`` then the guild does not
|
|
have a vanity invite set.
|
|
"""
|
|
# We start with { code: abc }
|
|
payload = await self._state.http.get_vanity_code(self.id)
|
|
if not payload['code']:
|
|
return
|
|
|
|
# Get the vanity channel & uses
|
|
data = await self._state.http.get_invite(payload['code'])
|
|
|
|
channel = self.get_channel(int(data['channel']['id'])) # type: ignore
|
|
data.update({'temporary': False, 'max_uses': 0, 'max_age': 0, 'uses': payload.get('uses', 0)}) # type: ignore
|
|
return Invite(state=self._state, data=payload, guild=self, channel=channel) # type: ignore # We're faking a payload here
|
|
|
|
async def audit_logs(
|
|
self,
|
|
*,
|
|
limit: Optional[int] = 100,
|
|
before: SnowflakeTime = MISSING,
|
|
after: SnowflakeTime = MISSING,
|
|
oldest_first: bool = MISSING,
|
|
user: Snowflake = MISSING,
|
|
action: AuditLogAction = MISSING,
|
|
) -> AsyncIterator[AuditLogEntry]:
|
|
"""Returns an :term:`asynchronous iterator` that enables receiving the guild's audit logs.
|
|
|
|
You must have :attr:`~Permissions.view_audit_log` to do this.
|
|
|
|
Examples
|
|
----------
|
|
|
|
Getting the first 100 entries: ::
|
|
|
|
async for entry in guild.audit_logs(limit=100):
|
|
print(f'{entry.user} did {entry.action} to {entry.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}')
|
|
|
|
Getting entries made by a specific user: ::
|
|
|
|
entries = [entry async for entry in guild.audit_logs(limit=None, user=guild.me)]
|
|
await channel.send(f'I made {len(entries)} moderation actions.')
|
|
|
|
Parameters
|
|
-----------
|
|
limit: Optional[:class:`int`]
|
|
The number of entries to retrieve. If ``None`` retrieve all entries.
|
|
before: Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]
|
|
Retrieve entries before this date or entry.
|
|
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
|
If the datetime is naive, it is assumed to be local time.
|
|
after: Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]
|
|
Retrieve entries after this date or entry.
|
|
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
|
If the datetime is naive, it is assumed to be local time.
|
|
oldest_first: :class:`bool`
|
|
If set to ``True``, return entries in oldest->newest order. Defaults to ``True`` if
|
|
``after`` is specified, otherwise ``False``.
|
|
user: :class:`abc.Snowflake`
|
|
The moderator to filter entries from.
|
|
action: :class:`AuditLogAction`
|
|
The action to filter with.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You are not allowed to fetch audit logs
|
|
HTTPException
|
|
An error occurred while fetching the audit logs.
|
|
|
|
Yields
|
|
--------
|
|
:class:`AuditLogEntry`
|
|
The audit log entry.
|
|
"""
|
|
|
|
async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]):
|
|
before_id = before.id if before else None
|
|
data = await self._state.http.get_audit_logs(
|
|
self.id, limit=retrieve, user_id=user_id, action_type=action_type, before=before_id
|
|
)
|
|
|
|
entries = data.get('audit_log_entries', [])
|
|
|
|
if data and entries:
|
|
if limit is not None:
|
|
limit -= len(data)
|
|
|
|
before = Object(id=int(entries[-1]['id']))
|
|
|
|
return data, entries, before, limit
|
|
|
|
async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[int]):
|
|
after_id = after.id if after else None
|
|
data = await self._state.http.get_audit_logs(
|
|
self.id, limit=retrieve, user_id=user_id, action_type=action_type, after=after_id
|
|
)
|
|
|
|
entries = data.get('audit_log_entries', [])
|
|
|
|
if data and entries:
|
|
if limit is not None:
|
|
limit -= len(data)
|
|
|
|
after = Object(id=int(entries[-1]['id']))
|
|
|
|
return data, entries, after, limit
|
|
|
|
if user is not MISSING:
|
|
user_id = user.id
|
|
else:
|
|
user_id = None
|
|
|
|
if action is not MISSING:
|
|
action_type: Optional[AuditLogEvent] = action.value
|
|
else:
|
|
action_type = None
|
|
|
|
if isinstance(before, datetime):
|
|
before = Object(id=utils.time_snowflake(before, high=False))
|
|
if isinstance(after, datetime):
|
|
after = Object(id=utils.time_snowflake(after, high=True))
|
|
|
|
if oldest_first:
|
|
if after is MISSING:
|
|
after = OLDEST_OBJECT
|
|
|
|
predicate = None
|
|
|
|
if oldest_first:
|
|
strategy, state = _after_strategy, after
|
|
if before:
|
|
predicate = lambda m: int(m['id']) < before.id
|
|
else:
|
|
strategy, state = _before_strategy, before
|
|
if after:
|
|
predicate = lambda m: int(m['id']) > after.id
|
|
|
|
# Circular import
|
|
from .webhook import Webhook
|
|
|
|
while True:
|
|
retrieve = 100 if limit is None else min(limit, 100)
|
|
if retrieve < 1:
|
|
return
|
|
|
|
data, raw_entries, state, limit = await strategy(retrieve, state, limit)
|
|
|
|
if predicate:
|
|
raw_entries = filter(predicate, raw_entries)
|
|
|
|
users = (User(data=raw_user, state=self._state) for raw_user in data.get('users', []))
|
|
user_map = {user.id: user for user in users}
|
|
|
|
automod_rules = (
|
|
AutoModRule(data=raw_rule, guild=self, state=self._state)
|
|
for raw_rule in data.get('auto_moderation_rules', [])
|
|
)
|
|
automod_rule_map = {rule.id: rule for rule in automod_rules}
|
|
|
|
webhooks = (Webhook.from_state(data=raw_webhook, state=self._state) for raw_webhook in data.get('webhooks', []))
|
|
webhook_map = {webhook.id: webhook for webhook in webhooks}
|
|
|
|
count = 0
|
|
|
|
for count, raw_entry in enumerate(raw_entries, 1):
|
|
# Weird Discord quirk
|
|
if raw_entry['action_type'] is None:
|
|
continue
|
|
|
|
yield AuditLogEntry(
|
|
data=raw_entry,
|
|
users=user_map,
|
|
automod_rules=automod_rule_map,
|
|
webhooks=webhook_map,
|
|
guild=self,
|
|
)
|
|
|
|
if count < 100:
|
|
# There's no data left after this
|
|
break
|
|
|
|
async def ack(self) -> None:
|
|
"""|coro|
|
|
|
|
Marks every message in this guild as read.
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Acking failed.
|
|
"""
|
|
return await self._state.http.ack_guild(self.id)
|
|
|
|
async def widget(self) -> Widget:
|
|
"""|coro|
|
|
|
|
Returns the widget of the guild.
|
|
|
|
.. note::
|
|
|
|
The guild must have the widget enabled to get this information.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
The widget for this guild is disabled.
|
|
HTTPException
|
|
Retrieving the widget failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`Widget`
|
|
The guild's widget.
|
|
"""
|
|
data = await self._state.http.get_widget(self.id)
|
|
|
|
return Widget(state=self._state, data=data)
|
|
|
|
async def edit_widget(
|
|
self,
|
|
*,
|
|
enabled: bool = MISSING,
|
|
channel: Optional[Snowflake] = MISSING,
|
|
reason: Optional[str] = None,
|
|
) -> None:
|
|
"""|coro|
|
|
|
|
Edits the widget of the guild. This can also be done with :attr:`~Guild.edit`.
|
|
|
|
You must have :attr:`~Permissions.manage_guild` to do this.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
enabled: :class:`bool`
|
|
Whether to enable the widget for the guild.
|
|
channel: Optional[:class:`~discord.abc.Snowflake`]
|
|
The new widget channel. ``None`` removes the widget channel.
|
|
reason: Optional[:class:`str`]
|
|
The reason for editing this widget. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permission to edit the widget.
|
|
HTTPException
|
|
Editing the widget failed.
|
|
"""
|
|
payload: EditWidgetSettings = {}
|
|
if channel is not MISSING:
|
|
payload['channel_id'] = None if channel is None else channel.id
|
|
if enabled is not MISSING:
|
|
payload['enabled'] = enabled
|
|
|
|
if payload:
|
|
await self._state.http.edit_widget(self.id, payload=payload, reason=reason)
|
|
|
|
async def welcome_screen(self) -> WelcomeScreen:
|
|
"""|coro|
|
|
|
|
Returns the guild's welcome screen.
|
|
|
|
You must have :attr:`~Permissions.manage_guild` permission to use this.
|
|
|
|
.. note::
|
|
|
|
The guild must have the welcome screen enabled to get this information.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Raises
|
|
-------
|
|
NotFound
|
|
The guild does not have a welcome screen.
|
|
Forbidden
|
|
You do not have :attr:`~Permissions.manage_guild`.
|
|
HTTPException
|
|
Retrieving the welcome screen failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`WelcomeScreen`
|
|
The welcome screen.
|
|
"""
|
|
data = await self._state.http.get_welcome_screen(self.id)
|
|
return WelcomeScreen(data=data, guild=self)
|
|
|
|
async def edit_welcome_screen(
|
|
self,
|
|
*,
|
|
description: str = MISSING,
|
|
welcome_channels: Sequence[WelcomeChannel] = MISSING,
|
|
enabled: bool = MISSING,
|
|
reason: Optional[str] = None,
|
|
):
|
|
"""|coro|
|
|
|
|
Edit the welcome screen.
|
|
|
|
Welcome channels can only accept custom emojis if :attr:`Guild.premium_tier` is level 2 or above.
|
|
|
|
You must have :attr:`~Permissions.manage_guild` in the guild to do this.
|
|
|
|
All parameters are optional.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Usage: ::
|
|
|
|
rules_channel = guild.get_channel(12345678)
|
|
announcements_channel = guild.get_channel(87654321)
|
|
|
|
custom_emoji = utils.get(guild.emojis, name='loudspeaker')
|
|
|
|
await welcome_screen.edit(
|
|
description='This is a very cool community server!',
|
|
welcome_channels=[
|
|
WelcomeChannel(channel=rules_channel, description='Read the rules!', emoji='👨🏫'),
|
|
WelcomeChannel(channel=announcements_channel, description='Watch out for announcements!', emoji=custom_emoji),
|
|
]
|
|
)
|
|
|
|
Parameters
|
|
------------
|
|
enabled: :class:`bool`
|
|
Whether the welcome screen will be shown.
|
|
description: :class:`str`
|
|
The welcome screen's description.
|
|
welcome_channels: Optional[List[:class:`WelcomeChannel`]]
|
|
The welcome channels (in order).
|
|
reason: Optional[:class:`str`]
|
|
The reason for editing the welcome screen. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
NotFound
|
|
The guild does not have a welcome screen.
|
|
HTTPException
|
|
Editing the welcome screen failed failed.
|
|
Forbidden
|
|
You don't have permissions to edit the welcome screen.
|
|
"""
|
|
payload = {}
|
|
|
|
if enabled is not MISSING:
|
|
payload['enabled'] = enabled
|
|
if description is not MISSING:
|
|
payload['description'] = description
|
|
if welcome_channels is not MISSING:
|
|
channels = [channel._to_dict() for channel in welcome_channels] if welcome_channels else []
|
|
payload['welcome_channels'] = channels
|
|
|
|
if payload:
|
|
await self._state.http.edit_welcome_screen(self.id, payload, reason=reason)
|
|
|
|
async def applications(
|
|
self, *, with_team: bool = False, type: Optional[ApplicationType] = None, channel: Optional[Snowflake] = None
|
|
) -> List[PartialApplication]:
|
|
"""|coro|
|
|
|
|
Returns the list of applications that are attached to this guild.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
with_team: :class:`bool`
|
|
Whether to include the team of the application.
|
|
type: :class:`ApplicationType`
|
|
The type of application to restrict the returned applications to.
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Fetching the applications failed.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`PartialApplication`]
|
|
The applications that belong to this guild.
|
|
"""
|
|
data = await self._state.http.get_guild_applications(
|
|
self.id, include_team=with_team, type=int(type) if type else None, channel_id=channel.id if channel else None
|
|
)
|
|
return [PartialApplication(state=self._state, data=app) for app in data]
|
|
|
|
async def premium_subscriptions(self) -> List[PremiumGuildSubscription]:
|
|
"""|coro|
|
|
|
|
Returns the list of premium subscriptions (boosts) for this guild.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permission to get the premium guild subscriptions.
|
|
HTTPException
|
|
Fetching the premium guild subscriptions failed.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`PremiumGuildSubscription`]
|
|
The premium guild subscriptions.
|
|
"""
|
|
data = await self._state.http.get_guild_subscriptions(self.id)
|
|
return [PremiumGuildSubscription(state=self._state, data=sub) for sub in data]
|
|
|
|
async def apply_premium_subscription_slots(self, *subscription_slots: Snowflake) -> List[PremiumGuildSubscription]:
|
|
r"""|coro|
|
|
|
|
Applies premium subscription slots to the guild (boosts the guild).
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
\*subscription_slots: :class:`PremiumGuildSubscriptionSlot`
|
|
The subscription slots to apply.
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Applying the premium subscription slots failed.
|
|
"""
|
|
if not subscription_slots:
|
|
return []
|
|
|
|
state = self._state
|
|
data = await state.http.apply_guild_subscription_slots(self.id, [slot.id for slot in subscription_slots])
|
|
return [PremiumGuildSubscription(state=state, data=sub) for sub in data]
|
|
|
|
async def entitlements(
|
|
self, *, with_sku: bool = True, with_application: bool = True, exclude_deleted: bool = False
|
|
) -> List[Entitlement]:
|
|
"""|coro|
|
|
|
|
Returns the list of entitlements for this guild.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
with_sku: :class:`bool`
|
|
Whether to include the SKU information in the returned entitlements.
|
|
with_application: :class:`bool`
|
|
Whether to include the application in the returned entitlements' SKUs.
|
|
exclude_deleted: :class:`bool`
|
|
Whether to exclude deleted entitlements.
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Retrieving the entitlements failed.
|
|
|
|
Returns
|
|
-------
|
|
List[:class:`Entitlement`]
|
|
The guild's entitlements.
|
|
"""
|
|
state = self._state
|
|
data = await state.http.get_guild_entitlements(
|
|
self.id, with_sku=with_sku, with_application=with_application, exclude_deleted=exclude_deleted
|
|
)
|
|
return [Entitlement(state=state, data=d) for d in data]
|
|
|
|
async def price_tiers(self) -> List[int]:
|
|
"""|coro|
|
|
|
|
Returns the list of price tiers available for use in this guild.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Fetching the price tiers failed.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`int`]
|
|
The available price tiers.
|
|
"""
|
|
return await self._state.http.get_price_tiers(1, self.id)
|
|
|
|
async def fetch_price_tier(self, price_tier: int, /) -> Dict[str, int]:
|
|
"""|coro|
|
|
|
|
Returns a mapping of currency to price for the given price tier.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
price_tier: :class:`int`
|
|
The price tier to retrieve.
|
|
|
|
Raises
|
|
-------
|
|
NotFound
|
|
The price tier does not exist.
|
|
HTTPException
|
|
Fetching the price tier failed.
|
|
|
|
Returns
|
|
-------
|
|
Dict[:class:`str`, :class:`int`]
|
|
The price tier mapping.
|
|
"""
|
|
return await self._state.http.get_price_tier(price_tier)
|
|
|
|
async def chunk(self, channel: Snowflake = MISSING) -> List[Member]:
|
|
"""|coro|
|
|
|
|
Requests all members that belong to this guild.
|
|
This is a websocket operation and can be slow.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. note::
|
|
This can only be used on guilds with less than 1000 members.
|
|
|
|
Parameters
|
|
-----------
|
|
channel: :class:`~abc.Snowflake`
|
|
The channel to request members from.
|
|
|
|
Raises
|
|
-------
|
|
ClientException
|
|
This guild cannot be chunked or chunking failed.
|
|
Guild is no longer available.
|
|
InvalidData
|
|
Did not receive a response from the gateway.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`Member`]
|
|
The members that belong to this guild.
|
|
"""
|
|
if self._offline_members_hidden:
|
|
raise ClientException('This guild cannot be chunked')
|
|
if self._state.is_guild_evicted(self):
|
|
raise ClientException('This guild is no longer available')
|
|
|
|
members = await self._state.chunk_guild(self, channels=[channel] if channel else [])
|
|
return members
|
|
|
|
async def fetch_members(
|
|
self,
|
|
channels: List[Snowflake] = MISSING,
|
|
*,
|
|
cache: bool = True,
|
|
force_scraping: bool = False,
|
|
delay: Union[int, float] = 1,
|
|
) -> List[Member]:
|
|
"""|coro|
|
|
|
|
Retrieves all members that belong to this guild.
|
|
This is a websocket operation and can be slow.
|
|
|
|
This does not enable you to receive events for the guild, and can be called multiple times.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. note::
|
|
If you are the owner, have either of :attr:`~Permissions.administrator`,
|
|
:attr:`~Permissions.kick_members`, :attr:`~Permissions.ban_members`, or :attr:`~Permissions.manage_roles`,
|
|
permissions will be fetched through OPcode 8 (this includes offline members).
|
|
Else, they will be scraped from the member sidebar.
|
|
|
|
Parameters
|
|
-----------
|
|
channels: List[:class:`~abc.Snowflake`]
|
|
A list of up to 5 channels to request members from. More channels make it faster.
|
|
This only applies when scraping from the member sidebar.
|
|
cache: :class:`bool`
|
|
Whether to cache the members as well. The cache will not be kept updated.
|
|
force_scraping: :class:`bool`
|
|
Whether to scrape the member sidebar regardless of permissions.
|
|
delay: Union[:class:`int`, :class:`float`]
|
|
The time in seconds to wait between requests.
|
|
This only applies when scraping from the member sidebar.
|
|
|
|
Raises
|
|
-------
|
|
ClientException
|
|
Fetching members failed.
|
|
Guild is no longer available.
|
|
InvalidData
|
|
Did not receive a response from the gateway.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`Member`]
|
|
The members that belong to this guild (offline members may not be included).
|
|
"""
|
|
if self._state.is_guild_evicted(self):
|
|
raise ClientException('This guild is no longer available')
|
|
|
|
members = await self._state.scrape_guild(
|
|
self, cache=cache, force_scraping=force_scraping, delay=delay, channels=channels
|
|
)
|
|
return members
|
|
|
|
async def query_members(
|
|
self,
|
|
query: Optional[str] = None,
|
|
*,
|
|
limit: int = 5,
|
|
user_ids: Optional[List[int]] = None,
|
|
presences: bool = True,
|
|
cache: bool = True,
|
|
subscribe: bool = False,
|
|
) -> List[Member]:
|
|
"""|coro|
|
|
|
|
Request members of this guild whose username or nickname starts with the given query.
|
|
This is a websocket operation.
|
|
|
|
.. note::
|
|
This is preferrable to using :meth:`fetch_member` as the client uses
|
|
it quite often, and you can also request presence.
|
|
|
|
.. versionadded:: 1.3
|
|
|
|
.. versionchanged:: 2.0
|
|
The function now raises a :exc:`TypeError` instead of ValueError.
|
|
|
|
Parameters
|
|
-----------
|
|
query: Optional[:class:`str`]
|
|
The string that the username or nickname should start with.
|
|
limit: :class:`int`
|
|
The maximum number of members to send back. This must be
|
|
a number between 5 and 100.
|
|
presences: :class:`bool`
|
|
Whether to request for presences to be provided.
|
|
This defaults to ``True``.
|
|
|
|
.. versionadded:: 1.6
|
|
cache: :class:`bool`
|
|
Whether to cache the members internally. This makes operations
|
|
such as :meth:`get_member` work for those that matched.
|
|
The cache will not be kept updated unless ``subscribe`` is set to ``True``.
|
|
user_ids: Optional[List[:class:`int`]]
|
|
List of user IDs to search for. If the user ID is not in the guild then it won't be returned.
|
|
|
|
.. versionadded:: 1.4
|
|
subscribe: :class:`bool`
|
|
Whether to subscribe to the resulting members. This will keep their info and presence updated.
|
|
This requires another request, and defaults to ``False``.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
|
|
Raises
|
|
-------
|
|
asyncio.TimeoutError
|
|
The query timed out waiting for the members.
|
|
TypeError
|
|
Invalid parameters were passed to the function.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`Member`]
|
|
The list of members that have matched the query.
|
|
"""
|
|
if not query and not user_ids:
|
|
raise TypeError('Must pass either query or user_ids')
|
|
|
|
if user_ids and query:
|
|
raise TypeError('Cannot pass both query and user_ids')
|
|
|
|
limit = min(100, limit or 5)
|
|
members = await self._state.query_members(
|
|
self, query=query, limit=limit, user_ids=user_ids, presences=presences, cache=cache # type: ignore # The two types are compatible
|
|
)
|
|
if subscribe:
|
|
ids: List[_Snowflake] = [str(m.id) for m in members]
|
|
await self._state.ws.request_lazy_guild(self.id, members=ids)
|
|
return members
|
|
|
|
async def query_recent_members(
|
|
self,
|
|
query: Optional[str] = None,
|
|
*,
|
|
limit: int = 1000,
|
|
cache: bool = True,
|
|
subscribe: bool = False,
|
|
) -> List[Member]:
|
|
"""|coro|
|
|
|
|
Request the most recent 10,000 joined members of this guild.
|
|
This is a websocket operation.
|
|
|
|
.. note::
|
|
|
|
This operation does not return presences.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Parameters
|
|
-----------
|
|
query: Optional[:class:`str`]
|
|
The string that the username or nickname should start with, if any.
|
|
limit: :class:`int`
|
|
The maximum number of members to send back. This must be
|
|
a number between 1 and 10,000.
|
|
cache: :class:`bool`
|
|
Whether to cache the members internally. This makes operations
|
|
such as :meth:`get_member` work for those that matched.
|
|
The cache will not be kept updated unless ``subscribe`` is set to ``True``.
|
|
subscribe: :class:`bool`
|
|
Whether to subscribe to the resulting members. This will keep their info and presence updated.
|
|
This requires another request, and defaults to ``False``.
|
|
|
|
Raises
|
|
-------
|
|
asyncio.TimeoutError
|
|
The query timed out waiting for the members.
|
|
TypeError
|
|
Invalid parameters were passed to the function.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`Member`]
|
|
The list of members that have matched the query.
|
|
"""
|
|
limit = min(10000, limit or 1)
|
|
members = await self._state.search_recent_members(self, query or '', limit, cache)
|
|
if subscribe:
|
|
ids: List[_Snowflake] = [str(m.id) for m in members]
|
|
for i in range(0, len(ids), 750):
|
|
subs = ids[i : i + 750]
|
|
await self._state.ws.request_lazy_guild(self.id, members=subs)
|
|
return members
|
|
|
|
async def change_voice_state(
|
|
self,
|
|
*,
|
|
channel: Optional[Snowflake],
|
|
self_mute: bool = False,
|
|
self_deaf: bool = False,
|
|
self_video: bool = False,
|
|
preferred_region: Optional[str] = MISSING,
|
|
) -> None:
|
|
"""|coro|
|
|
|
|
Changes client's voice state in the guild.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
Parameters
|
|
-----------
|
|
channel: Optional[:class:`abc.Snowflake`]
|
|
Channel the client wants to join. Use ``None`` to disconnect.
|
|
self_mute: :class:`bool`
|
|
Indicates if the client should be self-muted.
|
|
self_deaf: :class:`bool`
|
|
Indicates if the client should be self-deafened.
|
|
self_video: :class:`bool`
|
|
Indicates if the client is using video. Untested & unconfirmed
|
|
(do not use).
|
|
preferred_region: Optional[:class:`str`]
|
|
The preferred region to connect to.
|
|
|
|
.. versionchanged:: 2.0
|
|
The type of this parameter has changed to :class:`str`.
|
|
"""
|
|
state = self._state
|
|
ws = state.ws
|
|
channel_id = channel.id if channel else None
|
|
|
|
if preferred_region is None or channel_id is None:
|
|
region = None
|
|
else:
|
|
region = str(preferred_region) if preferred_region else state.preferred_rtc_region
|
|
|
|
await ws.voice_state(self.id, channel_id, self_mute, self_deaf, self_video, preferred_region=region)
|
|
|
|
async def request(self, **kwargs): # Purposefully left undocumented...
|
|
"""|coro|
|
|
|
|
Request a guild.
|
|
This is required to receive most events for large guilds.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. note::
|
|
This is done automatically by default, so you do not need
|
|
to perform this operation unless you passed ``request_guilds=False``
|
|
to your :class:`Client`.
|
|
"""
|
|
await self._state.request_guild(self.id, **kwargs)
|
|
|
|
async def automod_rules(self) -> List[AutoModRule]:
|
|
"""|coro|
|
|
|
|
Fetches all automod rules from the guild.
|
|
|
|
You must have :attr:`Permissions.manage_guild` to do this.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permission to view the automod rule.
|
|
NotFound
|
|
There are no automod rules within this guild.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`AutoModRule`]
|
|
The automod rules that were fetched.
|
|
"""
|
|
data = await self._state.http.get_auto_moderation_rules(self.id)
|
|
return [AutoModRule(data=d, guild=self, state=self._state) for d in data]
|
|
|
|
async def fetch_automod_rule(self, automod_rule_id: int, /) -> AutoModRule:
|
|
"""|coro|
|
|
|
|
Fetches an active automod rule from the guild.
|
|
|
|
You must have :attr:`Permissions.manage_guild` to do this.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
automod_rule_id: :class:`int`
|
|
The ID of the automod rule to fetch.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permission to view the automod rule.
|
|
|
|
Returns
|
|
--------
|
|
:class:`AutoModRule`
|
|
The automod rule that was fetched.
|
|
"""
|
|
data = await self._state.http.get_auto_moderation_rule(self.id, automod_rule_id)
|
|
return AutoModRule(data=data, guild=self, state=self._state)
|
|
|
|
async def create_automod_rule(
|
|
self,
|
|
*,
|
|
name: str,
|
|
event_type: AutoModRuleEventType,
|
|
trigger: AutoModTrigger,
|
|
actions: List[AutoModRuleAction],
|
|
enabled: bool = False,
|
|
exempt_roles: Sequence[Snowflake] = MISSING,
|
|
exempt_channels: Sequence[Snowflake] = MISSING,
|
|
reason: str = MISSING,
|
|
) -> AutoModRule:
|
|
"""|coro|
|
|
|
|
Create an automod rule.
|
|
|
|
You must have :attr:`Permissions.manage_guild` to do this.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Parameters
|
|
-----------
|
|
name: :class:`str`
|
|
The name of the automod rule.
|
|
event_type: :class:`AutoModRuleEventType`
|
|
The type of event that the automod rule will trigger on.
|
|
trigger: :class:`AutoModTrigger`
|
|
The trigger that will trigger the automod rule.
|
|
actions: List[:class:`AutoModRuleAction`]
|
|
The actions that will be taken when the automod rule is triggered.
|
|
enabled: :class:`bool`
|
|
Whether the automod rule is enabled.
|
|
Defaults to ``False``.
|
|
exempt_roles: Sequence[:class:`abc.Snowflake`]
|
|
A list of roles that will be exempt from the automod rule.
|
|
exempt_channels: Sequence[:class:`abc.Snowflake`]
|
|
A list of channels that will be exempt from the automod rule.
|
|
reason: :class:`str`
|
|
The reason for creating this automod rule. Shows up on the audit log.
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to create an automod rule.
|
|
HTTPException
|
|
Creating the automod rule failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`AutoModRule`
|
|
The automod rule that was created.
|
|
"""
|
|
data = await self._state.http.create_auto_moderation_rule(
|
|
self.id,
|
|
name=name,
|
|
event_type=event_type.value,
|
|
trigger_type=trigger.type.value,
|
|
trigger_metadata=trigger.to_metadata_dict() or None,
|
|
actions=[a.to_dict() for a in actions],
|
|
enabled=enabled,
|
|
exempt_roles=[str(r.id) for r in exempt_roles] if exempt_roles else None,
|
|
exempt_channel=[str(c.id) for c in exempt_channels] if exempt_channels else None,
|
|
reason=reason,
|
|
)
|
|
|
|
return AutoModRule(data=data, guild=self, state=self._state)
|
|
|
|
async def admin_community_eligibility(self) -> bool:
|
|
"""|coro|
|
|
|
|
Checks if the user is eligible to join the Discord Admin Community through the guild.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to join through the guild.
|
|
HTTPException
|
|
Checking eligibility failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`bool`
|
|
Whether the user is eligible to join the Discord Admin Community through the guild.
|
|
"""
|
|
data = await self._state.http.get_admin_server_eligibility(self.id)
|
|
return data['eligible_for_admin_server']
|
|
|
|
async def join_admin_community(self) -> Guild:
|
|
"""|coro|
|
|
|
|
Joins the Discord Admin Community through the guild.
|
|
You should check your eligibility with :meth:`admin_community_eligibility` before calling this.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to join through the guild.
|
|
HTTPException
|
|
Joining failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`Guild`
|
|
The Discord Admin Community guild.
|
|
"""
|
|
data = await self._state.http.join_admin_server(self.id)
|
|
return Guild(state=self._state, data=data)
|
|
|
|
async def migrate_command_scope(self) -> List[int]:
|
|
"""|coro|
|
|
|
|
Migrates the guild's bot integrations to the applications.commands scope.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
-------
|
|
Forbidden
|
|
You do not have permissions to migrate the guild's bots.
|
|
HTTPException
|
|
Migrating failed.
|
|
|
|
Returns
|
|
--------
|
|
List[:class:`int`]
|
|
The integration IDs that have newly-added application commands.
|
|
"""
|
|
data = await self._state.http.migrate_command_scope(self.id)
|
|
return list(map(int, data['integration_ids_with_app_commands']))
|
|
|
|
async def directory_broadcast_eligibility(self) -> bool:
|
|
"""|coro|
|
|
|
|
Checks if scheduled events can be broadcasted to the directories the guild is in.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Checking eligibility failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`bool`
|
|
Whether the guild is eligible to broadcast scheduled events to directories.
|
|
"""
|
|
data = await self._state.http.get_directory_broadcast_info(self.id, 1)
|
|
return data['can_broadcast']
|
|
|