class MessageEmbed(SlottedModel): """ Message embed object. Attributes ---------- title : str Title of the embed. type : str Type of the embed. description : str Description of the embed. url : str URL of the embed. """ title = Field(text) type = Field(str, default='rich') description = Field(text) url = Field(str) timestamp = Field(lazy_datetime) color = Field(int) footer = Field(MessageEmbedFooter) image = Field(MessageEmbedImage) thumbnail = Field(MessageEmbedThumbnail) video = Field(MessageEmbedVideo) author = Field(MessageEmbedAuthor) fields = Field(listof(MessageEmbedField))
class GuildEmoji(Emoji): """ An emoji object. Attributes ---------- id : snowflake The ID of this emoji. name : str The name of this emoji. require_colons : bool Whether this emoji requires colons to use. managed : bool Whether this emoji is managed by an integration. roles : list(snowflake) Roles this emoji is attached to. """ id = Field(snowflake) guild_id = Field(snowflake) name = Field(text) require_colons = Field(bool) managed = Field(bool) roles = Field(listof(snowflake)) @cached_property def guild(self): return self.client.state.guilds.get(self.guild_id)
class Ready(GatewayEvent): """ Sent after the initial gateway handshake is complete. Contains data required for bootstrapping the client's states. """ version = Field(int, alias='v') session_id = Field(str) user = Field(User) guilds = Field(listof(Guild))
class MessageDeleteBulk(GatewayEvent): """ Sent when multiple messages are deleted from a channel. Attributes ----- channel_id : snowflake The channel the messages are being deleted in. ids : list[snowflake] List of messages being deleted in the channel. """ channel_id = Field(snowflake) ids = Field(listof(snowflake))
class GuildEmojisUpdate(GatewayEvent): """ Sent when a guild's emojis are updated. Attributes ----- guild_id : snowflake The ID of the guild the emojis are being updated in. emojis : list[:class:`disco.types.guild.Emoji`] The new set of emojis for the guild """ guild_id = Field(snowflake) emojis = Field(listof(Emoji))
class Ready(GatewayEvent): """ Sent after the initial gateway handshake is complete. Contains data required for bootstrapping the client's states. Attributes ----- version : int The gateway version. session_id : str The session ID. user : :class:`disco.types.user.User` The user object for the authed account. guilds : list[:class:`disco.types.guild.Guild` All guilds this account is a member of. These are shallow guild objects. private_channels list[:class:`disco.types.channel.Channel`] All private channels (DMs) open for this account. """ version = Field(int, alias='v') session_id = Field(str) user = Field(User) guilds = Field(listof(Guild)) private_channels = Field(listof(Channel))
class PresenceUpdate(GatewayEvent): """ Sent when a user's presence is updated. Attributes ----- presence : :class:`disco.types.user.Presence` The updated presence object. guild_id : snowflake The guild this presence update is for. roles : list[snowflake] List of roles the user from the presence is part of. """ guild_id = Field(snowflake) roles = Field(listof(snowflake))
class GuildMembersChunk(GatewayEvent): """ Sent in response to a member's chunk request. Attributes ----- guild_id : snowflake The ID of the guild this member chunk is for. members : list[:class:`disco.types.guild.GuildMember`] The chunk of members. """ guild_id = Field(snowflake) members = Field(listof(GuildMember)) @property def guild(self): return self.client.state.guilds.get(self.guild_id)
class Emoji(Model): """ An emoji object Attributes ---------- id : snowflake The ID of this emoji. name : str The name of this emoji. require_colons : bool Whether this emoji requires colons to use. managed : bool Whether this emoji is managed by an integration. roles : list(snowflake) Roles this emoji is attached to. """ id = Field(snowflake) name = Field(text) require_colons = Field(bool) managed = Field(bool) roles = Field(listof(snowflake))
class Channel(Model, Permissible): """ Represents a Discord Channel Attributes ---------- id : snowflake The channel ID. guild_id : Optional[snowflake] The guild id this channel is part of. name : str The channels name. topic : str The channels topic. position : int The channels position. bitrate : int The channels bitrate. recipients: list(:class:`disco.types.user.User`) Members of this channel (if this is a DM channel). type : :const:`ChannelType` The type of this channel. overwrites : dict(snowflake, :class:`disco.types.channel.PermissionOverwrite`) Channel permissions overwrites. """ id = Field(snowflake) guild_id = Field(snowflake) name = Field(text) topic = Field(text) last_message_id = Field(snowflake) position = Field(int) bitrate = Field(int) recipients = Field(listof(User)) type = Field(enum(ChannelType)) overwrites = Field(dictof(PermissionOverwrite, key='id'), alias='permission_overwrites') def __init__(self, *args, **kwargs): super(Channel, self).__init__(*args, **kwargs) self.attach(six.itervalues(self.overwrites), { 'channel_id': self.id, 'channel': self }) def get_permissions(self, user): """ Get the permissions a user has in the channel Returns ------- :class:`disco.types.permissions.PermissionValue` Computed permission value for the user. """ if not self.guild_id: return Permissions.ADMINISTRATOR member = self.guild.members.get(user.id) base = self.guild.get_permissions(user) for ow in six.itervalues(self.overwrites): if ow.id != user.id and ow.id not in member.roles: continue base -= ow.deny base += ow.allow return base @property def is_guild(self): """ Whether this channel belongs to a guild """ return self.type in (ChannelType.GUILD_TEXT, ChannelType.GUILD_VOICE) @property def is_dm(self): """ Whether this channel is a DM (does not belong to a guild) """ return self.type in (ChannelType.DM, ChannelType.GROUP_DM) @property def is_voice(self): """ Whether this channel supports voice """ return self.type in (ChannelType.GUILD_VOICE, ChannelType.GROUP_DM) @property def messages(self): """ a default :class:`MessageIterator` for the channel """ return self.messages_iter() def messages_iter(self, **kwargs): """ Creates a new :class:`MessageIterator` for the channel with the given keyword arguments """ return MessageIterator(self.client, self.id, **kwargs) @cached_property def guild(self): """ Guild this channel belongs to (if relevant) """ return self.client.state.guilds.get(self.guild_id) def get_invites(self): """ Returns ------- list(:class:`disco.types.invite.Invite`) All invites for this channel. """ return self.client.api.channels_invites_list(self.id) def get_pins(self): """ Returns ------- list(:class:`disco.types.message.Message`) All pinned messages for this channel. """ return self.client.api.channels_pins_list(self.id) def send_message(self, content, nonce=None, tts=False): """ Send a message in this channel Parameters ---------- content : str The message contents to send. nonce : Optional[snowflake] The nonce to attach to the message. tts : Optional[bool] Whether this is a TTS message. Returns ------- :class:`disco.types.message.Message` The created message. """ return self.client.api.channels_messages_create( self.id, content, nonce, tts) def connect(self, *args, **kwargs): """ Connect to this channel over voice """ assert self.is_voice, 'Channel must support voice to connect' vc = VoiceClient(self) vc.connect(*args, **kwargs) return vc def create_overwrite(self, entity, allow=0, deny=0): from disco.types.guild import Role type = PermissionOverwriteType.ROLE if isinstance( entity, Role) else PermissionOverwriteType.MEMBER ow = PermissionOverwrite(id=entity.id, type=type, allow=allow, deny=deny) ow.channel_id = self.id ow.channel = self return self.update_overwrite(ow) def update_overwrite(self, ow): self.client.api.channels_permissions_modify( self.id, ow.id, ow.allow.value if ow.allow else 0, ow.deny.value if ow.deny else 0, ow.type.name) return ow def delete_overwrite(self, ow): self.client.api.channels_permissions_delete(self.id, ow.id) def delete_message(self, message): """ Deletes a single message from this channel. Args ---- message : snowflake|:class:`disco.types.message.Message` The message to delete. """ self.client.api.channels_messages_delete(self.id, to_snowflake(message)) @one_or_many def delete_messages(self, messages): """ Deletes a set of messages using the correct API route based on the number of messages passed. Args ---- messages : list[snowflake|:class:`disco.types.message.Message`] List of messages (or message ids) to delete. All messages must originate from this channel. """ messages = map(to_snowflake, messages) if not messages: return if len(messages) <= 2: for msg in messages: self.delete_message(msg) else: for chunk in chunks(messages, 100): self.client.api.channels_messages_delete_bulk(self.id, chunk)
class Message(Model): """ Represents a Message created within a Channel on Discord. Attributes ---------- id : snowflake The ID of this message. channel_id : snowflake The channel ID this message was sent in. type : ``MessageType`` Type of the message. author : :class:`disco.types.user.User` The author of this message. content : str The unicode contents of this message. nonce : str The nonce of this message. timestamp : datetime When this message was created. edited_timestamp : Optional[datetime] When this message was last edited. tts : bool Whether this is a TTS (text-to-speech) message. mention_everyone : bool Whether this message has an @everyone which mentions everyone. pinned : bool Whether this message is pinned in the channel. mentions : dict(snowflake, :class:`disco.types.user.User`) All users mentioned within this message. mention_roles : list(snowflake) All roles mentioned within this message. embeds : list(:class:`MessageEmbed`) All embeds for this message. attachments : list(:class:`MessageAttachment`) All attachments for this message. """ id = Field(snowflake) channel_id = Field(snowflake) type = Field(enum(MessageType)) author = Field(User) content = Field(text) nonce = Field(snowflake) timestamp = Field(datetime) edited_timestamp = Field(datetime) tts = Field(bool) mention_everyone = Field(bool) pinned = Field(bool) mentions = Field(dictof(User, key='id')) mention_roles = Field(listof(snowflake)) embeds = Field(listof(MessageEmbed)) attachments = Field(dictof(MessageAttachment, key='id')) def __str__(self): return '<Message {} ({})>'.format(self.id, self.channel_id) @cached_property def guild(self): """ Returns ------- :class:`disco.types.guild.Guild` The guild (if applicable) this message was created in. """ return self.channel.guild @cached_property def member(self): """ Returns ------- :class:`disco.types.guild.GuildMember` The guild member (if applicable) that sent this message. """ return self.channel.guild.get_member(self.author) @cached_property def channel(self): """ Returns ------- :class:`disco.types.channel.Channel` The channel this message was created in. """ return self.client.state.channels.get(self.channel_id) def reply(self, *args, **kwargs): """ Reply to this message (proxys arguments to :func:`disco.types.channel.Channel.send_message`) Returns ------- :class:`Message` The created message object. """ return self.channel.send_message(*args, **kwargs) def edit(self, content): """ Edit this message Args ---- content : str The new edited contents of the message. Returns ------- :class:`Message` The edited message object. """ return self.client.api.channels_messages_modify(self.channel_id, self.id, content) def delete(self): """ Delete this message. Returns ------- :class:`Message` The deleted message object. """ return self.client.api.channels_messages_delete(self.channel_id, self.id) def is_mentioned(self, entity): """ Returns ------- bool Whether the give entity was mentioned. """ id = to_snowflake(entity) return id in self.mentions or id in self.mention_roles @cached_property def without_mentions(self): """ Returns ------- str the message contents with all valid mentions removed. """ return self.replace_mentions( lambda u: '', lambda r: '') def replace_mentions(self, user_replace, role_replace): """ Replaces user and role mentions with the result of a given lambda/function. Args ---- user_replace : function A function taking a single argument, the user object mentioned, and returning a valid string. role_replace : function A function taking a single argument, the role ID mentioned, and returning a valid string. Returns ------- str The message contents with all valid mentions replaced. """ if not self.mentions and not self.mention_roles: return def replace(match): id = match.group(0) if id in self.mention_roles: return role_replace(id) else: return user_replace(self.mentions.get(id)) return re.sub('<@!?([0-9]+)>', replace, self.content)
class Guild(SlottedModel, Permissible): """ A guild object. Attributes ---------- id : snowflake The id of this guild. owner_id : snowflake The id of the owner. afk_channel_id : snowflake The id of the afk channel. embed_channel_id : snowflake The id of the embed channel. name : str Guild's name. icon : str Guild's icon (as PNG binary data). splash : str Guild's splash image (as PNG binary data). region : str Voice region. afk_timeout : int Delay after which users are automatically moved to the afk channel. embed_enabled : bool Whether the guild's embed is enabled. verification_level : int The verification level used by the guild. mfa_level : int The MFA level used by the guild. features : list(str) Extra features enabled for this guild. members : dict(snowflake, :class:`GuildMember`) All of the guild's members. channels : dict(snowflake, :class:`disco.types.channel.Channel`) All of the guild's channels. roles : dict(snowflake, :class:`Role`) All of the guild's roles. emojis : dict(snowflake, :class:`GuildEmoji`) All of the guild's emojis. voice_states : dict(str, :class:`disco.types.voice.VoiceState`) All of the guild's voice states. """ id = Field(snowflake) owner_id = Field(snowflake) afk_channel_id = Field(snowflake) embed_channel_id = Field(snowflake) name = Field(text) icon = Field(binary) splash = Field(binary) region = Field(str) afk_timeout = Field(int) embed_enabled = Field(bool) verification_level = Field(enum(VerificationLevel)) mfa_level = Field(int) features = Field(listof(str)) members = Field(dictof(GuildMember, key='id')) channels = Field(dictof(Channel, key='id')) roles = Field(dictof(Role, key='id')) emojis = Field(dictof(GuildEmoji, key='id')) voice_states = Field(dictof(VoiceState, key='session_id')) member_count = Field(int) presences = Field(listof(Presence)) synced = Field(bool, default=False) def __init__(self, *args, **kwargs): super(Guild, self).__init__(*args, **kwargs) self.attach(six.itervalues(self.channels), {'guild_id': self.id}) self.attach(six.itervalues(self.members), {'guild_id': self.id}) self.attach(six.itervalues(self.roles), {'guild_id': self.id}) self.attach(six.itervalues(self.emojis), {'guild_id': self.id}) self.attach(six.itervalues(self.voice_states), {'guild_id': self.id}) def get_permissions(self, user): """ Get the permissions a user has in this guild. Returns ------- :class:`disco.types.permissions.PermissionValue` Computed permission value for the user. """ if self.owner_id == user.id: return PermissionValue(Permissions.ADMINISTRATOR) member = self.get_member(user) value = PermissionValue(self.roles.get(self.id).permissions) for role in map(self.roles.get, member.roles): value += role.permissions return value def get_voice_state(self, user): """ Attempt to get a voice state for a given user (who should be a member of this guild). Returns ------- :class:`disco.types.voice.VoiceState` The voice state for the user in this guild. """ user = to_snowflake(user) for state in six.itervalues(self.voice_states): if state.user_id == user: return state def get_member(self, user): """ Attempt to get a member from a given user. Returns ------- :class:`GuildMember` The guild member object for the given user. """ user = to_snowflake(user) if user not in self.members: try: self.members[user] = self.client.api.guilds_members_get(self.id, user) except APIException: return return self.members.get(user) def create_role(self): """ Create a new role. Returns ------- :class:`Role` The newly created role. """ return self.client.api.guilds_roles_create(self.id) def delete_role(self, role): """ Delete a role. """ self.client.api.guilds_roles_delete(self.id, to_snowflake(role)) def update_role(self, role): return self.client.api.guilds_roles_modify(self.id, role.id, **{ 'name': role.name, 'permissions': role.permissions.value, 'position': role.position, 'color': role.color, 'hoist': role.hoist, 'mentionable': role.mentionable, }) def sync(self): if self.synced: return self.synced = True self.client.gw.send(OPCode.REQUEST_GUILD_MEMBERS, { 'guild_id': self.id, 'query': '', 'limit': 0, }) def get_bans(self): return self.client.api.guilds_bans_list(self.id) def delete_ban(self, user): self.client.api.guilds_bans_delete(self.id, to_snowflake(user)) def create_ban(self, user, delete_message_days=0): self.client.api.guilds_bans_create(self.id, to_snowflake(user), delete_message_days)
class GuildMember(SlottedModel): """ A GuildMember object. Attributes ---------- user : :class:`disco.types.user.User` The user object of this member. guild_id : snowflake The guild this member is part of. nick : str The nickname of the member. mute : bool Whether this member is server voice-muted. deaf : bool Whether this member is server voice-deafened. joined_at : datetime When this user joined the guild. roles : list(snowflake) Roles this member is part of. """ user = Field(User) guild_id = Field(snowflake) nick = Field(text) mute = Field(bool) deaf = Field(bool) joined_at = Field(str) roles = Field(listof(snowflake)) def __str__(self): return self.user.__str__() def get_voice_state(self): """ Returns ------- Optional[:class:`disco.types.voice.VoiceState`] Returns the voice state for the member if they are currently connected to the guild's voice server. """ return self.guild.get_voice_state(self) def kick(self): """ Kicks the member from the guild. """ self.client.api.guilds_members_kick(self.guild.id, self.user.id) def ban(self, delete_message_days=0): """ Bans the member from the guild. Args ---- delete_message_days : int The number of days to retroactively delete messages for. """ self.guild.create_ban(self, delete_message_days) def set_nickname(self, nickname=None): """ Sets the member's nickname (or clears it if None). Args ---- nickname : Optional[str] The nickname (or none to reset) to set. """ self.client.api.guilds_members_modify(self.guild.id, self.user.id, nick=nickname or '') def add_role(self, role): roles = self.roles + [role.id] self.client.api.guilds_members_modify(self.guild.id, self.user.id, roles=roles) @cached_property def owner(self): return self.guild.owner_id == self.id @cached_property def mention(self): if self.nick: return '<@!{}>'.format(self.id) return self.user.mention @property def id(self): """ Alias to the guild members user id. """ return self.user.id @cached_property def guild(self): return self.client.state.guilds.get(self.guild_id)
class PresenceUpdate(GatewayEvent): """ Sent when a user's presence is updated. """ guild_id = Field(snowflake) roles = Field(listof(snowflake))
class MessageDeleteBulk(GatewayEvent): """ Sent when multiple messages are deleted from a channel. """ channel_id = Field(snowflake) ids = Field(listof(snowflake))
class GuildMembersChunk(GatewayEvent): """ Sent in response to a member's chunk request. """ guild_id = Field(snowflake) members = Field(listof(GuildMember))