From b5d26190a6da6077937299b4cfc318d64b1ef406 Mon Sep 17 00:00:00 2001
From: Luke <luke@lmbyrne.dev>
Date: Tue, 28 Jan 2020 18:35:34 +0000
Subject: [PATCH]  Message Flags, embed suppressing and cross-posts. (#150)

* Add embed suppression and crosspost support.

* Add new message flags.

* toggle_embeds_suppress -> set_embeds_suppressed.
---
 disco/api/client.py        |  4 +-
 disco/types/base.py        | 74 +++++++++++++++++++++++++++++++++++++
 disco/types/message.py     | 58 ++++++++++++++++++++++++++++-
 disco/types/permissions.py | 76 +++-----------------------------------
 4 files changed, 137 insertions(+), 75 deletions(-)

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..85f20f6 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,25 @@ 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
+    SOURCE_MESSAGE_DELETED = 1 << 3
+    URGENT = 1 << 4
+
+
+class MessageFlagValue(BitsetValue):
+    map = MessageFlags
+
+
 class Message(SlottedModel):
     """
     Represents a Message created within a Channel on Discord.
@@ -393,6 +420,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 +430,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 +450,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 '<Message {} ({})>'.format(self.id, self.channel_id)
@@ -505,6 +541,24 @@ class Message(SlottedModel):
         """
         return self.client.api.channels_messages_delete(self.channel_id, self.id)
 
+    def set_embeds_suppressed(self, state):
+        """
+        Toggle this message's embed suppression.
+
+        Parameters
+        ----------
+        `state`
+            Whether this message's embeds should be suppressed.
+        """
+        flags = int(self.flags or 0)
+
+        if state:
+            flags |= MessageFlags.SUPPRESS_EMBEDS
+        else:
+            flags &= ~MessageFlags.SUPPRESS_EMBEDS
+
+        self.edit(flags=flags)
+
     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):