diff --git a/disco/api/client.py b/disco/api/client.py index 45367ff..38ef4fc 100644 --- a/disco/api/client.py +++ b/disco/api/client.py @@ -186,8 +186,8 @@ class APIClient(LoggingClass): return Message.create(self.client, r.json()) - def channels_messages_modify(self, channel, message, content=None, embed=None, sanitize=False): - payload = {} + def channels_messages_modify(self, channel, message, content=None, embed=None, flags=None, sanitize=False): + payload = optional(flags=flags) if content is not None: if sanitize: diff --git a/disco/types/base.py b/disco/types/base.py index ea51246..b657e77 100644 --- a/disco/types/base.py +++ b/disco/types/base.py @@ -421,3 +421,77 @@ class Model(six.with_metaclass(ModelMeta, Chainable)): class SlottedModel(Model): __slots__ = ['client'] + + +class BitsetMap(object): + @classmethod + def keys(cls): + for k, v in six.iteritems(cls.__dict__): + if k.isupper(): + yield k + + +class BitsetValue(object): + __slots__ = ['value', 'map'] + + def __init__(self, value=0): + if isinstance(value, self.__class__): + value = value.value + + self.value = value + + def check(self, *args): + for arg in args: + if not (self.value & arg) == arg: + return False + return True + + def add(self, other): + if isinstance(other, self.__class__): + self.value |= other.value + elif isinstance(other, int): + self.value |= other + else: + raise TypeError('Cannot BitsetValue.add from type {}'.format(type(other))) + return self + + def sub(self, other): + if isinstance(other, self.__class__): + self.value &= ~other.value + elif isinstance(other, int): + self.value &= ~other + else: + raise TypeError('Cannot BitsetValue.sub from type {}'.format(type(other))) + return self + + def __iadd__(self, other): + return self.add(other) + + def __isub__(self, other): + return self.sub(other) + + def __getattribute__(self, name): + try: + perm_value = getattr(super(BitsetValue, self).__getattribute__('map'), name.upper()) + return (self.value & perm_value) == perm_value + except AttributeError: + return super(BitsetValue, self).__getattribute__(name) + + def __setattr__(self, name, value): + try: + perm_value = getattr(self.map, name.upper()) + except AttributeError: + return super(BitsetValue, self).__setattr__(name, value) + + if value: + self.value |= perm_value + else: + self.value &= ~perm_value + + def __int__(self): + return self.value + + def to_dict(self): + return { + k: getattr(self, k) for k in list(self.map.keys()) + } diff --git a/disco/types/message.py b/disco/types/message.py index 1c3d68d..cb9ef92 100644 --- a/disco/types/message.py +++ b/disco/types/message.py @@ -5,11 +5,12 @@ import functools import unicodedata from disco.types.base import ( - SlottedModel, Field, ListField, AutoDictField, snowflake, text, - datetime, enum, cached_property, + BitsetMap, BitsetValue, SlottedModel, Field, ListField, AutoDictField, + snowflake, text, datetime, enum, cached_property, ) from disco.util.paginator import Paginator from disco.util.snowflake import to_snowflake +from disco.types.channel import ChannelType from disco.types.user import User @@ -26,6 +27,7 @@ class MessageType(object): USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9 USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10 USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11 + CHANNEL_FOLLOW_ADD = 12 class MessageActivityType(object): @@ -116,6 +118,12 @@ class MessageApplication(SlottedModel): name = Field(text) +class MessageReference(SlottedModel): + message_id = Field(snowflake) + channel_id = Field(snowflake) + guild_id = Field(snowflake) + + class MessageActivity(SlottedModel): """ The activity of a Rich Presence-related chat embed. @@ -359,6 +367,23 @@ class MessageAttachment(SlottedModel): width = Field(int) +class ChannelMention(SlottedModel): + id = Field(snowflake) + guild_id = Field(snowflake) + type = Field(enum(ChannelType)) + name = Field(text) + + +class MessageFlags(BitsetMap): + CROSSPOSTED = 1 << 0 + IS_CROSSPOST = 1 << 1 + SUPPRESS_EMBEDS = 1 << 2 + + +class MessageFlagValue(BitsetValue): + map = MessageFlags + + class Message(SlottedModel): """ Represents a Message created within a Channel on Discord. @@ -393,6 +418,8 @@ class Message(SlottedModel): IDs for roles mentioned within this message. embeds : list[`MessageEmbed`] Embeds for this message. + mention_channels : list[`ChannelMention`] + The channels mentioned in this message if it is cross-posted. attachments : dict[`MessageAttachment`] Attachments for this message. reactions : list[`MessageReaction`] @@ -401,6 +428,10 @@ class Message(SlottedModel): The activity of a Rich Presence-related chat embed. application : `MessageApplication` The application of a Rich Presence-related chat embed. + message_reference: `MessageReference` + The reference of a cross-posted message. + flags: `MessageFlagValue` + The flags attached to a message. """ id = Field(snowflake) channel_id = Field(snowflake) @@ -417,10 +448,13 @@ class Message(SlottedModel): mentions = AutoDictField(User, 'id') mention_roles = ListField(snowflake) embeds = ListField(MessageEmbed) + mention_channels = ListField(ChannelMention) attachments = AutoDictField(MessageAttachment, 'id') reactions = ListField(MessageReaction) activity = Field(MessageActivity) application = Field(MessageApplication) + message_reference = Field(MessageReference) + flags = Field(MessageFlagValue) def __str__(self): return ''.format(self.id, self.channel_id) @@ -505,6 +539,28 @@ class Message(SlottedModel): """ return self.client.api.channels_messages_delete(self.channel_id, self.id) + def toggle_embeds_suppress(self, state=None): + """ + Toggle this message's embed suppression. + + Args + ---- + `state` + Whether this message's embeds should be suppressed, + will just flip the state if not provided. + """ + flags = int(self.flags or 0) + if state is None: + state = not self.flags or not self.flags.SUPPRESS_EMBEDS + + if state: + flags |= MessageFlags.SUPPRESS_EMBEDS + else: + flags &= ~MessageFlags.SUPPRESS_EMBEDS + + self.edit(flags=flags) + return state + def get_reactors(self, emoji, *args, **kwargs): """ Returns an iterator which paginates the reactors for the given emoji. diff --git a/disco/types/permissions.py b/disco/types/permissions.py index b4f5f7d..233c52b 100644 --- a/disco/types/permissions.py +++ b/disco/types/permissions.py @@ -1,7 +1,7 @@ -import six +from disco.types.base import BitsetMap, BitsetValue -class Permissions(object): +class Permissions(BitsetMap): CREATE_INSTANT_INVITE = 1 << 0 KICK_MEMBERS = 1 << 1 BAN_MEMBERS = 1 << 2 @@ -33,81 +33,15 @@ class Permissions(object): MANAGE_WEBHOOKS = 1 << 29 MANAGE_EMOJIS = 1 << 30 - @classmethod - def keys(cls): - for k, v in six.iteritems(cls.__dict__): - if k.isupper(): - yield k - - -class PermissionValue(object): - __slots__ = ['value'] - - def __init__(self, value=0): - if isinstance(value, PermissionValue): - value = value.value - self.value = value +class PermissionValue(BitsetValue): + map = Permissions def can(self, *perms): # Administrator permission overwrites all others if self.administrator: return True - - for perm in perms: - if not (self.value & perm) == perm: - return False - return True - - def add(self, other): - if isinstance(other, PermissionValue): - self.value |= other.value - elif isinstance(other, int): - self.value |= other - else: - raise TypeError('Cannot PermissionValue.add from type {}'.format(type(other))) - return self - - def sub(self, other): - if isinstance(other, PermissionValue): - self.value &= ~other.value - elif isinstance(other, int): - self.value &= ~other - else: - raise TypeError('Cannot PermissionValue.sub from type {}'.format(type(other))) - return self - - def __iadd__(self, other): - return self.add(other) - - def __isub__(self, other): - return self.sub(other) - - def __getattribute__(self, name): - try: - perm_value = getattr(Permissions, name.upper()) - return (self.value & perm_value) == perm_value - except AttributeError: - return object.__getattribute__(self, name) - - def __setattr__(self, name, value): - try: - perm_value = getattr(Permissions, name.upper()) - except AttributeError: - return super(PermissionValue, self).__setattr__(name, value) - - if value: - self.value |= perm_value - else: - self.value &= ~perm_value - - def __int__(self): - return self.value - - def to_dict(self): - return { - k: getattr(self, k) for k in list(Permissions.keys()) - } + return self.check(*perms) @classmethod def text(cls):