packages feed

discord-haskell 1.8.8 → 1.8.9

raw patch · 11 files changed

+95/−58 lines, 11 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Discord.Internal.Gateway.EventLoop: setSequence :: IORef Integer -> Integer -> IO ()
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Eq Discord.Internal.Rest.Channel.AllowedMentions
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Eq Discord.Internal.Rest.Channel.ChannelInviteOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Eq Discord.Internal.Rest.Channel.ChannelPermissionsOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Eq Discord.Internal.Rest.Channel.ChannelPermissionsOptsType
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Eq Discord.Internal.Rest.Channel.GroupDMAddRecipientOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Eq Discord.Internal.Rest.Channel.MessageDetailedOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Eq Discord.Internal.Rest.Channel.MessageTiming
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Eq Discord.Internal.Rest.Channel.ModifyChannelOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Eq Discord.Internal.Rest.Channel.ReactionTiming
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Ord Discord.Internal.Rest.Channel.AllowedMentions
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Ord Discord.Internal.Rest.Channel.ChannelInviteOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Ord Discord.Internal.Rest.Channel.ChannelPermissionsOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Ord Discord.Internal.Rest.Channel.ChannelPermissionsOptsType
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Ord Discord.Internal.Rest.Channel.GroupDMAddRecipientOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Ord Discord.Internal.Rest.Channel.MessageDetailedOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Ord Discord.Internal.Rest.Channel.MessageTiming
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Ord Discord.Internal.Rest.Channel.ModifyChannelOpts
+ Discord.Internal.Rest.Channel: instance GHC.Classes.Ord Discord.Internal.Rest.Channel.ReactionTiming
+ Discord.Internal.Rest.Channel: instance GHC.Show.Show Discord.Internal.Rest.Channel.AllowedMentions
+ Discord.Internal.Rest.Channel: instance GHC.Show.Show Discord.Internal.Rest.Channel.ChannelInviteOpts
+ Discord.Internal.Rest.Channel: instance GHC.Show.Show Discord.Internal.Rest.Channel.ChannelPermissionsOpts
+ Discord.Internal.Rest.Channel: instance GHC.Show.Show Discord.Internal.Rest.Channel.ChannelPermissionsOptsType
+ Discord.Internal.Rest.Channel: instance GHC.Show.Show Discord.Internal.Rest.Channel.GroupDMAddRecipientOpts
+ Discord.Internal.Rest.Channel: instance GHC.Show.Show Discord.Internal.Rest.Channel.MessageDetailedOpts
+ Discord.Internal.Rest.Channel: instance GHC.Show.Show Discord.Internal.Rest.Channel.MessageTiming
+ Discord.Internal.Rest.Channel: instance GHC.Show.Show Discord.Internal.Rest.Channel.ModifyChannelOpts
+ Discord.Internal.Rest.Channel: instance GHC.Show.Show Discord.Internal.Rest.Channel.ReactionTiming
+ Discord.Internal.Rest.Emoji: instance GHC.Classes.Eq Discord.Internal.Rest.Emoji.EmojiImageParsed
+ Discord.Internal.Rest.Emoji: instance GHC.Classes.Eq Discord.Internal.Rest.Emoji.ModifyGuildEmojiOpts
+ Discord.Internal.Rest.Emoji: instance GHC.Classes.Ord Discord.Internal.Rest.Emoji.EmojiImageParsed
+ Discord.Internal.Rest.Emoji: instance GHC.Classes.Ord Discord.Internal.Rest.Emoji.ModifyGuildEmojiOpts
+ Discord.Internal.Rest.Emoji: instance GHC.Show.Show Discord.Internal.Rest.Emoji.EmojiImageParsed
+ Discord.Internal.Rest.Emoji: instance GHC.Show.Show Discord.Internal.Rest.Emoji.ModifyGuildEmojiOpts
+ Discord.Internal.Rest.Guild: instance GHC.Classes.Eq Discord.Internal.Rest.Guild.GuildMembersTiming
+ Discord.Internal.Rest.Guild: instance GHC.Classes.Ord Discord.Internal.Rest.Guild.GuildMembersTiming
+ Discord.Internal.Rest.Guild: instance GHC.Show.Show Discord.Internal.Rest.Guild.GuildMembersTiming
+ Discord.Internal.Rest.User: instance GHC.Classes.Eq Discord.Internal.Rest.User.CurrentUserAvatar
+ Discord.Internal.Rest.User: instance GHC.Classes.Ord Discord.Internal.Rest.User.CurrentUserAvatar
+ Discord.Internal.Rest.User: instance GHC.Show.Show Discord.Internal.Rest.User.CurrentUserAvatar
+ Discord.Internal.Rest.Webhook: instance GHC.Classes.Eq Discord.Internal.Rest.Webhook.CreateWebhookOpts
+ Discord.Internal.Rest.Webhook: instance GHC.Classes.Eq Discord.Internal.Rest.Webhook.ModifyWebhookOpts
+ Discord.Internal.Rest.Webhook: instance GHC.Classes.Ord Discord.Internal.Rest.Webhook.CreateWebhookOpts
+ Discord.Internal.Rest.Webhook: instance GHC.Classes.Ord Discord.Internal.Rest.Webhook.ModifyWebhookOpts
+ Discord.Internal.Rest.Webhook: instance GHC.Show.Show Discord.Internal.Rest.Webhook.CreateWebhookOpts
+ Discord.Internal.Rest.Webhook: instance GHC.Show.Show Discord.Internal.Rest.Webhook.ModifyWebhookOpts
- Discord.Internal.Types: type Object = HashMap Text Value
+ Discord.Internal.Types: type Object = KeyMap Value

Files

README.md view
@@ -1,4 +1,4 @@-# discord-haskell           [![Build Status](https://travis-ci.com/aquarial/discord-haskell.png?branch=master)](https://travis-ci.com/aquarial/discord-haskell)        [![Hackage version](http://img.shields.io/hackage/v/discord-haskell.svg?label=Hackage)](https://hackage.haskell.org/package/discord-haskell)+# discord-haskell           [![Build Status](https://app.travis-ci.com/aquarial/discord-haskell.svg?branch=master)](https://app.travis-ci.com/github/aquarial/discord-haskell)        [![Hackage version](http://img.shields.io/hackage/v/discord-haskell.svg?label=Hackage)](https://hackage.haskell.org/package/discord-haskell)  Build that discord bot in Haskell! Also checkout the [calamity](https://github.com/nitros12/calamity)  haskell discord library for a more advanced interface than `discord-haskell`.@@ -8,7 +8,7 @@ ```haskell {-# LANGUAGE OverloadedStrings #-}  -- allows "string literals" to be Text -import Control.Monad (when)+import Control.Monad (when, void) import Data.Text (isPrefixOf, toLower, Text) import qualified Data.Text.IO as TIO @@ -27,20 +27,25 @@  eventHandler :: Event -> DiscordHandler () eventHandler event = case event of-       MessageCreate m -> when (not (fromBot m) && isPing (messageText m)) $ do-               _ <- restCall (R.CreateReaction (messageChannel m, messageId m) "eyes")-               threadDelay (4 * 10^6)-               _ <- restCall (R.CreateMessage (messageChannel m) "Pong!")-               pure ()-       _ -> pure ()+       MessageCreate m -> when (not (fromBot m) && isPing m) $ do+               void $ restCall (R.CreateReaction (messageChannel m, messageId m) "eyes")+               threadDelay (2 * 10^6)+               void $ restCall (R.CreateMessage (messageChannel m) "Pong!")+       _ -> return ()  fromBot :: Message -> Bool-fromBot m = userIsBot (messageAuthor m)+fromBot = userIsBot . messageAuthor -isPing :: Text -> Bool-isPing = ("ping" `isPrefixOf`) . toLower+isPing :: Message -> Bool+isPing = ("ping" `isPrefixOf`) . toLower . messageText ``` +### Biggest TODOs++- [ ] APIv9+- [ ] rewrite eventloop [issue 70](https://github.com/aquarial/discord-haskell/issues/70)+- [ ] Event type includes roles and other cached info+- [ ] higher level bot interface? easier to add state and stuff  ### Installing 
changelog.md view
@@ -6,6 +6,13 @@  ## master ++## 1.8.9++Handle both aeson 1.0 and 2.0 [(breaking changes broke builds)](https://github.com/aquarial/discord-haskell/issues/77)++Simplify [some examples](https://github.com/aquarial/discord-haskell/issues/71)+ ## 1.8.8  Remove git artifacts from [examples/ping-pong.hs](https://github.com/aquarial/discord-haskell/issues/69)
discord-haskell.cabal view
@@ -1,7 +1,7 @@ cabal-version:       2.0 name:                discord-haskell -- library version is also noted at src/Discord/Rest/Prelude.hs-version:             1.8.8+version:             1.8.9 description:         Functions and data types to write discord bots.                      Official discord docs <https://discord.com/developers/docs/reference>.                      .@@ -28,7 +28,7 @@   main-is:             examples/ping-pong.hs   default-language:    Haskell2010   ghc-options:         -Wall-                       -fno-warn-type-defaults+                       -fno-warn-type-defaults -fno-warn-unused-record-wildcards                        -threaded   build-depends:       base                      , text@@ -37,7 +37,7 @@  library   ghc-options:         -Wall-                       -fno-warn-type-defaults+                       -fno-warn-type-defaults -fno-warn-unused-record-wildcards   hs-source-dirs:      src   default-language:    Haskell2010   -- extensions:
examples/ping-pong.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE OverloadedStrings #-}  -- allows "strings" to be Data.Text -import Control.Monad (when, forM_)+import Control.Monad (when, forM_, void) import qualified Data.Text as T import qualified Data.Text.IO as TIO @@ -27,7 +27,6 @@                         , discordOnEvent = eventHandler                         , discordOnLog = \s -> TIO.putStrLn s >> TIO.putStrLn ""                         }-  threadDelay (1 `div` 10 * 10^(6 :: Int))   TIO.putStrLn t  -- If the start handler throws an exception, discord-haskell will gracefully shutdown@@ -50,20 +49,20 @@   forM_ partialGuilds $ \pg -> do     Right guild <- restCall $ R.GetGuild (partialGuildId pg)     Right chans <- restCall $ R.GetGuildChannels (guildId guild)-    case filter isTextChannel chans of-      (c:_) -> do _ <- restCall $ R.CreateMessage (channelId c) "Hello! I will reply to pings with pongs"-                  pure ()-      _ -> pure ()+    forM_ (take 1 (filter isTextChannel chans))+      (\channel -> restCall $ R.CreateMessage (channelId channel)+                                      "Hello! I will reply to pings with pongs") + -- If an event handler throws an exception, discord-haskell will continue to run eventHandler :: Event -> DiscordHandler () eventHandler event = case event of       MessageCreate m -> when (not (fromBot m) && isPing m) $ do-        _ <- restCall (R.CreateReaction (messageChannel m, messageId m) "eyes")-        threadDelay (4 * 10^(6 :: Int))+        void $ restCall (R.CreateReaction (messageChannel m, messageId m) "eyes")+        threadDelay (2 * 10^(6 :: Int))          -- A very simple message.-        _ <- restCall (R.CreateMessage (messageChannel m) "Pong!")+        void $ restCall (R.CreateMessage (messageChannel m) "Pong!")          -- A more complex message. Text-to-speech, does not mention everyone nor         -- the user, and uses Discord native replies.@@ -78,17 +77,15 @@                        , R.messageDetailedReference = Just $                           def { referenceMessageId = Just $ messageId m }                        }-        _ <- restCall (R.CreateMessageDetailed (messageChannel m) opts)--        pure ()-      _ -> pure ()+        void $ restCall (R.CreateMessageDetailed (messageChannel m) opts)+      _ -> return ()  isTextChannel :: Channel -> Bool isTextChannel (ChannelText {}) = True isTextChannel _ = False  fromBot :: Message -> Bool-fromBot m = userIsBot (messageAuthor m)+fromBot = userIsBot . messageAuthor  isPing :: Message -> Bool isPing = ("ping" `T.isPrefixOf`) . T.toLower . messageText
src/Discord/Internal/Gateway/EventLoop.hs view
@@ -64,6 +64,9 @@                   Right m -> do writeChan events (Left (GatewayExceptionUnexpected m                                                          "Response to Identify must be Ready"))                                 pure ConnClosed+                  Left (CloseRequest code _str) -> do writeChan log ("gateway - close " <> T.pack (show code))+                                                      threadDelay (3 * (10^(6 :: Int)))+                                                      pure ConnStart                   Left ce -> do writeChan events (Left (GatewayExceptionConnection ce                                                          "Response to Identify"))                                 pure ConnClosed@@ -71,6 +74,9 @@                             writeChan events (Left (GatewayExceptionUnexpected m                                                       "Response to connecting must be hello"))                             pure ConnClosed+              Left (CloseRequest code _str) -> do writeChan log ("gateway - close " <> T.pack (show code))+                                                  threadDelay (3 * (10^(6 :: Int)))+                                                  pure ConnStart               Left ce -> do writeChan events (Left (GatewayExceptionConnection ce                                                      "Response to connecting"))                             pure ConnClosed@@ -97,9 +103,12 @@                       writeChan events (Left (GatewayExceptionUnexpected payload                                                "Response to Resume must be Hello/Invalid Session"))                       pure ConnClosed+                  Left (CloseRequest code _str) -> do+                      writeChan log ("gateway - retrying from " <> T.pack (show code))+                      threadDelay (3 * (10^(6 :: Int)))+                      pure (ConnReconnect (Auth tok) seshID seqID)                   Left e -> do-                      writeChan events (Left (GatewayExceptionConnection e-                                               "Could not ConnReconnect"))+                      writeChan events (Left (GatewayExceptionConnection e "Could not ConnReconnect"))                       pure ConnClosed           case next :: Either SomeException ConnLoopState of             Left _ -> do t <- getRandomR (3,20)@@ -135,9 +144,6 @@     writeChan send (Heartbeat num)     threadDelay (interval * 1000) -setSequence :: IORef Integer -> Integer -> IO ()-setSequence key i = writeIORef key i- -- | What we need to start an event stream data ConnectionData = ConnData { connection :: Connection                                , connSessionID :: T.Text@@ -172,6 +178,8 @@     case eitherPayload :: Either ConnectionException GatewayReceivable of       Left (CloseRequest code str) -> case code of           -- see Discord and MDN documentation on gateway close event codes+          -- https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-close-event-codes+          -- https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#properties           1000 -> ConnReconnect auth seshID <$> readIORef seqKey           1001 -> ConnReconnect auth seshID <$> readIORef seqKey           4000 -> ConnReconnect auth seshID <$> readIORef seqKey@@ -182,11 +190,11 @@                                               "Normal event loop close request"))                   pure ConnClosed       Left _ -> ConnReconnect auth seshID <$> readIORef seqKey-      Right (Dispatch event sq) -> do setSequence seqKey sq+      Right (Dispatch event sq) -> do writeIORef seqKey sq                                       writeChan eventChan (Right event)                                       writeIORef userSends True                                       loop-      Right (HeartbeatRequest sq) -> do setSequence seqKey sq+      Right (HeartbeatRequest sq) -> do writeIORef seqKey sq                                         writeChan send (Heartbeat sq)                                         loop       Right (Reconnect)      -> ConnReconnect auth seshID <$> readIORef seqKey
src/Discord/Internal/Rest/Channel.hs view
@@ -106,7 +106,7 @@   , messageDetailedFile                     :: Maybe (T.Text, B.ByteString)   , messageDetailedAllowedMentions          :: Maybe AllowedMentions   , messageDetailedReference                :: Maybe MessageReference-  }+  } deriving (Show, Eq, Ord)  instance Default MessageDetailedOpts where   def = MessageDetailedOpts { messageDetailedContent         = ""@@ -125,7 +125,7 @@   , mentionUserIds     :: [UserId]   , mentionRoleIds     :: [RoleId]   , mentionRepliedUser :: Bool-  }+  } deriving (Show, Eq, Ord)  instance Default AllowedMentions where   def = AllowedMentions { mentionEveryone    = False@@ -140,8 +140,10 @@   toJSON AllowedMentions{..} = object [                                  ("parse" .= [name :: T.Text | (name, True) <-                                      [ ("everyone", mentionEveryone),-                                       ("users",    mentionUsers),-                                       ("roles",    mentionRoles) ] ]),+                                       ("users",    mentionUsers && mentionUserIds == []),+                                       ("roles",    mentionRoles && mentionRoleIds == []) ] ]),+                                 -- https://discord.com/developers/docs/resources/channel#allowed-mentions-object+                                 --  parse.users and users list cannot both be active, prioritize id list                                  ("roles"        .= mentionRoleIds),                                  ("users"        .= mentionUserIds),                                  ("replied_user" .= mentionRepliedUser) ]@@ -150,6 +152,7 @@ data ReactionTiming = BeforeReaction MessageId                     | AfterReaction MessageId                     | LatestReaction+  deriving (Show, Eq, Ord)  reactionTimingToQuery :: ReactionTiming -> R.Option 'R.Https reactionTimingToQuery t = case t of@@ -162,6 +165,7 @@                    | BeforeMessage MessageId                    | AfterMessage MessageId                    | LatestMessages+  deriving (Show, Eq, Ord)  messageTimingToQuery :: MessageTiming -> R.Option 'R.Https messageTimingToQuery t = case t of@@ -175,7 +179,7 @@   , channelInviteOptsMaxUsages              :: Maybe Integer   , channelInviteOptsIsTemporary            :: Maybe Bool   , channelInviteOptsDontReuseSimilarInvite :: Maybe Bool-  }+  } deriving (Show, Eq, Ord)  instance ToJSON ChannelInviteOpts where   toJSON ChannelInviteOpts{..} = object [(name, val) | (name, Just val) <-@@ -193,7 +197,7 @@   , modifyChannelUserRateLimit        :: Maybe Integer   , modifyChannelPermissionOverwrites :: Maybe [Overwrite]   , modifyChannelParentId             :: Maybe ChannelId-  }+  } deriving (Show, Eq, Ord)  instance ToJSON ModifyChannelOpts where   toJSON ModifyChannelOpts{..} = object [(name, val) | (name, Just val) <-@@ -209,10 +213,12 @@ data ChannelPermissionsOpts = ChannelPermissionsOpts   { channelPermissionsOptsAllow :: Integer   , channelPermissionsOptsDeny :: Integer-  , channelPermissionsOptsType :: ChannelPermissionsOptsType}+  , channelPermissionsOptsType :: ChannelPermissionsOptsType+  } deriving (Show, Eq, Ord)  data ChannelPermissionsOptsType = ChannelPermissionsOptsUser                                 | ChannelPermissionsOptsRole+  deriving (Show, Eq, Ord)  instance ToJSON ChannelPermissionsOptsType where   toJSON t = case t of ChannelPermissionsOptsUser -> String "member"@@ -228,7 +234,7 @@   { groupDMAddRecipientUserToAdd :: UserId   , groupDMAddRecipientUserToAddNickName :: T.Text   , groupDMAddRecipientGDMJoinAccessToken :: T.Text-  }+  } deriving (Show, Eq, Ord)  channelMajorRoute :: ChannelRequest a -> String channelMajorRoute c = case c of
src/Discord/Internal/Rest/Emoji.hs view
@@ -45,7 +45,7 @@ data ModifyGuildEmojiOpts = ModifyGuildEmojiOpts      { modifyGuildEmojiName  :: T.Text      , modifyGuildEmojiRoles :: [RoleId]-     }+     } deriving (Show, Eq, Ord)  instance ToJSON ModifyGuildEmojiOpts where   toJSON (ModifyGuildEmojiOpts name roles) =@@ -53,6 +53,7 @@   data EmojiImageParsed = EmojiImageParsed T.Text+  deriving (Show, Eq, Ord)  parseEmojiImage :: B.ByteString -> Either T.Text EmojiImageParsed parseEmojiImage bs =
src/Discord/Internal/Rest/Guild.hs view
@@ -287,7 +287,7 @@ data GuildMembersTiming = GuildMembersTiming                           { guildMembersTimingLimit :: Maybe Int                           , guildMembersTimingAfter :: Maybe UserId-                          }+                          } deriving (Show, Eq, Ord)  guildMembersTimingToQuery :: GuildMembersTiming -> R.Option 'R.Https guildMembersTimingToQuery (GuildMembersTiming mLimit mAfter) =
src/Discord/Internal/Rest/Prelude.hs view
@@ -29,7 +29,7 @@   where   -- | https://discord.com/developers/docs/reference#user-agent   -- Second place where the library version is noted-  agent = "DiscordBot (https://github.com/aquarial/discord-haskell, 1.8.8)"+  agent = "DiscordBot (https://github.com/aquarial/discord-haskell, 1.8.9)"  -- Append to an URL infixl 5 //
src/Discord/Internal/Rest/User.hs view
@@ -54,6 +54,7 @@  -- | Formatted avatar data https://discord.com/developers/docs/resources/user#avatar-data data CurrentUserAvatar = CurrentUserAvatar T.Text+  deriving (Show, Eq, Ord)  parseCurrentUserAvatar :: B.ByteString -> Either T.Text CurrentUserAvatar parseCurrentUserAvatar bs =
src/Discord/Internal/Rest/Webhook.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE InstanceSigs #-}@@ -13,7 +14,7 @@   , WebhookRequest(..)   ) where -import Data.Aeson+import           Data.Aeson import qualified Data.Text as T import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL@@ -25,6 +26,17 @@ import Discord.Internal.Rest.Prelude import Discord.Internal.Types +-- aeson introduced type name for json key (text)+-- https://github.com/haskell/aeson/issues/881+# if MIN_VERSION_aeson(2, 0, 0)+import qualified Data.Aeson.Key as Key+toKey :: T.Text -> Key.Key+toKey = Key.fromText+# else+toKey :: T.Text -> T.Text+toKey = id+# endif+ instance Request (WebhookRequest a) where   majorRoute = webhookMajorRoute   jsonRequest = webhookJsonRequest@@ -49,10 +61,10 @@   { modifyWebhookOptsName          :: Maybe T.Text   , modifyWebhookOptsAvatar        :: Maybe T.Text   , modifyWebhookOptsChannelId     :: Maybe ChannelId-  }+  } deriving (Show, Eq, Ord)  instance ToJSON ModifyWebhookOpts where-  toJSON ModifyWebhookOpts{..} = object [(name, val) | (name, Just val) <-+  toJSON ModifyWebhookOpts{..} = object [(toKey name, val) | (name, Just val) <-                          [("channel_id",   toJSON <$> modifyWebhookOptsChannelId),                           ("name",   toJSON <$> modifyWebhookOptsName),                           ("avatar",  toJSON <$> modifyWebhookOptsAvatar) ] ]@@ -60,7 +72,7 @@ data CreateWebhookOpts = CreateWebhookOpts   { createWebhookOptsName          :: T.Text   , createWebhookOptsAvatar        :: Maybe T.Text-  }+  } deriving (Show, Eq, Ord)  instance ToJSON CreateWebhookOpts where   toJSON CreateWebhookOpts{..} = object [(name, val) | (name, Just val) <-@@ -70,24 +82,24 @@ data ExecuteWebhookWithTokenOpts = ExecuteWebhookWithTokenOpts   { executeWebhookWithTokenOptsUsername      :: Maybe T.Text   , executeWebhookWithTokenOptsContent       :: WebhookContent-  }-  deriving (Show, Eq, Ord)+  } deriving (Show, Eq, Ord)  data WebhookContent = WebhookContentText T.Text                     | WebhookContentFile T.Text B.ByteString                     | WebhookContentEmbeds [CreateEmbed]   deriving (Show, Eq, Ord) -webhookContentJson :: WebhookContent -> [(T.Text, Value)]+webhookContentJson :: WebhookContent -> [(T.Text, Maybe Value)] webhookContentJson c = case c of-                      WebhookContentText t -> [("content", toJSON t)]+                      WebhookContentText t -> [("content", Just (toJSON t))]                       WebhookContentFile _ _  -> []-                      WebhookContentEmbeds e -> [("embeds", toJSON (createEmbed <$> e))]+                      WebhookContentEmbeds e -> [("embeds", Just (toJSON (createEmbed <$> e)))]  instance ToJSON ExecuteWebhookWithTokenOpts where-  toJSON ExecuteWebhookWithTokenOpts{..} = object $ [(name, val) | (name, Just val) <--                         [("username",   toJSON <$> executeWebhookWithTokenOptsUsername)] ]-                      <> webhookContentJson executeWebhookWithTokenOptsContent+  toJSON ExecuteWebhookWithTokenOpts{..} = object $ [(toKey name, val) | (name, Just val) <-+                         [("username",   toJSON <$> executeWebhookWithTokenOptsUsername)]+                           <> webhookContentJson executeWebhookWithTokenOptsContent+                         ]  webhookMajorRoute :: WebhookRequest a -> String webhookMajorRoute ch = case ch of