sigmacord-1.0.0: src/Sigmacord/Internal/Rest/Channel.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
module Sigmacord.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 Sigmacord.Internal.Rest.Prelude
import Sigmacord.Internal.Types
import Control.Monad (join)
instance Request (ChannelRequest a) where
majorRoute = channelMajorRoute
jsonRequest = channelJsonRequest
data ChannelRequest a where
GetChannel :: ChannelId -> ChannelRequest Channel
ModifyChannel :: ChannelId -> ModifyChannelOpts -> ChannelRequest Channel
DeleteChannel :: ChannelId -> ChannelRequest Channel
GetChannelMessages :: ChannelId -> (Int, MessageTiming) -> ChannelRequest [Message]
GetChannelMessage :: (ChannelId, MessageId) -> ChannelRequest Message
CreateMessage :: ChannelId -> T.Text -> ChannelRequest Message
CreateMessageDetailed :: ChannelId -> MessageDetailedOpts -> ChannelRequest Message
CreateReaction :: (ChannelId, MessageId) -> T.Text -> ChannelRequest ()
DeleteOwnReaction :: (ChannelId, MessageId) -> T.Text -> ChannelRequest ()
DeleteUserReaction :: (ChannelId, MessageId) -> UserId -> T.Text -> ChannelRequest ()
DeleteSingleReaction :: (ChannelId, MessageId) -> T.Text -> ChannelRequest ()
GetReactions :: (ChannelId, MessageId) -> T.Text -> (Int, ReactionTiming) -> ChannelRequest [User]
DeleteAllReactions :: (ChannelId, MessageId) -> ChannelRequest ()
EditMessage :: (ChannelId, MessageId) -> MessageDetailedOpts
-> ChannelRequest Message
DeleteMessage :: (ChannelId, MessageId) -> ChannelRequest ()
BulkDeleteMessage :: (ChannelId, [MessageId]) -> ChannelRequest ()
EditChannelPermissions :: ChannelId -> Either RoleId UserId -> ChannelPermissionsOpts -> ChannelRequest ()
GetChannelInvites :: ChannelId -> ChannelRequest Object
CreateChannelInvite :: ChannelId -> ChannelInviteOpts -> ChannelRequest Invite
DeleteChannelPermission :: ChannelId -> Either RoleId UserId -> ChannelRequest ()
TriggerTypingIndicator :: ChannelId -> ChannelRequest ()
GetPinnedMessages :: ChannelId -> ChannelRequest [Message]
AddPinnedMessage :: (ChannelId, MessageId) -> ChannelRequest ()
DeletePinnedMessage :: (ChannelId, MessageId) -> ChannelRequest ()
GroupDMAddRecipient :: ChannelId -> GroupDMAddRecipientOpts -> ChannelRequest ()
GroupDMRemoveRecipient :: ChannelId -> UserId -> ChannelRequest ()
StartThreadFromMessage :: ChannelId -> MessageId -> StartThreadOpts -> ChannelRequest Channel
StartThreadNoMessage :: ChannelId -> StartThreadNoMessageOpts -> ChannelRequest Channel
JoinThread :: ChannelId -> ChannelRequest ()
AddThreadMember :: ChannelId -> UserId -> ChannelRequest ()
LeaveThread :: ChannelId -> ChannelRequest ()
RemoveThreadMember :: ChannelId -> UserId -> ChannelRequest ()
GetThreadMember :: ChannelId -> UserId -> ChannelRequest ThreadMember
ListThreadMembers :: ChannelId -> ChannelRequest [ThreadMember]
ListPublicArchivedThreads :: ChannelId -> (Maybe UTCTime, Maybe Integer) -> ChannelRequest ListThreads
ListPrivateArchivedThreads :: ChannelId -> (Maybe UTCTime, Maybe Integer) -> ChannelRequest ListThreads
ListJoinedPrivateArchivedThreads :: ChannelId -> (Maybe UTCTime, Maybe Integer) -> ChannelRequest ListThreads
data MessageDetailedOpts = MessageDetailedOpts
{
messageDetailedContent :: T.Text
,
messageDetailedTTS :: Bool
,
messageDetailedEmbeds :: Maybe [CreateEmbed]
,
messageDetailedFile :: Maybe (T.Text, B.ByteString)
,
messageDetailedAllowedMentions :: Maybe AllowedMentions
,
messageDetailedReference :: Maybe MessageReference
,
messageDetailedComponents :: Maybe [ActionRow]
,
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 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 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
data ChannelInviteOpts = ChannelInviteOpts
{
channelInviteOptsMaxAgeSeconds :: Maybe Integer
,
channelInviteOptsMaxUsages :: Maybe Integer
,
channelInviteOptsIsTemporary :: Maybe Bool
,
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 ]
data ModifyChannelOpts = ModifyChannelOpts
{
modifyChannelName :: Maybe T.Text
,
modifyChannelPosition :: Maybe Integer
,
modifyChannelTopic :: Maybe T.Text
,
modifyChannelNSFW :: Maybe Bool
,
modifyChannelBitrate :: Maybe Integer
,
modifyChannelUserRateLimit :: Maybe Integer
,
modifyChannelUserLimit :: Maybe Integer
,
modifyChannelPermissionOverwrites :: Maybe [Overwrite]
,
modifyChannelParentId :: Maybe ChannelId
,
modifyChannelDefaultAutoArchive :: Maybe Integer
,
modifyChannelThreadArchived :: Maybe Bool
,
modifyChannelThreadAutoArchive :: Maybe Integer
,
modifyChannelThreadLocked :: Maybe Bool
,
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 ]
data ChannelPermissionsOpts = ChannelPermissionsOpts
{
channelPermissionsOptsAllow :: Integer
,
channelPermissionsOptsDeny :: Integer
} deriving (Show, Read, Eq, Ord)
data GroupDMAddRecipientOpts = GroupDMAddRecipientOpts
{
groupDMAddRecipientUserToAdd :: UserId
,
groupDMAddRecipientUserToAddNickName :: T.Text
,
groupDMAddRecipientGDMJoinAccessToken :: T.Text
} deriving (Show, Read, Eq, Ord)
data StartThreadOpts = StartThreadOpts
{
startThreadName :: T.Text
,
startThreadAutoArchive :: Maybe Integer
,
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
]
data StartThreadNoMessageOpts = StartThreadNoMessageOpts
{
startThreadNoMessageBaseOpts :: StartThreadOpts
,
startThreadNoMessageType :: Integer
,
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
]
data ListThreads = ListThreads
{
listThreadsThreads :: [Channel]
,
listThreadsMembers :: [ThreadMember]
,
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
(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)