summaryrefslogtreecommitdiff
path: root/deps/discord-haskell/src/Discord/Internal/Rest
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2023-11-16 19:06:43 -0500
committerLLLL Colonq <llll@colonq>2023-11-16 19:06:43 -0500
commitdcef0b65069fb38fd0f6c4382353167f603ebff1 (patch)
tree45954ffe308c3dd056e6af4f734e6d2af89e5856 /deps/discord-haskell/src/Discord/Internal/Rest
Initial commit
Diffstat (limited to 'deps/discord-haskell/src/Discord/Internal/Rest')
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/ApplicationCommands.hs172
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/Channel.hs607
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/Emoji.hs201
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/Guild.hs468
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/HTTP.hs140
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/Interactions.hs90
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/Invite.hs43
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/Prelude.hs74
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/ScheduledEvents.hs73
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/User.hs99
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/Voice.hs37
-rw-r--r--deps/discord-haskell/src/Discord/Internal/Rest/Webhook.hs202
12 files changed, 2206 insertions, 0 deletions
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/ApplicationCommands.hs b/deps/discord-haskell/src/Discord/Internal/Rest/ApplicationCommands.hs
new file mode 100644
index 0000000..9ed33b3
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/ApplicationCommands.hs
@@ -0,0 +1,172 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Discord.Internal.Rest.ApplicationCommands where
+
+import Data.Aeson (Value)
+import Discord.Internal.Rest.Prelude
+import Discord.Internal.Types
+import Discord.Internal.Types.ApplicationCommands
+ ( ApplicationCommandPermissions,
+ GuildApplicationCommandPermissions(GuildApplicationCommandPermissions),
+ EditApplicationCommand,
+ CreateApplicationCommand,
+ ApplicationCommand )
+import Network.HTTP.Req as R
+
+instance Request (ApplicationCommandRequest a) where
+ jsonRequest = applicationCommandJsonRequest
+ majorRoute = applicationCommandMajorRoute
+
+-- | Requests related to application commands
+data ApplicationCommandRequest a where
+ -- | Fetch all of the global commands for your application. Returns an list of 'ApplicationCommand's.
+ GetGlobalApplicationCommands :: ApplicationId
+ -> ApplicationCommandRequest [ApplicationCommand]
+ -- | Create a new global command. Returns an 'ApplicationCommand'.
+ --
+ -- __Note__: Creating a command with the same name as an existing command for your application will overwrite the old command.
+ CreateGlobalApplicationCommand :: ApplicationId
+ -> CreateApplicationCommand
+ -> ApplicationCommandRequest ApplicationCommand
+ -- | Fetch a global command for your application. Returns an 'ApplicationCommand'.
+ GetGlobalApplicationCommand :: ApplicationId
+ -> ApplicationCommandId
+ -> ApplicationCommandRequest ApplicationCommand
+ -- | Edit a global command. Returns an 'ApplicationCommand'.
+ --
+ -- All fields are optional, but any fields provided will entirely overwrite the existing values of those fields.
+ EditGlobalApplicationCommand :: ApplicationId
+ -> ApplicationCommandId
+ -> EditApplicationCommand
+ -> ApplicationCommandRequest ApplicationCommand
+ -- | Delete a global command.
+ DeleteGlobalApplicationCommand :: ApplicationId
+ -> ApplicationCommandId
+ -> ApplicationCommandRequest ()
+ -- | Takes a list of 'CreateApplicationCommand', overwriting the existing global command list for this application.
+ --
+ -- __Note__: This will overwrite __all__ types of application commands: slash commands, user commands, and message commands.
+ BulkOverWriteGlobalApplicationCommand :: ApplicationId
+ -> [CreateApplicationCommand]
+ -> ApplicationCommandRequest ()
+ -- | Fetch all of the guild commands for your application for a specific guild. Returns an list of 'ApplicationCommands'.
+ GetGuildApplicationCommands :: ApplicationId
+ -> GuildId
+ -> ApplicationCommandRequest [ApplicationCommand]
+ -- | Create a new guild command. New guild commands will be available in the guild immediately.
+ -- Returns an 'ApplicationCommand'.
+ -- If the command did not already exist, it will count toward daily application command create limits.
+ --
+ -- __Note__: Creating a command with the same name as an existing command for your application will overwrite the old command.
+ CreateGuildApplicationCommand :: ApplicationId
+ -> GuildId
+ -> CreateApplicationCommand
+ -> ApplicationCommandRequest ApplicationCommand
+ -- | Fetch a guild command for your application. Returns an 'ApplicationCommand'
+ GetGuildApplicationCommand :: ApplicationId
+ -> GuildId
+ -> ApplicationCommandId
+ -> ApplicationCommandRequest ApplicationCommand
+ -- | Edit a guild command. Updates for guild commands will be available immediately. Returns an 'ApplicationCommand'.
+ -- All fields are optional, but any fields provided will entirely overwrite the existing values of those fields.
+ EditGuildApplicationCommand :: ApplicationId
+ -> GuildId
+ -> ApplicationCommandId
+ -> CreateApplicationCommand
+ -> ApplicationCommandRequest ApplicationCommand
+ -- | Delete a guild command.
+ DeleteGuildApplicationCommand :: ApplicationId
+ -> GuildId
+ -> ApplicationCommandId
+ -> ApplicationCommandRequest ()
+ -- | Takes a list of `CreateApplicationCommand`, overwriting the existing command list for this application for the targeted guild.
+ --
+ -- __Note__: This will overwrite __all__ types of application commands: slash commands, user commands, and message commands.
+ BulkOverWriteGuildApplicationCommand :: ApplicationId
+ -> GuildId
+ -> [CreateApplicationCommand]
+ -> ApplicationCommandRequest ()
+ -- | Fetches permissions for all commands for your application in a guild.
+ GetGuildApplicationCommandPermissions :: ApplicationId
+ -> GuildId
+ -> ApplicationCommandRequest GuildApplicationCommandPermissions
+ -- | Fetches permissions for a specific command for your application in a guild.
+ GetApplicationCommandPermissions :: ApplicationId
+ -> GuildId
+ -> ApplicationCommandId
+ -> ApplicationCommandRequest GuildApplicationCommandPermissions
+ -- | Edits command permissions for a specific command for your application.
+ -- You can add up to 100 permission overwrites for a command.
+ -- __Notes__:
+ --
+ -- * This endpoint will overwrite existing permissions for the command in that guild
+ -- * This endpoint requires authentication with a Bearer token that has permission to manage the guild and its roles.
+ -- * Deleting or renaming a command will permanently delete all permissions for the command
+ EditApplicationCommandPermissions :: ApplicationId
+ -> GuildId
+ -> ApplicationCommandId
+ -> [ApplicationCommandPermissions]
+ -> ApplicationCommandRequest GuildApplicationCommandPermissions
+
+-- | The base url for application commands
+applications :: ApplicationId -> R.Url 'R.Https
+applications s = baseUrl /: "applications" /~ s
+
+-- | The major routes identifiers for `ApplicationCommandRequest`s
+applicationCommandMajorRoute :: ApplicationCommandRequest a -> String
+applicationCommandMajorRoute a = case a of
+ (GetGlobalApplicationCommands aid) -> "get_glob_appcomm" <> show aid
+ (CreateGlobalApplicationCommand aid _) -> "write_glob_appcomm" <> show aid
+ (GetGlobalApplicationCommand aid _) -> "get_glob_appcomm" <> show aid
+ (EditGlobalApplicationCommand aid _ _) -> "write_glob_appcomm" <> show aid
+ (DeleteGlobalApplicationCommand aid _) -> "write_glob_appcomm" <> show aid
+ (BulkOverWriteGlobalApplicationCommand aid _) -> "write_glob_appcomm" <> show aid
+ (GetGuildApplicationCommands aid _) -> "get_appcomm" <> show aid
+ (CreateGuildApplicationCommand aid _ _) -> "write_appcomm" <> show aid
+ (GetGuildApplicationCommand aid _ _) -> "get_appcomm" <> show aid
+ (EditGuildApplicationCommand aid _ _ _) -> "write_appcomm" <> show aid
+ (DeleteGuildApplicationCommand aid _ _) -> "write_appcomm" <> show aid
+ (BulkOverWriteGuildApplicationCommand aid _ _) -> "write_appcomm" <> show aid
+ (GetGuildApplicationCommandPermissions aid _) -> "appcom_perm " <> show aid
+ (GetApplicationCommandPermissions aid _ _) -> "appcom_perm " <> show aid
+ (EditApplicationCommandPermissions aid _ _ _) -> "appcom_perm " <> show aid
+
+-- | The `JsonRequest`s for `ApplicationCommandRequest`s
+applicationCommandJsonRequest :: ApplicationCommandRequest a -> JsonRequest
+applicationCommandJsonRequest a = case a of
+ (GetGlobalApplicationCommands aid) ->
+ Get (applications aid /: "commands") mempty
+ (CreateGlobalApplicationCommand aid cac) ->
+ Post (applications aid /: "commands") (convert cac) mempty
+ (GetGlobalApplicationCommand aid aci) ->
+ Get (applications aid /: "commands" /~ aci) mempty
+ (EditGlobalApplicationCommand aid aci eac) ->
+ Patch (applications aid /: "commands" /~ aci) (convert eac) mempty
+ (DeleteGlobalApplicationCommand aid aci) ->
+ Delete (applications aid /: "commands" /~ aci) mempty
+ (BulkOverWriteGlobalApplicationCommand aid cacs) ->
+ Put (applications aid /: "commands") (R.ReqBodyJson $ toJSON cacs) mempty
+ (GetGuildApplicationCommands aid gid) ->
+ Get (applications aid /: "guilds" /~ gid /: "commands") mempty
+ (CreateGuildApplicationCommand aid gid cac) ->
+ Post (applications aid /: "guilds" /~ gid /: "commands") (convert cac) mempty
+ (GetGuildApplicationCommand aid gid aci) ->
+ Get (applications aid /: "guilds" /~ gid /: "commands" /~ aci) mempty
+ (EditGuildApplicationCommand aid gid aci eac) ->
+ Patch (applications aid /: "guilds" /~ gid /: "commands" /~ aci) (convert eac) mempty
+ (DeleteGuildApplicationCommand aid gid aci) ->
+ Delete (applications aid /: "guilds" /~ gid /: "commands" /~ aci) mempty
+ (BulkOverWriteGuildApplicationCommand aid gid cacs) ->
+ Put (applications aid /: "guilds" /~ gid /: "commands") (R.ReqBodyJson $ toJSON cacs) mempty
+ (GetGuildApplicationCommandPermissions aid gid) ->
+ Get (applications aid /: "guilds" /~ gid /: "commands" /: "permissions") mempty
+ (GetApplicationCommandPermissions aid gid cid) ->
+ Get (applications aid /: "guilds" /~ gid /: "commands" /~ cid /: "permissions") mempty
+ (EditApplicationCommandPermissions aid gid cid ps) ->
+ Put (applications aid /: "guilds" /~ gid /: "commands" /~ cid /: "permissions") (R.ReqBodyJson $ toJSON (GuildApplicationCommandPermissions cid aid gid ps)) mempty
+ where
+ convert :: (ToJSON a) => a -> RestIO (ReqBodyJson Value)
+ convert = (pure @RestIO) . R.ReqBodyJson . toJSON
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/Channel.hs b/deps/discord-haskell/src/Discord/Internal/Rest/Channel.hs
new file mode 100644
index 0000000..1024d9d
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/Channel.hs
@@ -0,0 +1,607 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Provides actions for Channel API interactions
+module Discord.Internal.Rest.Channel
+ ( ChannelRequest(..)
+ , MessageDetailedOpts(..)
+ , AllowedMentions(..)
+ , ReactionTiming(..)
+ , MessageTiming(..)
+ , ChannelInviteOpts(..)
+ , ModifyChannelOpts(..)
+ , ChannelPermissionsOpts(..)
+ , GroupDMAddRecipientOpts(..)
+ , StartThreadOpts(..)
+ , StartThreadNoMessageOpts(..)
+ , ListThreads(..)
+ ) where
+
+
+import Data.Aeson
+import Data.Default (Default, def)
+import Data.Emoji (unicodeByName)
+import qualified Data.Text as T
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import Network.HTTP.Client (RequestBody (RequestBodyBS))
+import Network.HTTP.Client.MultipartFormData (partFileRequestBody, partBS)
+import Network.HTTP.Req ((/:), (/~))
+import qualified Network.HTTP.Req as R
+
+import Discord.Internal.Rest.Prelude
+import Discord.Internal.Types
+import Control.Monad (join)
+
+instance Request (ChannelRequest a) where
+ majorRoute = channelMajorRoute
+ jsonRequest = channelJsonRequest
+
+-- | Data constructor for requests. See <https://discord.com/developers/docs/resources/ API>
+data ChannelRequest a where
+ -- | Gets a channel by its id.
+ GetChannel :: ChannelId -> ChannelRequest Channel
+ -- | Edits channels options.
+ ModifyChannel :: ChannelId -> ModifyChannelOpts -> ChannelRequest Channel
+ -- | Deletes a channel if its id doesn't equal to the id of guild.
+ DeleteChannel :: ChannelId -> ChannelRequest Channel
+ -- | Gets a messages from a channel with limit of 100 per request.
+ GetChannelMessages :: ChannelId -> (Int, MessageTiming) -> ChannelRequest [Message]
+ -- | Gets a message in a channel by its id.
+ GetChannelMessage :: (ChannelId, MessageId) -> ChannelRequest Message
+ -- | Sends a message to a channel.
+ CreateMessage :: ChannelId -> T.Text -> ChannelRequest Message
+ -- | Sends a message with granular controls.
+ CreateMessageDetailed :: ChannelId -> MessageDetailedOpts -> ChannelRequest Message
+ -- | Add an emoji reaction to a message. ID must be present for custom emoji
+ CreateReaction :: (ChannelId, MessageId) -> T.Text -> ChannelRequest ()
+ -- | Remove a Reaction this bot added
+ DeleteOwnReaction :: (ChannelId, MessageId) -> T.Text -> ChannelRequest ()
+ -- | Remove a Reaction someone else added
+ DeleteUserReaction :: (ChannelId, MessageId) -> UserId -> T.Text -> ChannelRequest ()
+ -- | Deletes all reactions of a single emoji on a message
+ DeleteSingleReaction :: (ChannelId, MessageId) -> T.Text -> ChannelRequest ()
+ -- | List of users that reacted with this emoji
+ GetReactions :: (ChannelId, MessageId) -> T.Text -> (Int, ReactionTiming) -> ChannelRequest [User]
+ -- | Delete all reactions on a message
+ DeleteAllReactions :: (ChannelId, MessageId) -> ChannelRequest ()
+ -- | Edits a message content.
+ EditMessage :: (ChannelId, MessageId) -> MessageDetailedOpts
+ -> ChannelRequest Message
+ -- | Deletes a message.
+ DeleteMessage :: (ChannelId, MessageId) -> ChannelRequest ()
+ -- | Deletes a group of messages.
+ BulkDeleteMessage :: (ChannelId, [MessageId]) -> ChannelRequest ()
+ -- | Edits a permission overrides for a channel.
+ EditChannelPermissions :: ChannelId -> Either RoleId UserId -> ChannelPermissionsOpts -> ChannelRequest ()
+ -- | Gets all instant invites to a channel.
+ GetChannelInvites :: ChannelId -> ChannelRequest Object
+ -- | Creates an instant invite to a channel.
+ CreateChannelInvite :: ChannelId -> ChannelInviteOpts -> ChannelRequest Invite
+ -- | Deletes a permission override from a channel.
+ DeleteChannelPermission :: ChannelId -> Either RoleId UserId -> ChannelRequest ()
+ -- | Sends a typing indicator a channel which lasts 10 seconds.
+ TriggerTypingIndicator :: ChannelId -> ChannelRequest ()
+ -- | Gets all pinned messages of a channel.
+ GetPinnedMessages :: ChannelId -> ChannelRequest [Message]
+ -- | Pins a message.
+ AddPinnedMessage :: (ChannelId, MessageId) -> ChannelRequest ()
+ -- | Unpins a message.
+ DeletePinnedMessage :: (ChannelId, MessageId) -> ChannelRequest ()
+ -- | Adds a recipient to a Group DM using their access token
+ GroupDMAddRecipient :: ChannelId -> GroupDMAddRecipientOpts -> ChannelRequest ()
+ -- | Removes a recipient from a Group DM
+ GroupDMRemoveRecipient :: ChannelId -> UserId -> ChannelRequest ()
+ -- | Start a thread from a message
+ StartThreadFromMessage :: ChannelId -> MessageId -> StartThreadOpts -> ChannelRequest Channel
+ -- | Start a thread without a message
+ StartThreadNoMessage :: ChannelId -> StartThreadNoMessageOpts -> ChannelRequest Channel
+ -- | Join a thread
+ JoinThread :: ChannelId -> ChannelRequest ()
+ -- | Add a thread member
+ AddThreadMember :: ChannelId -> UserId -> ChannelRequest ()
+ -- | Leave a thread
+ LeaveThread :: ChannelId -> ChannelRequest ()
+ -- | Remove a thread member
+ RemoveThreadMember :: ChannelId -> UserId -> ChannelRequest ()
+ -- | Get a thread member
+ GetThreadMember :: ChannelId -> UserId -> ChannelRequest ThreadMember
+ -- | List the thread members
+ ListThreadMembers :: ChannelId -> ChannelRequest [ThreadMember]
+ -- | List public archived threads in the given channel. Optionally before a
+ -- given time, and optional maximum number of threads. Returns the threads,
+ -- thread members, and whether there are more to collect.
+ -- Requires the READ_MESSAGE_HISTORY permission.
+ ListPublicArchivedThreads :: ChannelId -> (Maybe UTCTime, Maybe Integer) -> ChannelRequest ListThreads
+ -- | List private archived threads in the given channel. Optionally before a
+ -- given time, and optional maximum number of threads. Returns the threads,
+ -- thread members, and whether there are more to collect.
+ -- Requires both the READ_MESSAGE_HISTORY and MANAGE_THREADS permissions.
+ ListPrivateArchivedThreads :: ChannelId -> (Maybe UTCTime, Maybe Integer) -> ChannelRequest ListThreads
+ -- | List joined private archived threads in the given channel. Optionally
+ -- before a given time, and optional maximum number of threads. Returns the
+ -- threads, thread members, and whether there are more to collect.
+ -- Requires both the READ_MESSAGE_HISTORY and MANAGE_THREADS permissions.
+ ListJoinedPrivateArchivedThreads :: ChannelId -> (Maybe UTCTime, Maybe Integer) -> ChannelRequest ListThreads
+
+
+-- | Options for `CreateMessageDetailed` requests.
+data MessageDetailedOpts = MessageDetailedOpts
+ { -- | The message contents (up to 2000 characters)
+ messageDetailedContent :: T.Text
+ , -- | `True` if this is a TTS message
+ messageDetailedTTS :: Bool
+ , -- | embedded rich content (up to 6000 characters)
+ messageDetailedEmbeds :: Maybe [CreateEmbed]
+ , -- | the contents of the file being sent
+ messageDetailedFile :: Maybe (T.Text, B.ByteString)
+ , -- | allowed mentions for the message
+ messageDetailedAllowedMentions :: Maybe AllowedMentions
+ , -- | If `Just`, reply to the message referenced
+ messageDetailedReference :: Maybe MessageReference
+ , -- | Message components for the message
+ messageDetailedComponents :: Maybe [ActionRow]
+ , -- | IDs of up to 3 `Sticker` in the server to send with the message
+ messageDetailedStickerIds :: Maybe [StickerId]
+ } deriving (Show, Read, Eq, Ord)
+
+instance Default MessageDetailedOpts where
+ def = MessageDetailedOpts { messageDetailedContent = ""
+ , messageDetailedTTS = False
+ , messageDetailedEmbeds = Nothing
+ , messageDetailedFile = Nothing
+ , messageDetailedAllowedMentions = Nothing
+ , messageDetailedReference = Nothing
+ , messageDetailedComponents = Nothing
+ , messageDetailedStickerIds = Nothing
+ }
+
+-- | Data constructor for `GetReactions` requests
+data ReactionTiming = BeforeReaction MessageId
+ | AfterReaction MessageId
+ | LatestReaction
+ deriving (Show, Read, Eq, Ord)
+
+reactionTimingToQuery :: ReactionTiming -> R.Option 'R.Https
+reactionTimingToQuery t = case t of
+ (BeforeReaction snow) -> "before" R.=: show snow
+ (AfterReaction snow) -> "after" R.=: show snow
+ LatestReaction -> mempty
+
+-- | Data constructor for `GetChannelMessages` requests.
+--
+-- See <https://discord.com/developers/docs/resources/channel#get-channel-messages>
+data MessageTiming = AroundMessage MessageId
+ | BeforeMessage MessageId
+ | AfterMessage MessageId
+ | LatestMessages
+ deriving (Show, Read, Eq, Ord)
+
+messageTimingToQuery :: MessageTiming -> R.Option 'R.Https
+messageTimingToQuery t = case t of
+ (AroundMessage snow) -> "around" R.=: show snow
+ (BeforeMessage snow) -> "before" R.=: show snow
+ (AfterMessage snow) -> "after" R.=: show snow
+ LatestMessages -> mempty
+
+-- | Options for `CreateChannelInvite` requests
+data ChannelInviteOpts = ChannelInviteOpts
+ { -- | How long the invite is valid for (in seconds)
+ channelInviteOptsMaxAgeSeconds :: Maybe Integer
+ , -- | How many uses the invite is valid for
+ channelInviteOptsMaxUsages :: Maybe Integer
+ , -- | Whether this invite only grants temporary membership
+ channelInviteOptsIsTemporary :: Maybe Bool
+ , -- | Don't reuse a similar invite. Useful for creating many unique one time
+ -- use invites
+ channelInviteOptsDontReuseSimilarInvite :: Maybe Bool
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON ChannelInviteOpts where
+ toJSON ChannelInviteOpts{..} = objectFromMaybes
+ ["max_age" .=? channelInviteOptsMaxAgeSeconds,
+ "max_uses" .=? channelInviteOptsMaxUsages,
+ "temporary" .=? channelInviteOptsIsTemporary,
+ "unique" .=? channelInviteOptsDontReuseSimilarInvite ]
+
+-- | Options for `ModifyChannel` requests
+data ModifyChannelOpts = ModifyChannelOpts
+ { -- | (All) The name of the channel (max 100 characters)
+ modifyChannelName :: Maybe T.Text
+ , -- | (All) Position of the channel in the listing
+ modifyChannelPosition :: Maybe Integer
+ , -- | (Text) The channel topic text (max 1024 characters)
+ modifyChannelTopic :: Maybe T.Text
+ , -- | (Text) Wether the channel is tagged as NSFW
+ modifyChannelNSFW :: Maybe Bool
+ , -- | (Voice) Bitrate (in bps) of a voice channel. Min 8000, max 96000
+ -- (128000 for boosted servers)
+ modifyChannelBitrate :: Maybe Integer
+ , -- | (Text) The rate limit of the channel, in seconds (0-21600), does not
+ -- affect bots and users with @manage_channel@ or @manage_messages@
+ -- permissons
+ modifyChannelUserRateLimit :: Maybe Integer
+ , -- | (Voice) the user limit of the voice channel, max 99
+ modifyChannelUserLimit :: Maybe Integer
+ , -- | (All) The channel permissions
+ modifyChannelPermissionOverwrites :: Maybe [Overwrite]
+ , -- | (All) The parent category of the channel
+ modifyChannelParentId :: Maybe ChannelId
+ , -- | (Text) Auto-archive duration for Threads
+ modifyChannelDefaultAutoArchive :: Maybe Integer
+ , -- | (Thread) Whether the thread is archived
+ modifyChannelThreadArchived :: Maybe Bool
+ , -- | (Thread) duration in minutes to automatically archive the thread after
+ -- recent activity, can be set to: 60, 1440, 4320 or 10080
+ modifyChannelThreadAutoArchive :: Maybe Integer
+ , -- | (Thread) Whether the thread is locked. When a thread is locked, only
+ -- users with @manage_threads@ can unarchive it
+ modifyChannelThreadLocked :: Maybe Bool
+ , -- | (Thread) Whether non-moderators can add other non-moderators to a
+ -- thread. Only available on private threads
+ modifyChannelThreadInvitable :: Maybe Bool
+ } deriving (Show, Read, Eq, Ord)
+
+instance Default ModifyChannelOpts where
+ def = ModifyChannelOpts Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
+
+instance ToJSON ModifyChannelOpts where
+ toJSON ModifyChannelOpts{..} = objectFromMaybes
+ ["name" .=? modifyChannelName,
+ "position" .=? modifyChannelPosition,
+ "topic" .=? modifyChannelTopic,
+ "nsfw" .=? modifyChannelNSFW,
+ "bitrate" .=? modifyChannelBitrate,
+ "rate_limit_per_user" .=? modifyChannelUserRateLimit,
+ "user_limit" .=? modifyChannelUserLimit,
+ "permission_overwrites" .=? modifyChannelPermissionOverwrites,
+ "parent_id" .=? modifyChannelParentId,
+ "default_auto_archive_duration" .=? modifyChannelDefaultAutoArchive,
+ "archived" .=? modifyChannelThreadArchived,
+ "auto_archive_duration" .=? modifyChannelThreadAutoArchive,
+ "locked" .=? modifyChannelThreadLocked,
+ "invitable" .=? modifyChannelThreadInvitable ]
+
+-- | Options for The `EditChannelPermissions` request
+--
+-- Since the JSON encoding of this datatype will require information in the
+-- route (the Either decides whether the overwrite is for a user or a role), we
+-- do not provide a ToJSON instance. Instead, the JSON is manually constructed
+-- in the 'channelJsonRequest' function.
+data ChannelPermissionsOpts = ChannelPermissionsOpts
+ { -- | The permission integer for the explicitly allowed permissions
+ channelPermissionsOptsAllow :: Integer
+ , -- | The permission integer for the explicitly denied permissions
+ channelPermissionsOptsDeny :: Integer
+ } deriving (Show, Read, Eq, Ord)
+
+-- | Options for `GroupDMAddRecipient` request
+--
+-- See <https://discord.com/developers/docs/resources/channel#group-dm-add-recipient>
+data GroupDMAddRecipientOpts = GroupDMAddRecipientOpts
+ { -- | The id of the user to add to the Group DM
+ groupDMAddRecipientUserToAdd :: UserId
+ , -- | The nickname given to the user being added
+ groupDMAddRecipientUserToAddNickName :: T.Text
+ , -- | Access token of the user. That user must have granted your app the
+ -- @gdm.join@ scope.
+ groupDMAddRecipientGDMJoinAccessToken :: T.Text
+ } deriving (Show, Read, Eq, Ord)
+
+-- | Options for `StartThreadFromMessage` request
+data StartThreadOpts = StartThreadOpts
+ { -- | Name of the thread
+ startThreadName :: T.Text
+ , -- | Period of innactivity after which the thread gets archived in minutes.
+ --
+ -- Can be one of 60, 1440, 4320, 10080
+ startThreadAutoArchive :: Maybe Integer
+ , -- | Amount of seconds a user has to wait before sending another message
+ -- (0-21600)
+ startThreadRateLimit :: Maybe Integer
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON StartThreadOpts where
+ toJSON StartThreadOpts{..} = objectFromMaybes
+ [ "name" .== startThreadName
+ , "auto_archive_duration" .=? startThreadAutoArchive
+ , "rate_limit_per_user" .=? startThreadRateLimit
+ ]
+
+-- | Options for `StartThreadNoMessage` request
+data StartThreadNoMessageOpts = StartThreadNoMessageOpts
+ { -- | Base options for the thread
+ startThreadNoMessageBaseOpts :: StartThreadOpts
+ , -- | The type of thread to create
+ --
+ -- Can be @10@, @11@, or @12@. See
+ -- <https://discord.com/developers/docs/resources/channel#channel-object-channel-types>
+ startThreadNoMessageType :: Integer
+ , -- | Whether non-moderators can add other non-moderators to a thread. Only
+ -- available when creating a private thread.
+ startThreadNoMessageInvitable :: Maybe Bool
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON StartThreadNoMessageOpts where
+ toJSON StartThreadNoMessageOpts{..} = objectFromMaybes
+ [ "name" .== startThreadName startThreadNoMessageBaseOpts
+ , "auto_archive_duration" .=? startThreadAutoArchive startThreadNoMessageBaseOpts
+ , "rate_limit_per_user" .=? startThreadRateLimit startThreadNoMessageBaseOpts
+ , "type" .== startThreadNoMessageType
+ , "invitable" .=? startThreadNoMessageInvitable
+ ]
+
+-- | Result type of `ListJoinedPrivateArchivedThreads`,
+-- `ListPrivateArchivedThreads` and `ListPublicArchivedThreads`
+data ListThreads = ListThreads
+ { -- | The returned threads
+ listThreadsThreads :: [Channel]
+ , -- | A thread member object for each returned thread the current user has
+ -- joined
+ listThreadsMembers :: [ThreadMember]
+ , -- | Whether there is more data to retrieve
+ listThreadsHasMore :: Bool
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON ListThreads where
+ toJSON ListThreads{..} = object
+ [ ("threads", toJSON listThreadsThreads)
+ , ("members", toJSON listThreadsMembers)
+ , ("has_more", toJSON listThreadsHasMore)
+ ]
+
+instance FromJSON ListThreads where
+ parseJSON = withObject "ListThreads" $ \o ->
+ ListThreads <$> o .: "threads"
+ <*> o .: "members"
+ <*> o .: "has_more"
+
+channelMajorRoute :: ChannelRequest a -> String
+channelMajorRoute c = case c of
+ (GetChannel chan) -> "get_chan " <> show chan
+ (ModifyChannel chan _) -> "mod_chan " <> show chan
+ (DeleteChannel chan) -> "mod_chan " <> show chan
+ (GetChannelMessages chan _) -> "msg " <> show chan
+ (GetChannelMessage (chan, _)) -> "get_msg " <> show chan
+ (CreateMessage chan _) -> "msg " <> show chan
+ (CreateMessageDetailed chan _) -> "msg " <> show chan
+ (CreateReaction (chan, _) _) -> "add_react " <> show chan
+ (DeleteOwnReaction (chan, _) _) -> "react " <> show chan
+ (DeleteUserReaction (chan, _) _ _) -> "react " <> show chan
+ (DeleteSingleReaction (chan, _) _) -> "react " <> show chan
+ (GetReactions (chan, _) _ _) -> "react " <> show chan
+ (DeleteAllReactions (chan, _)) -> "react " <> show chan
+ (EditMessage (chan, _) _) -> "get_msg " <> show chan
+ (DeleteMessage (chan, _)) -> "get_msg " <> show chan
+ (BulkDeleteMessage (chan, _)) -> "del_msgs " <> show chan
+ (EditChannelPermissions chan _ _) -> "perms " <> show chan
+ (GetChannelInvites chan) -> "invites " <> show chan
+ (CreateChannelInvite chan _) -> "invites " <> show chan
+ (DeleteChannelPermission chan _) -> "perms " <> show chan
+ (TriggerTypingIndicator chan) -> "tti " <> show chan
+ (GetPinnedMessages chan) -> "pins " <> show chan
+ (AddPinnedMessage (chan, _)) -> "pin " <> show chan
+ (DeletePinnedMessage (chan, _)) -> "pin " <> show chan
+ (GroupDMAddRecipient chan _) -> "groupdm " <> show chan
+ (GroupDMRemoveRecipient chan _) -> "groupdm " <> show chan
+ (StartThreadFromMessage chan _ _) -> "thread " <> show chan
+ (StartThreadNoMessage chan _) -> "thread " <> show chan
+ (JoinThread chan) -> "thread " <> show chan
+ (AddThreadMember chan _) -> "thread " <> show chan
+ (LeaveThread chan) -> "thread " <> show chan
+ (RemoveThreadMember chan _) -> "thread " <> show chan
+ (GetThreadMember chan _) -> "thread " <> show chan
+ (ListThreadMembers chan) -> "thread " <> show chan
+ (ListPublicArchivedThreads chan _) -> "thread " <> show chan
+ (ListPrivateArchivedThreads chan _) -> "thread " <> show chan
+ (ListJoinedPrivateArchivedThreads chan _) -> "thread " <> show chan
+
+cleanupEmoji :: T.Text -> T.Text
+cleanupEmoji emoji =
+ let noAngles = T.replace "<" "" (T.replace ">" "" emoji)
+ byName = T.pack <$> unicodeByName (T.unpack (T.replace ":" "" emoji))
+ in case (byName, T.stripPrefix ":" noAngles) of
+ (Just e, _) -> e
+ (_, Just a) -> "custom:" <> a
+ (_, Nothing) -> noAngles
+
+channels :: R.Url 'R.Https
+channels = baseUrl /: "channels"
+
+channelJsonRequest :: ChannelRequest r -> JsonRequest
+channelJsonRequest c = case c of
+ (GetChannel chan) ->
+ Get (channels /~ chan) mempty
+
+ (ModifyChannel chan patch) ->
+ Patch (channels /~ chan) (pure (R.ReqBodyJson patch)) mempty
+
+ (DeleteChannel chan) ->
+ Delete (channels /~ chan) mempty
+
+ (GetChannelMessages chan (n,timing)) ->
+ let n' = max 1 (min 100 n)
+ options = "limit" R.=: n' <> messageTimingToQuery timing
+ in Get (channels /~ chan /: "messages") options
+
+ (GetChannelMessage (chan, msg)) ->
+ Get (channels /~ chan /: "messages" /~ msg) mempty
+
+ (CreateMessage chan msg) ->
+ let content = ["content" .= msg]
+ body = pure $ R.ReqBodyJson $ object content
+ in Post (channels /~ chan /: "messages") body mempty
+
+ (CreateMessageDetailed chan msgOpts) ->
+ let fileUpload = messageDetailedFile msgOpts
+ filePart =
+ ( case fileUpload of
+ Nothing -> []
+ Just f ->
+ [ partFileRequestBody
+ "file"
+ (T.unpack $ fst f)
+ (RequestBodyBS $ snd f)
+ ]
+ )
+ ++ join (maybe [] (maybeEmbed . Just <$>) (messageDetailedEmbeds msgOpts))
+
+ payloadData = objectFromMaybes $
+ [ "content" .== messageDetailedContent msgOpts
+ , "tts" .== messageDetailedTTS msgOpts ] ++
+ [ "embeds" .=? ((createEmbed <$>) <$> messageDetailedEmbeds msgOpts)
+ , "allowed_mentions" .=? messageDetailedAllowedMentions msgOpts
+ , "message_reference" .=? messageDetailedReference msgOpts
+ , "components" .=? messageDetailedComponents msgOpts
+ , "sticker_ids" .=? messageDetailedStickerIds msgOpts
+ ]
+ payloadPart = partBS "payload_json" $ BL.toStrict $ encode payloadData
+
+ body = R.reqBodyMultipart (payloadPart : filePart)
+ in Post (channels /~ chan /: "messages") body mempty
+
+ (CreateReaction (chan, msgid) emoji) ->
+ let e = cleanupEmoji emoji
+ in Put (channels /~ chan /: "messages" /~ msgid /: "reactions" /: e /: "@me" )
+ R.NoReqBody mempty
+
+ (DeleteOwnReaction (chan, msgid) emoji) ->
+ let e = cleanupEmoji emoji
+ in Delete (channels /~ chan /: "messages" /~ msgid /: "reactions" /: e /: "@me" ) mempty
+
+ (DeleteUserReaction (chan, msgid) uID emoji) ->
+ let e = cleanupEmoji emoji
+ in Delete (channels /~ chan /: "messages" /~ msgid /: "reactions" /: e /~ uID ) mempty
+
+ (DeleteSingleReaction (chan, msgid) emoji) ->
+ let e = cleanupEmoji emoji
+ in Delete (channels /~ chan /: "messages" /~ msgid /: "reactions" /: e) mempty
+
+ (GetReactions (chan, msgid) emoji (n, timing)) ->
+ let e = cleanupEmoji emoji
+ n' = max 1 (min 100 n)
+ options = "limit" R.=: n' <> reactionTimingToQuery timing
+ in Get (channels /~ chan /: "messages" /~ msgid /: "reactions" /: e) options
+
+ (DeleteAllReactions (chan, msgid)) ->
+ Delete (channels /~ chan /: "messages" /~ msgid /: "reactions" ) mempty
+
+ -- copied from CreateMessageDetailed, should be outsourced to function probably
+ (EditMessage (chan, msg) msgOpts) ->
+ let fileUpload = messageDetailedFile msgOpts
+ filePart =
+ ( case fileUpload of
+ Nothing -> []
+ Just f ->
+ [ partFileRequestBody
+ "file"
+ (T.unpack $ fst f)
+ (RequestBodyBS $ snd f)
+ ]
+ )
+ ++ join (maybe [] (maybeEmbed . Just <$>) (messageDetailedEmbeds msgOpts))
+
+ payloadData = objectFromMaybes $
+ [ "content" .== messageDetailedContent msgOpts
+ , "tts" .== messageDetailedTTS msgOpts ] ++
+ [ "embeds" .=? ((createEmbed <$>) <$> messageDetailedEmbeds msgOpts)
+ , "allowed_mentions" .=? messageDetailedAllowedMentions msgOpts
+ , "message_reference" .=? messageDetailedReference msgOpts
+ , "components" .=? messageDetailedComponents msgOpts
+ , "sticker_ids" .=? messageDetailedStickerIds msgOpts
+ ]
+ payloadPart = partBS "payload_json" $ BL.toStrict $ encode payloadData
+
+ body = R.reqBodyMultipart (payloadPart : filePart)
+ in Patch (channels /~ chan /: "messages" /~ msg) body mempty
+
+ (DeleteMessage (chan, msg)) ->
+ Delete (channels /~ chan /: "messages" /~ msg) mempty
+
+ (BulkDeleteMessage (chan, msgs)) ->
+ let body = pure . R.ReqBodyJson $ object ["messages" .= msgs]
+ in Post (channels /~ chan /: "messages" /: "bulk-delete") body mempty
+
+ (EditChannelPermissions chan overwriteId (ChannelPermissionsOpts a d)) ->
+ let body = R.ReqBodyJson $ object [("type", toJSON (either (const 0) (const 1) overwriteId :: Int))
+ ,("allow", toJSON a)
+ ,("deny", toJSON d)]
+ in Put (channels /~ chan /: "permissions" /~ either unId unId overwriteId) body mempty
+
+ (GetChannelInvites chan) ->
+ Get (channels /~ chan /: "invites") mempty
+
+ (CreateChannelInvite chan patch) ->
+ Post (channels /~ chan /: "invites") (pure (R.ReqBodyJson patch)) mempty
+
+ (DeleteChannelPermission chan overwriteId) ->
+ Delete (channels /~ chan /: "permissions" /~ either unId unId overwriteId) mempty
+
+ (TriggerTypingIndicator chan) ->
+ Post (channels /~ chan /: "typing") (pure R.NoReqBody) mempty
+
+ (GetPinnedMessages chan) ->
+ Get (channels /~ chan /: "pins") mempty
+
+ (AddPinnedMessage (chan, msg)) ->
+ Put (channels /~ chan /: "pins" /~ msg) R.NoReqBody mempty
+
+ (DeletePinnedMessage (chan, msg)) ->
+ Delete (channels /~ chan /: "pins" /~ msg) mempty
+
+ (GroupDMAddRecipient chan (GroupDMAddRecipientOpts uid nick tok)) ->
+ Put (channels /~ chan /~ chan /: "recipients" /~ uid)
+ (R.ReqBodyJson (object [ ("access_token", toJSON tok)
+ , ("nick", toJSON nick)]))
+ mempty
+
+ (GroupDMRemoveRecipient chan userid) ->
+ Delete (channels /~ chan /~ chan /: "recipients" /~ userid) mempty
+
+ (StartThreadFromMessage chan mid sto) ->
+ Post (channels /~ chan /: "messages" /~ mid /: "threads")
+ (pure $ R.ReqBodyJson $ toJSON sto)
+ mempty
+
+ (StartThreadNoMessage chan sto) ->
+ Post (channels /~ chan /: "messages" /: "threads")
+ (pure $ R.ReqBodyJson $ toJSON sto)
+ mempty
+
+ (JoinThread chan) ->
+ Put (channels /~ chan /: "thread-members" /: "@me")
+ R.NoReqBody mempty
+
+ (AddThreadMember chan uid) ->
+ Put (channels /~ chan /: "thread-members" /~ uid)
+ R.NoReqBody mempty
+
+ (LeaveThread chan) ->
+ Delete (channels /~ chan /: "thread-members" /: "@me")
+ mempty
+
+ (RemoveThreadMember chan uid) ->
+ Delete (channels /~ chan /: "thread-members" /~ uid)
+ mempty
+
+ (GetThreadMember chan uid) ->
+ Get (channels /~ chan /: "thread-members" /~ uid)
+ mempty
+
+ (ListThreadMembers chan) ->
+ Get (channels /~ chan /: "thread-members")
+ mempty
+
+ (ListPublicArchivedThreads chan (time, lim)) ->
+ Get (channels /~ chan /: "threads" /: "archived" /: "public")
+ (maybe mempty ("limit" R.=:) lim <> maybe mempty ("before" R.=:) time)
+
+ (ListPrivateArchivedThreads chan (time, lim)) ->
+ Get (channels /~ chan /: "threads" /: "archived" /: "private")
+ (maybe mempty ("limit" R.=:) lim <> maybe mempty ("before" R.=:) time)
+
+ (ListJoinedPrivateArchivedThreads chan (time, lim)) ->
+ Get (channels /~ chan /: "users" /: "@me" /: "threads" /: "archived" /: "private")
+ (maybe mempty ("limit" R.=:) lim <> maybe mempty ("before" R.=:) time)
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/Emoji.hs b/deps/discord-haskell/src/Discord/Internal/Rest/Emoji.hs
new file mode 100644
index 0000000..2a52171
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/Emoji.hs
@@ -0,0 +1,201 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | Provides actions for Channel API interactions
+module Discord.Internal.Rest.Emoji
+ ( EmojiRequest (..),
+ ModifyGuildEmojiOpts (..),
+ parseEmojiImage,
+ parseStickerImage,
+ StickerRequest (..),
+ CreateGuildStickerOpts (..),
+ EditGuildStickerOpts (..)
+ )
+where
+
+import Data.Aeson
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Base64 as B64
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import Discord.Internal.Rest.Prelude
+import Discord.Internal.Types
+import Network.HTTP.Req ((/:), (/~))
+import qualified Network.HTTP.Req as R
+
+instance Request (EmojiRequest a) where
+ majorRoute = emojiMajorRoute
+ jsonRequest = emojiJsonRequest
+
+-- | Data constructor for requests. See <https://discord.com/developers/docs/resources/ API>
+data EmojiRequest a where
+ -- | List of emoji objects for the given guild. Requires MANAGE_EMOJIS permission.
+ ListGuildEmojis :: GuildId -> EmojiRequest [Emoji]
+ -- | Emoji object for the given guild and emoji ID
+ GetGuildEmoji :: GuildId -> EmojiId -> EmojiRequest Emoji
+ -- | Create a new guild emoji (static&animated). Requires MANAGE_EMOJIS permission.
+ CreateGuildEmoji :: GuildId -> T.Text -> Base64Image Emoji -> EmojiRequest Emoji
+ -- | Requires MANAGE_EMOJIS permission
+ ModifyGuildEmoji :: GuildId -> EmojiId -> ModifyGuildEmojiOpts -> EmojiRequest Emoji
+ -- | Requires MANAGE_EMOJIS permission
+ DeleteGuildEmoji :: GuildId -> EmojiId -> EmojiRequest ()
+
+data ModifyGuildEmojiOpts = ModifyGuildEmojiOpts
+ { modifyGuildEmojiName :: T.Text,
+ modifyGuildEmojiRoles :: [RoleId]
+ }
+ deriving (Show, Read, Eq, Ord)
+
+instance ToJSON ModifyGuildEmojiOpts where
+ toJSON (ModifyGuildEmojiOpts name roles) =
+ object ["name" .= name, "roles" .= roles]
+
+
+-- | @parseEmojiImage bs@ will attempt to convert the given image bytestring @bs@
+-- to the base64 format expected by the Discord API. It may return Left with an
+-- error reason if either the bytestring is too large, or if the image format
+-- could not be predetermined from the opening few bytes. This function does
+-- /not/ validate the rest of the image, nor check that its dimensions are
+-- 128x128 as required by Discord. This is up to the library user to check.
+--
+-- This function accepts all file types accepted by 'getMimeType'.
+parseEmojiImage :: B.ByteString -> Either T.Text (Base64Image Emoji)
+parseEmojiImage bs
+ | B.length bs > 256000 = Left "Cannot create emoji - File is larger than 256kb"
+ | Just mime <- getMimeType bs = Right (Base64Image mime (TE.decodeUtf8 (B64.encode bs)))
+ | otherwise = Left "Unsupported image format provided"
+
+emojiMajorRoute :: EmojiRequest a -> String
+emojiMajorRoute c = case c of
+ (ListGuildEmojis g) -> "emoji " <> show g
+ (GetGuildEmoji g _) -> "emoji " <> show g
+ (CreateGuildEmoji g _ _) -> "emoji " <> show g
+ (ModifyGuildEmoji g _ _) -> "emoji " <> show g
+ (DeleteGuildEmoji g _) -> "emoji " <> show g
+
+guilds :: R.Url 'R.Https
+guilds = baseUrl /: "guilds"
+
+emojiJsonRequest :: EmojiRequest r -> JsonRequest
+emojiJsonRequest c = case c of
+ (ListGuildEmojis g) -> Get (guilds /~ g /: "emojis") mempty
+ (GetGuildEmoji g e) -> Get (guilds /~ g /: "emojis" /~ e) mempty
+ (CreateGuildEmoji g name b64im) ->
+ Post
+ (guilds /~ g /: "emojis")
+ ( pure
+ ( R.ReqBodyJson
+ ( object
+ [ "name" .= name,
+ "image" .= b64im
+ -- todo , "roles" .= ...
+ ]
+ )
+ )
+ )
+ mempty
+ (ModifyGuildEmoji g e o) ->
+ Patch
+ (guilds /~ g /: "emojis" /~ e)
+ (pure (R.ReqBodyJson o))
+ mempty
+ (DeleteGuildEmoji g e) -> Delete (guilds /~ g /: "emojis" /~ e) mempty
+
+-- | @parseStickerImage bs@ accepts PNG, APNG, or Lottie JSON bytestring @bs@ and
+-- will attempt to convert it to the base64 format expected by the Discord API.
+-- It may return Left with an error reason if the image format is unexpected.
+-- This function does /not/ validate the contents of the image, this is up to
+-- the library user to check.
+parseStickerImage :: B.ByteString -> Either T.Text (Base64Image Sticker)
+parseStickerImage bs
+ | B.length bs > 512000
+ = Left "Cannot create sticker - File is larger than 512kb"
+ | Just "image/png" <- getMimeType bs
+ = Right (Base64Image "image/png" (TE.decodeUtf8 (B64.encode bs)))
+ | not (B.null bs) && B.head bs == 0x7b -- '{'
+ = Right (Base64Image "application/json" (TE.decodeUtf8 (B64.encode bs)))
+ | otherwise
+ = Left "Unsupported image format provided"
+
+-- | Options for `CreateGuildSticker`
+data CreateGuildStickerOpts = CreateGuildStickerOpts
+ { guildStickerName :: T.Text,
+ guildStickerDescription :: T.Text,
+ guildStickerTags :: [T.Text],
+ guildStickerFile :: Base64Image Sticker
+ }
+ deriving (Show, Read, Eq, Ord)
+
+instance ToJSON CreateGuildStickerOpts where
+ toJSON (CreateGuildStickerOpts name desc tags b64im) =
+ object
+ [ ("name", toJSON name),
+ ("description", toJSON desc),
+ ("tags", toJSON $ T.intercalate "," tags),
+ ("file", toJSON b64im)
+ ]
+
+-- | Options for `ModifyGuildSticker`
+data EditGuildStickerOpts = EditGuildStickerOpts
+ { editGuildStickerName :: Maybe T.Text,
+ editGuildStickerDescription :: Maybe T.Text,
+ editGuildStickerTags :: Maybe [T.Text]
+ }
+ deriving (Show, Read, Eq, Ord)
+
+instance ToJSON EditGuildStickerOpts where
+ toJSON EditGuildStickerOpts {..} =
+ objectFromMaybes
+ [ "name" .=? editGuildStickerName,
+ "description" .=? editGuildStickerDescription,
+ "tags" .=? fmap (T.intercalate ",") editGuildStickerTags
+ ]
+
+instance Request (StickerRequest a) where
+ majorRoute = stickerMajorRoute
+ jsonRequest = stickerJsonRequest
+
+-- | Data constructor for requests. See <https://discord.com/developers/docs/resources/ API>
+--
+-- Be warned that these are untested due to not having a spare server with
+-- boosts. Functionality is at your own risk.
+data StickerRequest a where
+ -- | Returns a sticker object for the given sticker ID.
+ GetSticker :: StickerId -> StickerRequest Sticker
+ -- | Returns the list of sticker packs available to Nitro subscribers.
+ ListNitroStickerPacks :: StickerRequest [StickerPack]
+ -- | Returns an array of sticker objects for the given guild.
+ ListGuildStickers :: GuildId -> StickerRequest [Sticker]
+ -- | Returns a sticker object for the given guild and sticker ID.
+ GetGuildSticker :: GuildId -> StickerId -> StickerRequest Sticker
+ -- | Create a new sticker for the guild.
+ CreateGuildSticker :: GuildId -> CreateGuildStickerOpts -> StickerRequest Sticker
+ -- | Modify a sticker for a guild.
+ ModifyGuildSticker :: GuildId -> StickerId -> EditGuildStickerOpts -> StickerRequest Sticker
+ -- | Delete a guild sticker
+ DeleteGuildSticker :: GuildId -> StickerId -> StickerRequest ()
+
+stickerMajorRoute :: StickerRequest a -> String
+stickerMajorRoute = \case
+ GetSticker gid -> "sticker " <> show gid
+ ListNitroStickerPacks -> "sticker"
+ ListGuildStickers gid -> "sticker " <> show gid
+ GetGuildSticker gid _ -> "sticker " <> show gid
+ CreateGuildSticker gid _ -> "sticker " <> show gid
+ ModifyGuildSticker gid _ _ -> "sticker " <> show gid
+ DeleteGuildSticker gid _ -> "sticker " <> show gid
+
+stickerJsonRequest :: StickerRequest a -> JsonRequest
+stickerJsonRequest = \case
+ GetSticker gid -> Get (baseUrl /: "stickers" /~ gid) mempty
+ ListNitroStickerPacks -> Get (baseUrl /: "sticker-packs") mempty
+ ListGuildStickers gid -> Get (stickersGuild gid) mempty
+ GetGuildSticker gid sid -> Get (stickersGuild gid /~ sid) mempty
+ CreateGuildSticker gid cgso -> Post (stickersGuild gid) (pure $ R.ReqBodyJson $ toJSON cgso) mempty
+ ModifyGuildSticker gid sid egso -> Patch (stickersGuild gid /~ sid) (pure $ R.ReqBodyJson egso) mempty
+ DeleteGuildSticker gid sid -> Delete (stickersGuild gid /~ sid) mempty
+ where
+ stickersGuild gid = baseUrl /: "guilds" /~ gid /: "stickers"
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/Guild.hs b/deps/discord-haskell/src/Discord/Internal/Rest/Guild.hs
new file mode 100644
index 0000000..a0cb3aa
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/Guild.hs
@@ -0,0 +1,468 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Provides actions for Channel API interactions
+module Discord.Internal.Rest.Guild
+ ( GuildRequest(..)
+ , CreateGuildChannelOpts(..)
+ , ModifyGuildOpts(..)
+ , AddGuildMemberOpts(..)
+ , ModifyGuildMemberOpts(..)
+ , GuildMembersTiming(..)
+ , CreateGuildBanOpts(..)
+ , ModifyGuildRoleOpts(..)
+ , CreateGuildIntegrationOpts(..)
+ , ModifyGuildIntegrationOpts(..)
+ ) where
+
+
+import Data.Aeson
+import Network.HTTP.Req ((/:), (/~))
+import qualified Network.HTTP.Req as R
+import qualified Data.Text as T
+
+import Discord.Internal.Rest.Prelude
+import Discord.Internal.Types
+import Data.Default (Default(..))
+
+instance Request (GuildRequest a) where
+ majorRoute = guildMajorRoute
+ jsonRequest = guildJsonRequest
+
+-- | Data constructor for requests. See <https://discord.com/developers/docs/resources/ API>
+data GuildRequest a where
+ -- -- Creating a guild with the API is annoying. Do it manually.
+ -- -- https://discord.com/developers/docs/resources/guild#create-guild
+
+ -- | Returns the new 'Guild' object for the given id
+ GetGuild :: GuildId -> GuildRequest Guild
+ -- | Modify a guild's settings. Returns the updated 'Guild' object on success. Fires a
+ -- Guild Update 'Event'.
+ ModifyGuild :: GuildId -> ModifyGuildOpts -> GuildRequest Guild
+ -- | Delete a guild permanently. User must be owner. Fires a Guild Delete 'Event'.
+ DeleteGuild :: GuildId -> GuildRequest ()
+ -- | Returns a list of guild 'Channel' objects
+ GetGuildChannels :: GuildId -> GuildRequest [Channel]
+ -- | Create a new 'Channel' object for the guild. Requires 'MANAGE_CHANNELS'
+ -- permission. Returns the new 'Channel' object on success. Fires a Channel Create
+ -- 'Event'
+ CreateGuildChannel :: GuildId -> T.Text -> [Overwrite] -> CreateGuildChannelOpts -> GuildRequest Channel
+ -- | Modify the positions of a set of channel objects for the guild. Requires
+ -- 'MANAGE_CHANNELS' permission. Returns a list of all of the guild's 'Channel'
+ -- objects on success. Fires multiple Channel Update 'Event's.
+ ModifyGuildChannelPositions :: GuildId -> [(ChannelId,Int)] -> GuildRequest [Channel]
+ -- | Returns a guild 'Member' object for the specified user
+ GetGuildMember :: GuildId -> UserId -> GuildRequest GuildMember
+ -- | Returns a list of guild 'Member' objects that are members of the guild.
+ ListGuildMembers :: GuildId -> GuildMembersTiming -> GuildRequest [GuildMember]
+ -- | Adds a user to the guild, provided you have a valid oauth2 access token
+ -- for the user with the guilds.join scope. Returns the guild 'Member' as the body.
+ -- Fires a Guild Member Add 'Event'. Requires the bot to have the
+ -- CREATE_INSTANT_INVITE permission.
+ AddGuildMember :: GuildId -> UserId -> AddGuildMemberOpts
+ -> GuildRequest ()
+ -- | Modify attributes of a guild 'Member'. Fires a Guild Member Update 'Event'.
+ ModifyGuildMember :: GuildId -> UserId -> ModifyGuildMemberOpts -> GuildRequest GuildMember
+ -- | Modify the nickname of the current user
+ ModifyCurrentUserNick :: GuildId -> T.Text -> GuildRequest ()
+ -- | Add a member to a guild role. Requires 'MANAGE_ROLES' permission.
+ AddGuildMemberRole :: GuildId -> UserId -> RoleId -> GuildRequest ()
+ -- | Remove a member from a guild role. Requires 'MANAGE_ROLES' permission.
+ RemoveGuildMemberRole :: GuildId -> UserId -> RoleId -> GuildRequest ()
+ -- | Remove a member from a guild. Requires 'KICK_MEMBER' permission. Fires a
+ -- Guild Member Remove 'Event'.
+ RemoveGuildMember :: GuildId -> UserId -> GuildRequest ()
+ -- | Returns a list of 'Ban' objects for users that are banned from this guild. Requires the
+ -- 'BAN_MEMBERS' permission
+ GetGuildBans :: GuildId -> GuildRequest [GuildBan]
+ -- | Returns a 'Ban' object for the user banned from this guild. Requires the
+ -- 'BAN_MEMBERS' permission
+ GetGuildBan :: GuildId -> UserId -> GuildRequest GuildBan
+ -- | Create a guild ban, and optionally Delete previous messages sent by the banned
+ -- user. Requires the 'BAN_MEMBERS' permission. Fires a Guild Ban Add 'Event'.
+ CreateGuildBan :: GuildId -> UserId -> CreateGuildBanOpts -> GuildRequest ()
+ -- | Remove the ban for a user. Requires the 'BAN_MEMBERS' permissions.
+ -- Fires a Guild Ban Remove 'Event'.
+ RemoveGuildBan :: GuildId -> UserId -> GuildRequest ()
+ -- | Returns a list of 'Role' objects for the guild. Requires the 'MANAGE_ROLES'
+ -- permission
+ GetGuildRoles :: GuildId -> GuildRequest [Role]
+ -- | Create a new 'Role' for the guild. Requires the 'MANAGE_ROLES' permission.
+ -- Returns the new role object on success. Fires a Guild Role Create 'Event'.
+ CreateGuildRole :: GuildId -> ModifyGuildRoleOpts -> GuildRequest Role
+ -- | Modify the positions of a set of role objects for the guild. Requires the
+ -- 'MANAGE_ROLES' permission. Returns a list of all of the guild's 'Role' objects
+ -- on success. Fires multiple Guild Role Update 'Event's.
+ ModifyGuildRolePositions :: GuildId -> [(RoleId, Integer)] -> GuildRequest [Role]
+ -- | Modify a guild role. Requires the 'MANAGE_ROLES' permission. Returns the
+ -- updated 'Role' on success. Fires a Guild Role Update 'Event's.
+ ModifyGuildRole :: GuildId -> RoleId -> ModifyGuildRoleOpts -> GuildRequest Role
+ -- | Delete a guild role. Requires the 'MANAGE_ROLES' permission. Fires a Guild Role
+ -- Delete 'Event'.
+ DeleteGuildRole :: GuildId -> RoleId -> GuildRequest ()
+ -- | Returns an object with one 'pruned' key indicating the number of members
+ -- that would be removed in a prune operation. Requires the 'KICK_MEMBERS'
+ -- permission.
+ GetGuildPruneCount :: GuildId -> Integer -> GuildRequest Object
+ -- | Begin a prune operation. Requires the 'KICK_MEMBERS' permission. Returns an
+ -- object with one 'pruned' key indicating the number of members that were removed
+ -- in the prune operation. Fires multiple Guild Member Remove 'Events'.
+ BeginGuildPrune :: GuildId -> Integer -> GuildRequest Object
+ -- | Returns a list of 'VoiceRegion' objects for the guild. Unlike the similar /voice
+ -- route, this returns VIP servers when the guild is VIP-enabled.
+ GetGuildVoiceRegions :: GuildId -> GuildRequest [VoiceRegion]
+ -- | Returns a list of 'Invite' objects for the guild. Requires the 'MANAGE_GUILD'
+ -- permission.
+ GetGuildInvites :: GuildId -> GuildRequest [Invite]
+ -- | Return a list of 'Integration' objects for the guild. Requires the 'MANAGE_GUILD'
+ -- permission.
+ GetGuildIntegrations :: GuildId -> GuildRequest [Integration]
+ -- | Attach an 'Integration' object from the current user to the guild. Requires the
+ -- 'MANAGE_GUILD' permission. Fires a Guild Integrations Update 'Event'.
+ CreateGuildIntegration :: GuildId -> IntegrationId -> CreateGuildIntegrationOpts -> GuildRequest ()
+ -- | Modify the behavior and settings of a 'Integration' object for the guild.
+ -- Requires the 'MANAGE_GUILD' permission. Fires a Guild Integrations Update 'Event'.
+ ModifyGuildIntegration :: GuildId -> IntegrationId -> ModifyGuildIntegrationOpts
+ -> GuildRequest ()
+ -- | Delete the attached 'Integration' object for the guild. Requires the
+ -- 'MANAGE_GUILD' permission. Fires a Guild Integrations Update 'Event'.
+ DeleteGuildIntegration :: GuildId -> IntegrationId -> GuildRequest ()
+ -- | Sync an 'Integration'. Requires the 'MANAGE_GUILD' permission.
+ SyncGuildIntegration :: GuildId -> IntegrationId -> GuildRequest ()
+ -- | Returns the 'GuildWidget' object. Requires the 'MANAGE_GUILD' permission.
+ GetGuildWidget :: GuildId -> GuildRequest GuildWidget
+ -- | Modify a 'GuildWidget' object for the guild. All attributes may be passed in with
+ -- JSON and modified. Requires the 'MANAGE_GUILD' permission. Returns the updated
+ -- 'GuildWidget' object.
+ ModifyGuildWidget :: GuildId -> GuildWidget -> GuildRequest GuildWidget
+ -- | Vanity URL
+ GetGuildVanityURL :: GuildId -> GuildRequest T.Text
+
+-- | Options for `ModifyGuildIntegration`
+data ModifyGuildIntegrationOpts = ModifyGuildIntegrationOpts
+ { modifyGuildIntegrationOptsExpireBehavior :: Integer
+ , modifyGuildIntegrationOptsExpireGraceSeconds :: Integer
+ , modifyGuildIntegrationOptsEmoticonsEnabled :: Bool
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON ModifyGuildIntegrationOpts where
+ toJSON ModifyGuildIntegrationOpts{..} = objectFromMaybes
+ [ "expire_grace_period" .== modifyGuildIntegrationOptsExpireGraceSeconds
+ , "expire_behavior" .== modifyGuildIntegrationOptsExpireBehavior
+ , "enable_emoticons" .== modifyGuildIntegrationOptsEmoticonsEnabled ]
+
+-- | Options for `CreateGuildIntegration`
+newtype CreateGuildIntegrationOpts = CreateGuildIntegrationOpts
+ { createGuildIntegrationOptsType :: T.Text
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON CreateGuildIntegrationOpts where
+ toJSON CreateGuildIntegrationOpts{..} = objectFromMaybes
+ ["type" .== createGuildIntegrationOptsType]
+
+-- | Options for `CreateGuildBan`
+data CreateGuildBanOpts = CreateGuildBanOpts
+ { createGuildBanOptsDeleteLastNMessages :: Maybe Int
+ , createGuildBanOptsReason :: Maybe T.Text
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON CreateGuildBanOpts where
+ toJSON CreateGuildBanOpts{..} = objectFromMaybes
+ [ "delete_message_days"
+ .=? createGuildBanOptsDeleteLastNMessages
+ , "reason" .=? createGuildBanOptsReason]
+
+-- | Options for `ModifyGuildRole`
+data ModifyGuildRoleOpts = ModifyGuildRoleOpts
+ { modifyGuildRoleOptsName :: Maybe T.Text
+ , modifyGuildRoleOptsPermissions :: Maybe RolePermissions
+ , modifyGuildRoleOptsColor :: Maybe DiscordColor
+ , modifyGuildRoleOptsSeparateSidebar :: Maybe Bool
+ , modifyGuildRoleOptsMentionable :: Maybe Bool
+ , modifyGuildRoleOptsIcon :: Maybe T.Text
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON ModifyGuildRoleOpts where
+ toJSON ModifyGuildRoleOpts{..} = objectFromMaybes
+ ["name" .=? modifyGuildRoleOptsName,
+ "permissions" .=? modifyGuildRoleOptsPermissions,
+ "color" .=? modifyGuildRoleOptsColor,
+ "hoist" .=? modifyGuildRoleOptsSeparateSidebar,
+ "mentionable" .=? modifyGuildRoleOptsMentionable,
+ "icon" .=? modifyGuildRoleOptsIcon]
+
+-- | Options for `AddGuildMember`
+data AddGuildMemberOpts = AddGuildMemberOpts
+ { addGuildMemberOptsAccessToken :: T.Text
+ , addGuildMemberOptsNickname :: Maybe T.Text
+ , addGuildMemberOptsRoles :: Maybe [RoleId]
+ , addGuildMemberOptsIsMuted :: Maybe Bool
+ , addGuildMemberOptsIsDeafened :: Maybe Bool
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON AddGuildMemberOpts where
+ toJSON AddGuildMemberOpts{..} = objectFromMaybes
+ ["access_token" .== addGuildMemberOptsAccessToken,
+ "nick" .=? addGuildMemberOptsNickname,
+ "roles" .=? addGuildMemberOptsRoles,
+ "mute" .=? addGuildMemberOptsIsMuted,
+ "deaf" .=? addGuildMemberOptsIsDeafened]
+
+-- | Options for `ModifyGuildMember`
+data ModifyGuildMemberOpts = ModifyGuildMemberOpts
+ { modifyGuildMemberOptsNickname :: Maybe T.Text
+ , modifyGuildMemberOptsRoles :: Maybe [RoleId]
+ , modifyGuildMemberOptsIsMuted :: Maybe Bool
+ , modifyGuildMemberOptsIsDeafened :: Maybe Bool
+ , modifyGuildMemberOptsMoveToChannel :: Maybe ChannelId
+ , modifyGuildMemberOptsTimeoutUntil :: Maybe (Maybe UTCTime) -- ^ If `Just Nothing`, the timeout will be removed.
+ } deriving (Show, Read, Eq, Ord)
+
+instance Default ModifyGuildMemberOpts where
+ def = ModifyGuildMemberOpts Nothing Nothing Nothing Nothing Nothing Nothing
+
+instance ToJSON ModifyGuildMemberOpts where
+ toJSON ModifyGuildMemberOpts{..} = objectFromMaybes
+ ["nick" .=? modifyGuildMemberOptsNickname,
+ "roles" .=? modifyGuildMemberOptsRoles,
+ "mute" .=? modifyGuildMemberOptsIsMuted,
+ "deaf" .=? modifyGuildMemberOptsIsDeafened,
+ "channel_id" .=? modifyGuildMemberOptsMoveToChannel,
+ "communication_disabled_until" .=? modifyGuildMemberOptsTimeoutUntil]
+
+-- | Options for `CreateGuildChannel`
+data CreateGuildChannelOpts
+ -- | Create a text channel
+ = CreateGuildChannelOptsText {
+ createGuildChannelOptsTopic :: Maybe T.Text
+ , createGuildChannelOptsUserMessageRateDelay :: Maybe Integer
+ , createGuildChannelOptsIsNSFW :: Maybe Bool
+ , createGuildChannelOptsCategoryId :: Maybe ChannelId }
+ -- | Create a voice channel
+ | CreateGuildChannelOptsVoice {
+ createGuildChannelOptsBitrate :: Maybe Integer
+ , createGuildChannelOptsMaxUsers :: Maybe Integer
+ , createGuildChannelOptsCategoryId :: Maybe ChannelId }
+ -- | Create a category
+ | CreateGuildChannelOptsCategory
+ deriving (Show, Read, Eq, Ord)
+
+-- | Converts a channel name, a list of permissions and other channel options into a JSON Value
+createChannelOptsToJSON :: T.Text -> [Overwrite] -> CreateGuildChannelOpts -> Value
+createChannelOptsToJSON name perms opts = objectFromMaybes optsJSON
+ where
+ optsJSON = case opts of
+ CreateGuildChannelOptsText{..} ->
+ ["name" .== String name
+ ,"type" .== Number 0
+ ,"permission_overwrites" .== perms
+ ,"topic" .=? createGuildChannelOptsTopic
+ ,"rate_limit_per_user" .=? createGuildChannelOptsUserMessageRateDelay
+ ,"nsfw" .=? createGuildChannelOptsIsNSFW
+ ,"parent_id" .=? createGuildChannelOptsCategoryId]
+ CreateGuildChannelOptsVoice{..} ->
+ ["name" .== String name
+ ,"type" .== Number 2
+ ,"permission_overwrites" .== perms
+ ,"bitrate" .=? createGuildChannelOptsBitrate
+ ,"user_limit" .=? createGuildChannelOptsMaxUsers
+ ,"parent_id" .=? createGuildChannelOptsCategoryId]
+ CreateGuildChannelOptsCategory ->
+ ["name" .== String name
+ ,"type" .== Number 4
+ ,"permission_overwrites" .== perms]
+
+
+-- | Options for `ModifyGuild`
+--
+-- See <https://discord.com/developers/docs/resources/guild#modify-guild>
+data ModifyGuildOpts = ModifyGuildOpts
+ { modifyGuildOptsName :: Maybe T.Text
+ , modifyGuildOptsAFKChannelId :: Maybe ChannelId
+ , modifyGuildOptsIcon :: Maybe T.Text
+ , modifyGuildOptsOwnerId :: Maybe UserId
+ -- Region
+ -- VerificationLevel
+ -- DefaultMessageNotification
+ -- ExplicitContentFilter
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON ModifyGuildOpts where
+ toJSON ModifyGuildOpts{..} = objectFromMaybes
+ ["name" .=? modifyGuildOptsName,
+ "afk_channel_id" .=? modifyGuildOptsAFKChannelId,
+ "icon" .=? modifyGuildOptsIcon,
+ "owner_id" .=? modifyGuildOptsOwnerId]
+
+data GuildMembersTiming = GuildMembersTiming
+ { guildMembersTimingLimit :: Maybe Int
+ , guildMembersTimingAfter :: Maybe UserId
+ } deriving (Show, Read, Eq, Ord)
+
+guildMembersTimingToQuery :: GuildMembersTiming -> R.Option 'R.Https
+guildMembersTimingToQuery (GuildMembersTiming mLimit mAfter) =
+ let limit = case mLimit of
+ Nothing -> mempty
+ Just lim -> "limit" R.=: lim
+ after = case mAfter of
+ Nothing -> mempty
+ Just aft -> "after" R.=: show aft
+ in limit <> after
+
+guildMajorRoute :: GuildRequest a -> String
+guildMajorRoute c = case c of
+ (GetGuild g) -> "guild " <> show g
+ (ModifyGuild g _) -> "guild " <> show g
+ (DeleteGuild g) -> "guild " <> show g
+ (GetGuildChannels g) -> "guild_chan " <> show g
+ (CreateGuildChannel g _ _ _) -> "guild_chan " <> show g
+ (ModifyGuildChannelPositions g _) -> "guild_chan " <> show g
+ (GetGuildMember g _) -> "guild_memb " <> show g
+ (ListGuildMembers g _) -> "guild_membs " <> show g
+ (AddGuildMember g _ _) -> "guild_membs " <> show g
+ (ModifyGuildMember g _ _) -> "guild_membs " <> show g
+ (ModifyCurrentUserNick g _) -> "guild_membs " <> show g
+ (AddGuildMemberRole g _ _) -> "guild_membs " <> show g
+ (RemoveGuildMemberRole g _ _) -> "guild_membs " <> show g
+ (RemoveGuildMember g _) -> "guild_membs " <> show g
+ (GetGuildBan g _) -> "guild_bans " <> show g
+ (GetGuildBans g) -> "guild_bans " <> show g
+ (CreateGuildBan g _ _) -> "guild_ban " <> show g
+ (RemoveGuildBan g _) -> "guild_ban " <> show g
+ (GetGuildRoles g) -> "guild_roles " <> show g
+ (CreateGuildRole g _) -> "guild_roles " <> show g
+ (ModifyGuildRolePositions g _) -> "guild_roles " <> show g
+ (ModifyGuildRole g _ _) -> "guild_role " <> show g
+ (DeleteGuildRole g _) -> "guild_role " <> show g
+ (GetGuildPruneCount g _) -> "guild_prune " <> show g
+ (BeginGuildPrune g _) -> "guild_prune " <> show g
+ (GetGuildVoiceRegions g) -> "guild_voice " <> show g
+ (GetGuildInvites g) -> "guild_invit " <> show g
+ (GetGuildIntegrations g) -> "guild_integ " <> show g
+ (CreateGuildIntegration g _ _) -> "guild_integ " <> show g
+ (ModifyGuildIntegration g _ _) -> "guild_intgr " <> show g
+ (DeleteGuildIntegration g _) -> "guild_intgr " <> show g
+ (SyncGuildIntegration g _) -> "guild_sync " <> show g
+ (GetGuildWidget g) -> "guild_widget " <> show g
+ (ModifyGuildWidget g _) -> "guild_widget " <> show g
+ (GetGuildVanityURL g) -> "guild " <> show g
+
+
+guilds :: R.Url 'R.Https
+guilds = baseUrl /: "guilds"
+
+guildJsonRequest :: GuildRequest r -> JsonRequest
+guildJsonRequest c = case c of
+ (GetGuild guild) ->
+ Get (guilds /~ guild) mempty
+
+ (ModifyGuild guild patch) ->
+ Patch (guilds /~ guild) (pure (R.ReqBodyJson patch)) mempty
+
+ (DeleteGuild guild) ->
+ Delete (guilds /~ guild) mempty
+
+ (GetGuildChannels guild) ->
+ Get (guilds /~ guild /: "channels") mempty
+
+ (CreateGuildChannel guild name perms patch) ->
+ Post (guilds /~ guild /: "channels")
+ (pure (R.ReqBodyJson (createChannelOptsToJSON name perms patch))) mempty
+
+ (ModifyGuildChannelPositions guild newlocs) ->
+ let patch = map (\(a, b) -> object [("id", toJSON a)
+ ,("position", toJSON b)]) newlocs
+ in Patch (guilds /~ guild /: "channels") (pure (R.ReqBodyJson patch)) mempty
+
+ (GetGuildMember guild member) ->
+ Get (guilds /~ guild /: "members" /~ member) mempty
+
+ (ListGuildMembers guild range) ->
+ Get (guilds /~ guild /: "members") (guildMembersTimingToQuery range)
+
+ (AddGuildMember guild user patch) ->
+ Put (guilds /~ guild /: "members" /~ user) (R.ReqBodyJson patch) mempty
+
+ (ModifyGuildMember guild member patch) ->
+ Patch (guilds /~ guild /: "members" /~ member) (pure (R.ReqBodyJson patch)) mempty
+
+ (ModifyCurrentUserNick guild name) ->
+ let patch = object ["nick" .= name]
+ in Patch (guilds /~ guild /: "members/@me/nick") (pure (R.ReqBodyJson patch)) mempty
+
+ (AddGuildMemberRole guild user role) ->
+ let body = R.ReqBodyJson (object [])
+ in Put (guilds /~ guild /: "members" /~ user /: "roles" /~ role) body mempty
+
+ (RemoveGuildMemberRole guild user role) ->
+ Delete (guilds /~ guild /: "members" /~ user /: "roles" /~ role) mempty
+
+ (RemoveGuildMember guild user) ->
+ Delete (guilds /~ guild /: "members" /~ user) mempty
+
+ (GetGuildBan guild user) -> Get (guilds /~ guild /: "bans" /~ user) mempty
+
+ (GetGuildBans guild) -> Get (guilds /~ guild /: "bans") mempty
+
+ (CreateGuildBan guild user patch) ->
+ Put (guilds /~ guild /: "bans" /~ user) (R.ReqBodyJson patch) mempty
+
+ (RemoveGuildBan guild ban) ->
+ Delete (guilds /~ guild /: "bans" /~ ban) mempty
+
+ (GetGuildRoles guild) ->
+ Get (guilds /~ guild /: "roles") mempty
+
+ (CreateGuildRole guild patch) ->
+ Post (guilds /~ guild /: "roles") (pure (R.ReqBodyJson patch)) mempty
+
+ (ModifyGuildRolePositions guild patch) ->
+ let body = map (\(role, pos) -> object ["id".=role, "position".=pos]) patch
+ in Patch (guilds /~ guild /: "roles") (pure (R.ReqBodyJson body)) mempty
+
+ (ModifyGuildRole guild role patch) ->
+ Patch (guilds /~ guild /: "roles" /~ role) (pure (R.ReqBodyJson patch)) mempty
+
+ (DeleteGuildRole guild role) ->
+ Delete (guilds /~ guild /: "roles" /~ role) mempty
+
+ (GetGuildPruneCount guild days) ->
+ Get (guilds /~ guild /: "prune") ("days" R.=: days)
+
+ (BeginGuildPrune guild days) ->
+ Post (guilds /~ guild /: "prune") (pure R.NoReqBody) ("days" R.=: days)
+
+ (GetGuildVoiceRegions guild) ->
+ Get (guilds /~ guild /: "regions") mempty
+
+ (GetGuildInvites guild) ->
+ Get (guilds /~ guild /: "invites") mempty
+
+ (GetGuildIntegrations guild) ->
+ Get (guilds /~ guild /: "integrations") mempty
+
+ (CreateGuildIntegration guild iid opts) ->
+ let patch = object ["type" .= createGuildIntegrationOptsType opts, "id" .= iid]
+ in Post (guilds /~ guild /: "integrations") (pure (R.ReqBodyJson patch)) mempty
+
+ (ModifyGuildIntegration guild iid patch) ->
+ let body = pure (R.ReqBodyJson patch)
+ in Patch (guilds /~ guild /: "integrations" /~ iid) body mempty
+
+ (DeleteGuildIntegration guild integ) ->
+ Delete (guilds /~ guild /: "integrations" /~ integ) mempty
+
+ (SyncGuildIntegration guild integ) ->
+ Post (guilds /~ guild /: "integrations" /~ integ) (pure R.NoReqBody) mempty
+
+ (GetGuildWidget guild) ->
+ Get (guilds /~ guild /: "integrations") mempty
+
+ (ModifyGuildWidget guild patch) ->
+ Patch (guilds /~ guild /: "widget") (pure (R.ReqBodyJson patch)) mempty
+
+ (GetGuildVanityURL guild) ->
+ Get (guilds /~ guild /: "vanity-url") mempty
+
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/HTTP.hs b/deps/discord-haskell/src/Discord/Internal/Rest/HTTP.hs
new file mode 100644
index 0000000..f9c0341
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/HTTP.hs
@@ -0,0 +1,140 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE MultiWayIf #-}
+
+-- | Provide HTTP primitives
+module Discord.Internal.Rest.HTTP
+ ( restLoop
+ , Request(..)
+ , JsonRequest(..)
+ , RestCallInternalException(..)
+ ) where
+
+import Prelude hiding (log)
+
+import Control.Monad.IO.Class (liftIO)
+import Control.Concurrent (threadDelay)
+import Control.Exception.Safe (try)
+import Control.Concurrent.MVar
+import Control.Concurrent.Chan
+import Data.Ix (inRange)
+import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import Text.Read (readMaybe)
+import Data.Maybe (fromMaybe)
+import qualified Network.HTTP.Req as R
+import qualified Data.Map.Strict as M
+
+import Discord.Internal.Types
+import Discord.Internal.Rest.Prelude
+
+-- | An exception in a Rest call
+data RestCallInternalException
+ -- | Error code from Discord
+ = RestCallInternalErrorCode Int B.ByteString B.ByteString
+ -- | Couldn't parse the response
+ | RestCallInternalNoParse String BL.ByteString
+ -- | Something went bad in the HTTP process
+ | RestCallInternalHttpException R.HttpException
+ deriving (Show)
+
+-- | Rest event loop
+restLoop :: Auth -> Chan (String, JsonRequest, MVar (Either RestCallInternalException BL.ByteString))
+ -> Chan T.Text -> IO ()
+restLoop auth urls log = loop M.empty
+ where
+ loop ratelocker = do
+ threadDelay (40 * 1000)
+ (route, request, thread) <- readChan urls
+ curtime <- getPOSIXTime
+ case compareRate ratelocker route curtime of
+ Locked -> do writeChan urls (route, request, thread)
+ loop ratelocker
+ Available -> do let action = compileRequest auth request
+ reqIO <- try $ restIOtoIO (tryRequest log action)
+ case reqIO :: Either R.HttpException (RequestResponse, Timeout) of
+ Left e -> do
+ writeChan log ("rest - http exception " <> T.pack (show e))
+ putMVar thread (Left (RestCallInternalHttpException e))
+ loop ratelocker
+ Right (resp, retry) -> do
+ case resp of
+ -- decode "[]" == () for expected empty calls
+ ResponseByteString "" -> putMVar thread (Right "[]")
+ ResponseByteString bs -> putMVar thread (Right bs)
+ ResponseErrorCode e s b ->
+ putMVar thread (Left (RestCallInternalErrorCode e s b))
+ ResponseTryAgain -> writeChan urls (route, request, thread)
+ case retry of
+ GlobalWait i -> do
+ writeChan log ("rest - GLOBAL WAIT LIMIT: "
+ <> T.pack (show ((i - curtime) * 1000)))
+ threadDelay $ round ((i - curtime + 0.1) * 1000)
+ loop ratelocker
+ PathWait i -> loop $ M.insert route i (removeAllExpire ratelocker curtime)
+ NoLimit -> loop ratelocker
+
+data RateLimited = Available | Locked
+
+compareRate :: M.Map String POSIXTime -> String -> POSIXTime -> RateLimited
+compareRate ratelocker route curtime =
+ case M.lookup route ratelocker of
+ Just unlockTime -> if curtime < unlockTime then Locked else Available
+ Nothing -> Available
+
+removeAllExpire :: M.Map String POSIXTime -> POSIXTime -> M.Map String POSIXTime
+removeAllExpire ratelocker curtime =
+ if M.size ratelocker > 100 then M.filter (> curtime) ratelocker
+ else ratelocker
+
+data RequestResponse = ResponseTryAgain
+ | ResponseByteString BL.ByteString
+ | ResponseErrorCode Int B.ByteString B.ByteString
+ deriving (Show)
+
+data Timeout = GlobalWait POSIXTime
+ | PathWait POSIXTime
+ | NoLimit
+
+tryRequest :: Chan T.Text -> RestIO R.LbsResponse -> RestIO (RequestResponse, Timeout)
+tryRequest _log action = do
+ resp <- action
+ now <- liftIO getPOSIXTime
+ let body = R.responseBody resp
+ code = R.responseStatusCode resp
+ status = R.responseStatusMessage resp
+ global = (Just ("true" :: String) ==) $ readMaybeBS =<< R.responseHeader resp "X-RateLimit-Global"
+ remain = fromMaybe 1 $ readMaybeBS =<< R.responseHeader resp "X-RateLimit-Remaining" :: Integer
+ reset = withDelta . fromMaybe 10 $ readMaybeBS =<< R.responseHeader resp "X-RateLimit-Reset-After"
+
+ withDelta :: Double -> POSIXTime
+ withDelta dt = now + fromRational (toRational dt)
+
+ if | code == 429 -> pure (ResponseTryAgain, if global then GlobalWait reset
+ else PathWait reset)
+ | code `elem` [500,502] -> pure (ResponseTryAgain, NoLimit)
+ | inRange (200,299) code -> pure ( ResponseByteString body
+ , if remain > 0 then NoLimit else PathWait reset )
+ | inRange (400,499) code -> pure (ResponseErrorCode code status (BL.toStrict body)
+ , if remain > 0 then NoLimit else PathWait reset )
+ | otherwise -> pure (ResponseErrorCode code status (BL.toStrict body), NoLimit)
+
+readMaybeBS :: Read a => B.ByteString -> Maybe a
+readMaybeBS = readMaybe . T.unpack . TE.decodeUtf8
+
+compileRequest :: Auth -> JsonRequest -> RestIO R.LbsResponse
+compileRequest auth request = action
+ where
+ authopt = authHeader auth <> R.header "X-RateLimit-Precision" "millisecond"
+
+ action = case request of
+ (Delete url opts) -> R.req R.DELETE url R.NoReqBody R.lbsResponse (authopt <> opts)
+ (Get url opts) -> R.req R.GET url R.NoReqBody R.lbsResponse (authopt <> opts)
+ (Put url body opts) -> R.req R.PUT url body R.lbsResponse (authopt <> opts)
+ (Patch url body opts) -> do b <- body
+ R.req R.PATCH url b R.lbsResponse (authopt <> opts)
+ (Post url body opts) -> do b <- body
+ R.req R.POST url b R.lbsResponse (authopt <> opts)
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/Interactions.hs b/deps/discord-haskell/src/Discord/Internal/Rest/Interactions.hs
new file mode 100644
index 0000000..44e41d1
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/Interactions.hs
@@ -0,0 +1,90 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Discord.Internal.Rest.Interactions (InteractionResponseRequest(..)) where
+
+import Data.Aeson (encode)
+import qualified Data.ByteString.Lazy as BL
+import Discord.Internal.Rest.Prelude
+ ( RestIO,
+ Request(..),
+ JsonRequest(Delete, Post, Get, Patch),
+ baseUrl)
+import Discord.Internal.Types
+import Discord.Internal.Types.Interactions
+import Network.HTTP.Client.MultipartFormData (PartM, partBS)
+import Network.HTTP.Req ((/:), (/~))
+import qualified Network.HTTP.Req as R
+
+-- | Data constructor for Interaction response requests
+data InteractionResponseRequest a where
+ -- | Create a response to an Interaction from the gateway.
+ --
+ -- This endpoint also supports file attachments similar to the webhook endpoints.
+ -- Refer to [Uploading files](https://discord.com/developers/docs/reference#uploading-files)
+ -- for details on uploading files and @multipart/form-data@ requests.
+ CreateInteractionResponse :: InteractionId -> InteractionToken -> InteractionResponse -> InteractionResponseRequest ()
+ -- | Returns the initial Interaction response.
+ GetOriginalInteractionResponse :: ApplicationId -> InteractionToken -> InteractionResponseRequest Message
+ -- | Edits the initial Interaction response.
+ EditOriginalInteractionResponse :: ApplicationId -> InteractionToken -> InteractionResponseMessage -> InteractionResponseRequest Message
+ -- | Deletes the initial Interaction response.
+ DeleteOriginalInteractionResponse :: ApplicationId -> InteractionToken -> InteractionResponseRequest ()
+ -- | Create a followup message for an Interaction
+ CreateFollowupInteractionMessage :: ApplicationId -> InteractionToken -> InteractionResponseMessage -> InteractionResponseRequest Message
+ -- | Returns a followup message for an Interaction.
+ GetFollowupInteractionMessage :: ApplicationId -> InteractionToken -> MessageId -> InteractionResponseRequest Message
+ -- | Edits a followup message for an Interaction.
+ EditFollowupInteractionMessage :: ApplicationId -> InteractionToken -> MessageId -> InteractionResponse -> InteractionResponseRequest Message
+ -- | Deletes a followup message for an Interaction.
+ DeleteFollowupInteractionMessage :: ApplicationId -> InteractionToken -> MessageId -> InteractionResponseRequest ()
+
+instance Request (InteractionResponseRequest a) where
+ jsonRequest = interactionResponseJsonRequest
+ majorRoute = interactionResponseMajorRoute
+
+interactionResponseMajorRoute :: InteractionResponseRequest a -> String
+interactionResponseMajorRoute a = case a of
+ (CreateInteractionResponse iid _ _) -> "intresp " <> show iid
+ (GetOriginalInteractionResponse aid _) -> "intresp " <> show aid
+ (EditOriginalInteractionResponse aid _ _) -> "intresp " <> show aid
+ (DeleteOriginalInteractionResponse aid _) -> "intresp " <> show aid
+ (CreateFollowupInteractionMessage iid _ _) -> "intrespf " <> show iid
+ (GetFollowupInteractionMessage aid _ _) -> "intrespf " <> show aid
+ (EditFollowupInteractionMessage aid _ _ _) -> "intrespf " <> show aid
+ (DeleteFollowupInteractionMessage aid _ _) -> "intrespf " <> show aid
+
+interaction :: ApplicationId -> InteractionToken -> R.Url 'R.Https
+interaction aid it = baseUrl /: "webhooks" /~ aid /~ it /: "messages"
+
+interactionResponseJsonRequest :: InteractionResponseRequest a -> JsonRequest
+interactionResponseJsonRequest a = case a of
+ (CreateInteractionResponse iid it i) ->
+ Post (baseUrl /: "interactions" /~ iid /~ it /: "callback") (convert i) mempty
+ (GetOriginalInteractionResponse aid it) ->
+ Get (interaction aid it /: "@original") mempty
+ (EditOriginalInteractionResponse aid it i) ->
+ Patch (interaction aid it /: "@original") (convertIRM i) mempty
+ (DeleteOriginalInteractionResponse aid it) ->
+ Delete (interaction aid it /: "@original") mempty
+ (CreateFollowupInteractionMessage aid it i) ->
+ Post (baseUrl /: "webhooks" /~ aid /~ it) (convertIRM i) mempty
+ (GetFollowupInteractionMessage aid it mid) ->
+ Get (interaction aid it /~ mid) mempty
+ (EditFollowupInteractionMessage aid it mid i) ->
+ Patch (interaction aid it /~ mid) (convert i) mempty
+ (DeleteFollowupInteractionMessage aid it mid) ->
+ Delete (interaction aid it /~ mid) mempty
+ where
+ convert :: InteractionResponse -> RestIO R.ReqBodyMultipart
+ convert ir@(InteractionResponseChannelMessage irm) = R.reqBodyMultipart (partBS "payload_json" (BL.toStrict $ encode ir) : convert' irm)
+ convert ir@(InteractionResponseUpdateMessage irm) = R.reqBodyMultipart (partBS "payload_json" (BL.toStrict $ encode ir) : convert' irm)
+ convert ir = R.reqBodyMultipart [partBS "payload_json" $ BL.toStrict $ encode ir]
+ convertIRM :: InteractionResponseMessage -> RestIO R.ReqBodyMultipart
+ convertIRM irm = R.reqBodyMultipart (partBS "payload_json" (BL.toStrict $ encode irm) : convert' irm)
+ convert' :: InteractionResponseMessage -> [PartM IO]
+ convert' InteractionResponseMessage {..} = case interactionResponseMessageEmbeds of
+ Nothing -> []
+ Just f -> (maybeEmbed . Just) =<< f
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/Invite.hs b/deps/discord-haskell/src/Discord/Internal/Rest/Invite.hs
new file mode 100644
index 0000000..79b4aa5
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/Invite.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Provides actions for Channel API interactions
+module Discord.Internal.Rest.Invite
+ ( InviteRequest(..)
+ ) where
+
+import Network.HTTP.Req ((/:))
+import qualified Network.HTTP.Req as R
+import qualified Data.Text as T
+
+import Discord.Internal.Rest.Prelude
+import Discord.Internal.Types
+
+instance Request (InviteRequest a) where
+ majorRoute = inviteMajorRoute
+ jsonRequest = inviteJsonRequest
+
+
+-- | Data constructor for requests. See <https://discord.com/developers/docs/resources/ API>
+data InviteRequest a where
+ -- | Get invite for given code
+ GetInvite :: T.Text -> InviteRequest Invite
+ -- | Delete invite by code
+ DeleteInvite :: T.Text -> InviteRequest Invite
+
+inviteMajorRoute :: InviteRequest a -> String
+inviteMajorRoute c = case c of
+ (GetInvite _) -> "invite "
+ (DeleteInvite _) -> "invite "
+
+invite :: R.Url 'R.Https
+invite = baseUrl /: "invites"
+
+inviteJsonRequest :: InviteRequest r -> JsonRequest
+inviteJsonRequest c = case c of
+ (GetInvite g) -> Get (invite R./: g) mempty
+ (DeleteInvite g) -> Delete (invite R./: g) mempty
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/Prelude.hs b/deps/discord-haskell/src/Discord/Internal/Rest/Prelude.hs
new file mode 100644
index 0000000..4b7d825
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/Prelude.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs #-}
+
+-- | Utility and base types and functions for the Discord Rest API
+module Discord.Internal.Rest.Prelude where
+
+import Prelude hiding (log)
+import Control.Exception.Safe (throwIO)
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import Data.String (IsString(fromString))
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+
+import qualified Network.HTTP.Req as R
+import Web.Internal.HttpApiData (ToHttpApiData)
+
+import Discord.Internal.Types
+
+import Paths_discord_haskell (version)
+import Data.Version (showVersion)
+
+-- | The api version to use.
+apiVersion :: T.Text
+apiVersion = "10"
+
+-- | The base url (Req) for API requests
+baseUrl :: R.Url 'R.Https
+baseUrl = R.https "discord.com" R./: "api" R./: apiVersion'
+ where apiVersion' = "v" <> apiVersion
+
+-- | Discord requires HTTP headers for authentication.
+authHeader :: Auth -> R.Option 'R.Https
+authHeader auth =
+ R.header "Authorization" (TE.encodeUtf8 (authToken auth))
+ <> R.header "User-Agent" agent
+ where
+ -- | https://discord.com/developers/docs/reference#user-agent
+ -- Second place where the library version is noted
+ agent = fromString $ "DiscordBot (https://github.com/discord-haskell/discord-haskell, " <> showVersion version <> ")"
+
+-- Possibly append to an URL
+infixl 5 /?
+(/?) :: ToHttpApiData a => R.Url scheme -> Maybe a -> R.Url scheme
+(/?) url Nothing = url
+(/?) url (Just part) = url R./~ part
+
+
+-- | A compiled HTTP request ready to execute
+data JsonRequest where
+ Delete :: R.Url 'R.Https -> R.Option 'R.Https -> JsonRequest
+ Get :: R.Url 'R.Https -> R.Option 'R.Https -> JsonRequest
+ Put :: R.HttpBody a => R.Url 'R.Https -> a -> R.Option 'R.Https -> JsonRequest
+ Patch :: R.HttpBody a => R.Url 'R.Https -> RestIO a -> R.Option 'R.Https -> JsonRequest
+ Post :: R.HttpBody a => R.Url 'R.Https -> RestIO a -> R.Option 'R.Https -> JsonRequest
+
+class Request a where
+ -- | used for putting a request into a rate limit bucket
+ -- https://discord.com/developers/docs/topics/rate-limits#rate-limits
+ majorRoute :: a -> String
+
+ -- | build a JSON http request
+ jsonRequest :: a -> JsonRequest
+
+-- | Same Monad as IO. Overwrite Req settings
+newtype RestIO a = RestIO { restIOtoIO :: IO a }
+ deriving (Functor, Applicative, Monad, MonadIO)
+
+instance R.MonadHttp RestIO where
+ -- | Throw actual exceptions
+ handleHttpException = liftIO . throwIO
+ -- | Don't throw exceptions on http error codes like 404
+ getHttpConfig = pure $ R.defaultHttpConfig { R.httpConfigCheckResponse = \_ _ _ -> Nothing }
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/ScheduledEvents.hs b/deps/discord-haskell/src/Discord/Internal/Rest/ScheduledEvents.hs
new file mode 100644
index 0000000..bb35d12
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/ScheduledEvents.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Provides actions for Scheduled Event API
+module Discord.Internal.Rest.ScheduledEvents
+ ( ScheduledEventRequest(..)
+ ) where
+import Data.Aeson ( ToJSON(toJSON) )
+import Discord.Internal.Rest.Prelude ( JsonRequest(..)
+ , Request
+ ( jsonRequest
+ , majorRoute
+ )
+ , baseUrl
+ )
+import Discord.Internal.Types.Prelude ( GuildId
+ , ScheduledEventId
+ )
+import Discord.Internal.Types.ScheduledEvents
+ ( CreateScheduledEventData
+ , ModifyScheduledEventData
+ , ScheduledEvent
+ , ScheduledEventUser
+ )
+import qualified Network.HTTP.Req as R
+import Network.HTTP.Req ( (/:), (/~) )
+
+-- | Data constructor for requests.
+-- See <https://discord.com/developers/docs/resources/guild-scheduled-event>
+data ScheduledEventRequest a where
+ -- | Gets all the Scheduled Events of a Guild
+ ListScheduledEvents ::GuildId
+ -> ScheduledEventRequest [ScheduledEvent]
+ -- | Creates a new ScheduledEvent
+ CreateScheduledEvent ::GuildId
+ -> CreateScheduledEventData
+ -> ScheduledEventRequest ScheduledEvent
+ -- | Gets the information about an Event
+ GetScheduledEvent ::GuildId
+ -> ScheduledEventId
+ -> ScheduledEventRequest ScheduledEvent
+ -- | Modifies a Scheduled Event's information
+ ModifyScheduledEvent ::GuildId
+ -> ScheduledEventId
+ -> ModifyScheduledEventData
+ -> ScheduledEventRequest ScheduledEvent
+ -- | Delete a ScheduledEvent
+ DeleteScheduledEvent ::GuildId
+ -> ScheduledEventId
+ -> ScheduledEventRequest ()
+ -- | Gets the Users that subscribed to the event
+ GetScheduledEventUsers ::GuildId
+ -> ScheduledEventId
+ -> ScheduledEventRequest [ScheduledEventUser]
+
+sevEndpoint :: GuildId -> R.Url 'R.Https
+sevEndpoint gid = baseUrl /: "guilds" /~ gid /: "scheduled-events"
+
+instance Request (ScheduledEventRequest a) where
+ majorRoute = const "scheduledEvent"
+ jsonRequest rq = case rq of
+ ListScheduledEvents gid -> Get (sevEndpoint gid) mempty
+ GetScheduledEvent gid ev -> Get (sevEndpoint gid /~ ev) mempty
+ CreateScheduledEvent gid ev ->
+ Post (sevEndpoint gid) (pure $ R.ReqBodyJson $ toJSON ev) mempty
+ ModifyScheduledEvent gid evi ev -> Patch
+ (sevEndpoint gid /~ evi)
+ (pure $ R.ReqBodyJson $ toJSON ev)
+ mempty
+ DeleteScheduledEvent gid evi -> Delete (sevEndpoint gid /~ evi) mempty
+ GetScheduledEventUsers gid evi ->
+ Get (sevEndpoint gid /~ evi /: "users") mempty
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/User.hs b/deps/discord-haskell/src/Discord/Internal/Rest/User.hs
new file mode 100644
index 0000000..28c0505
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/User.hs
@@ -0,0 +1,99 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Provides actions for Channel API interactions
+module Discord.Internal.Rest.User
+ ( UserRequest(..)
+ , parseAvatarImage
+ ) where
+
+
+import Data.Aeson
+import Network.HTTP.Req ((/:), (/~))
+import qualified Network.HTTP.Req as R
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Base64 as B64
+
+import Discord.Internal.Rest.Prelude
+import Discord.Internal.Types
+
+instance Request (UserRequest a) where
+ majorRoute = userMajorRoute
+ jsonRequest = userJsonRequest
+
+
+-- | Data constructor for requests. See <https://discord.com/developers/docs/resources/ API>
+data UserRequest a where
+ -- | Returns the 'User' object of the requester's account. For OAuth2, this requires
+ -- the identify scope, which will return the object without an email, and optionally
+ -- the email scope, which returns the object with an email.
+ GetCurrentUser :: UserRequest User
+ -- | Returns a 'User' for a given user ID
+ GetUser :: UserId -> UserRequest User
+ -- | Modify user's username & avatar pic
+ ModifyCurrentUser :: T.Text -> Base64Image User -> UserRequest User
+ -- | Returns a list of user 'Guild' objects the current user is a member of.
+ -- Requires the guilds OAuth2 scope.
+ GetCurrentUserGuilds :: UserRequest [PartialGuild]
+ -- | Leave a guild.
+ LeaveGuild :: GuildId -> UserRequest ()
+ -- | Returns a list of DM 'Channel' objects
+ GetUserDMs :: UserRequest [Channel]
+ -- | Create a new DM channel with a user. Returns a DM 'Channel' object.
+ CreateDM :: UserId -> UserRequest Channel
+
+ GetUserConnections :: UserRequest [ConnectionObject]
+
+-- | @parseAvatarImage bs@ will attempt to convert the given image bytestring
+-- @bs@ to the base64 format expected by the Discord API. It may return Left
+-- with an error reason if the image format could not be predetermined from the
+-- opening magic bytes. This function does /not/ validate the rest of the image,
+-- and this is up to the library user to check themselves.
+--
+-- This function accepts all file types accepted by 'getMimeType'.
+parseAvatarImage :: B.ByteString -> Either T.Text (Base64Image User)
+parseAvatarImage bs
+ | Just mime <- getMimeType bs = Right (Base64Image mime (TE.decodeUtf8 (B64.encode bs)))
+ | otherwise = Left "Unsupported image format provided"
+
+userMajorRoute :: UserRequest a -> String
+userMajorRoute c = case c of
+ (GetCurrentUser) -> "me "
+ (GetUser _) -> "user "
+ (ModifyCurrentUser _ _) -> "modify_user "
+ (GetCurrentUserGuilds) -> "get_user_guilds "
+ (LeaveGuild g) -> "leave_guild " <> show g
+ (GetUserDMs) -> "get_dms "
+ (CreateDM _) -> "make_dm "
+ (GetUserConnections) -> "connections "
+
+users :: R.Url 'R.Https
+users = baseUrl /: "users"
+
+userJsonRequest :: UserRequest r -> JsonRequest
+userJsonRequest c = case c of
+ (GetCurrentUser) -> Get (users /: "@me") mempty
+
+ (GetUser user) -> Get (users /~ user ) mempty
+
+ (ModifyCurrentUser name b64im) ->
+ Patch (users /: "@me") (pure (R.ReqBodyJson (object [ "username" .= name
+ , "avatar" .= b64im ]))) mempty
+
+ (GetCurrentUserGuilds) -> Get (users /: "@me" /: "guilds") mempty
+
+ (LeaveGuild guild) -> Delete (users /: "@me" /: "guilds" /~ guild) mempty
+
+ (GetUserDMs) -> Get (users /: "@me" /: "channels") mempty
+
+ (CreateDM user) ->
+ let body = R.ReqBodyJson $ object ["recipient_id" .= user]
+ in Post (users /: "@me" /: "channels") (pure body) mempty
+
+ (GetUserConnections) ->
+ Get (users /: "@me" /: "connections") mempty
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/Voice.hs b/deps/discord-haskell/src/Discord/Internal/Rest/Voice.hs
new file mode 100644
index 0000000..9966aea
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/Voice.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Provides actions for Voice API interactions
+module Discord.Internal.Rest.Voice
+ ( VoiceRequest(..)
+ ) where
+
+
+import Network.HTTP.Req ((/:))
+import qualified Network.HTTP.Req as R
+
+import Discord.Internal.Rest.Prelude
+import Discord.Internal.Types
+
+instance Request (VoiceRequest a) where
+ majorRoute = voiceMajorRoute
+ jsonRequest = voiceJsonRequest
+
+-- | Data constructor for requests
+data VoiceRequest a where
+ -- | List all available 'VoiceRegion's.
+ ListVoiceRegions :: VoiceRequest [VoiceRegion]
+
+voiceMajorRoute :: VoiceRequest a -> String
+voiceMajorRoute c = case c of
+ (ListVoiceRegions) -> "whatever "
+
+voices :: R.Url 'R.Https
+voices = baseUrl /: "voice"
+
+voiceJsonRequest :: VoiceRequest r -> JsonRequest
+voiceJsonRequest c = case c of
+ (ListVoiceRegions) -> Get (voices /: "regions") mempty
diff --git a/deps/discord-haskell/src/Discord/Internal/Rest/Webhook.hs b/deps/discord-haskell/src/Discord/Internal/Rest/Webhook.hs
new file mode 100644
index 0000000..7b4a545
--- /dev/null
+++ b/deps/discord-haskell/src/Discord/Internal/Rest/Webhook.hs
@@ -0,0 +1,202 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Provides actions for Webhook API interactions
+module Discord.Internal.Rest.Webhook
+ ( CreateWebhookOpts(..)
+ , ExecuteWebhookWithTokenOpts(..)
+ , ModifyWebhookOpts(..)
+ , WebhookContent(..)
+ , WebhookRequest(..)
+ ) where
+
+import Data.Aeson
+import qualified Data.Text as T
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import Network.HTTP.Req ((/:), (/~))
+import qualified Network.HTTP.Req as R
+import Network.HTTP.Client (RequestBody (RequestBodyBS))
+import Network.HTTP.Client.MultipartFormData (partBS, partFileRequestBody)
+
+import Discord.Internal.Rest.Prelude
+import Discord.Internal.Types
+
+instance Request (WebhookRequest a) where
+ majorRoute = webhookMajorRoute
+ jsonRequest = webhookJsonRequest
+
+-- | Data constructors for webhook requests.
+data WebhookRequest a where
+ -- | Creates a new webhook and returns a webhook object on success. Requires the @MANAGE_WEBHOOKS@ permission.
+ -- An error will be returned if a webhook name (name) is not valid. A webhook name is valid if:
+ --
+ -- * It does not contain the substring @clyde@ (case-insensitive)
+ -- * It follows the nickname guidelines in the Usernames and Nicknames documentation,
+ -- with an exception that webhook names can be up to 80 characters
+ CreateWebhook :: ChannelId
+ -> CreateWebhookOpts
+ -> WebhookRequest Webhook
+ -- | Returns a channel's `Webhook`s as a list. Requires the @MANAGE_WEBHOOKS@ permission.
+ GetChannelWebhooks :: ChannelId
+ -> WebhookRequest [Webhook]
+ -- | Returns a guild's `Webhook`s as a list. Requires the @MANAGE_WEBHOOKS@ permission.
+ GetGuildWebhooks :: GuildId
+ -> WebhookRequest [Webhook]
+ -- | Returns the `Webhook` for the given id. If a token is given, authentication is not required.
+ GetWebhook :: WebhookId
+ -> Maybe WebhookToken
+ -> WebhookRequest Webhook
+ -- | Modify a webhook. Requires the @MANAGE_WEBHOOKS@ permission. Returns the updated `Webhook` on success.
+ -- If a token is given, authentication is not required.
+ ModifyWebhook :: WebhookId
+ -> Maybe WebhookToken
+ -> ModifyWebhookOpts
+ -> WebhookRequest Webhook
+ -- | Delete a webhook permanently. Requires the @MANAGE_WEBHOOKS@ permission.
+ -- If a token is given, authentication is not required.
+ DeleteWebhook :: WebhookId
+ -> Maybe WebhookToken
+ -> WebhookRequest ()
+ -- | Executes a Webhook.
+ --
+ -- Refer to [Uploading Files](https://discord.com/developers/docs/reference#uploading-files)
+ -- for details on attachments and @multipart/form-data@ requests.
+ ExecuteWebhook :: WebhookId
+ -> WebhookToken
+ -> ExecuteWebhookWithTokenOpts
+ -> WebhookRequest ()
+ -- We don't support slack and github compatible webhooks because you should
+ -- just use execute webhook.
+
+ -- | Returns a previously-sent webhook message from the same token.
+ GetWebhookMessage :: WebhookId
+ -> WebhookToken
+ -> MessageId
+ -> WebhookRequest Message
+ -- | Edits a previously-sent webhook message from the same token.
+ EditWebhookMessage :: WebhookId
+ -> WebhookToken
+ -> MessageId
+ -> T.Text -- currently we don't support the full range of edits - feel free to PR and fix this
+ -> WebhookRequest Message
+ -- | Deletes a previously-sent webhook message from the same token.
+ DeleteWebhookMessage :: WebhookId
+ -> WebhookToken
+ -> MessageId
+ -> WebhookRequest ()
+
+-- | Options for `ModifyWebhook` and `ModifyWebhookWithToken`
+data ModifyWebhookOpts = ModifyWebhookOpts
+ { modifyWebhookOptsName :: Maybe T.Text
+ , modifyWebhookOptsAvatar :: Maybe T.Text
+ , modifyWebhookOptsChannelId :: Maybe ChannelId
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON ModifyWebhookOpts where
+ toJSON ModifyWebhookOpts{..} = objectFromMaybes
+ ["channel_id" .=? modifyWebhookOptsChannelId,
+ "name" .=? modifyWebhookOptsName,
+ "avatar" .=? modifyWebhookOptsAvatar ]
+
+-- | Options for `CreateWebhook`
+data CreateWebhookOpts = CreateWebhookOpts
+ { createWebhookOptsName :: T.Text
+ , createWebhookOptsAvatar :: Maybe T.Text
+ } deriving (Show, Read, Eq, Ord)
+
+instance ToJSON CreateWebhookOpts where
+ toJSON CreateWebhookOpts{..} = objectFromMaybes
+ ["name" .== createWebhookOptsName,
+ "avatar" .=? createWebhookOptsAvatar ]
+
+-- | Options for `ExecuteWebhookWithToken`
+data ExecuteWebhookWithTokenOpts = ExecuteWebhookWithTokenOpts
+ { executeWebhookWithTokenOptsUsername :: Maybe T.Text
+ , executeWebhookWithTokenOptsContent :: WebhookContent
+ } deriving (Show, Read, Eq, Ord)
+
+-- | A webhook's content
+data WebhookContent = WebhookContentText T.Text
+ | WebhookContentFile T.Text B.ByteString
+ | WebhookContentEmbeds [CreateEmbed]
+ deriving (Show, Read, Eq, Ord)
+
+webhookContentJson :: WebhookContent -> [(AesonKey, Value)]
+webhookContentJson c = case c of
+ WebhookContentText t -> [("content", toJSON t)]
+ WebhookContentFile _ _ -> []
+ WebhookContentEmbeds e -> [("embeds", toJSON (createEmbed <$> e))]
+
+instance ToJSON ExecuteWebhookWithTokenOpts where
+ toJSON ExecuteWebhookWithTokenOpts{..} = objectFromMaybes $
+ ["username" .=? executeWebhookWithTokenOptsUsername]
+ <> fmap Just (webhookContentJson executeWebhookWithTokenOptsContent)
+
+-- | Major routes for webhook requests
+webhookMajorRoute :: WebhookRequest a -> String
+webhookMajorRoute ch = case ch of
+ (CreateWebhook c _) -> "aaaaaahook " <> show c
+ (GetChannelWebhooks c) -> "aaaaaahook " <> show c
+ (GetGuildWebhooks g) -> "aaaaaahook " <> show g
+ (GetWebhook w _) -> "getwebhook " <> show w
+ (ModifyWebhook w _ _) -> "modifyhook " <> show w
+ (DeleteWebhook w _) -> "deletehook " <> show w
+ (ExecuteWebhook w _ _) -> "executehk " <> show w
+ (GetWebhookMessage w _ _) -> "gethkmsg " <> show w
+ (EditWebhookMessage w _ _ _) -> "edithkmsg " <> show w
+ (DeleteWebhookMessage w _ _) -> "delhkmsg " <> show w
+
+-- | Create a 'JsonRequest' from a `WebhookRequest`
+webhookJsonRequest :: WebhookRequest r -> JsonRequest
+webhookJsonRequest ch = case ch of
+ (CreateWebhook channel patch) ->
+ let body = pure (R.ReqBodyJson patch)
+ in Post (baseUrl /: "channels" /~ channel /: "webhooks") body mempty
+
+ (GetChannelWebhooks c) ->
+ Get (baseUrl /: "channels" /~ c /: "webhooks") mempty
+
+ (GetGuildWebhooks g) ->
+ Get (baseUrl /: "guilds" /~ g /: "webhooks") mempty
+
+ (GetWebhook w t) ->
+ Get (baseUrl /: "webhooks" /~ w /? t) mempty
+
+ (ModifyWebhook w t p) ->
+ Patch (baseUrl /: "webhooks" /~ w /? t) (pure (R.ReqBodyJson p)) mempty
+
+ (DeleteWebhook w t) ->
+ Delete (baseUrl /: "webhooks" /~ w /? t) mempty
+
+ (ExecuteWebhook w tok o) ->
+ case executeWebhookWithTokenOptsContent o of
+ WebhookContentFile name text ->
+ let part = partFileRequestBody "file" (T.unpack name) (RequestBodyBS text)
+ body = R.reqBodyMultipart [part]
+ in Post (baseUrl /: "webhooks" /~ w /~ tok) body mempty
+ WebhookContentText _ ->
+ let body = pure (R.ReqBodyJson o)
+ in Post (baseUrl /: "webhooks" /~ w /~ tok) body mempty
+ WebhookContentEmbeds embeds ->
+ let mkPart (name,content) = partFileRequestBody name (T.unpack name) (RequestBodyBS content)
+ uploads CreateEmbed{..} = [(n,c) | (n, Just (CreateEmbedImageUpload c)) <-
+ [ ("author.png", createEmbedAuthorIcon)
+ , ("thumbnail.png", createEmbedThumbnail)
+ , ("image.png", createEmbedImage)
+ , ("footer.png", createEmbedFooterIcon) ]]
+ parts = map mkPart (concatMap uploads embeds)
+ partsJson = [partBS "payload_json" $ BL.toStrict $ encode $ toJSON $ object ["embed" .= createEmbed e] | e <- embeds]
+ body = R.reqBodyMultipart (partsJson ++ parts)
+ in Post (baseUrl /: "webhooks" /~ w /: unToken tok) body mempty
+
+ (GetWebhookMessage w t m) ->
+ Get (baseUrl /: "webhooks" /~ w /~ t /: "messages" /~ m) mempty
+
+ (EditWebhookMessage w t m p) ->
+ Patch (baseUrl /: "webhooks" /~ w /~ t /: "messages" /~ m) (pure (R.ReqBodyJson $ object ["content" .= p])) mempty
+
+ (DeleteWebhookMessage w t m) ->
+ Delete (baseUrl /: "webhooks" /~ w /~ t /: "messages" /~ m) mempty