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 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 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)