packages feed

calamity-0.3.0.0: Calamity/HTTP/Interaction.hs

-- | Interaction endpoints
module Calamity.HTTP.Interaction (
  InteractionRequest (..),
  InteractionCallbackMessageOptions (..),
  InteractionCallbackAutocomplete (..),
  InteractionCallbackAutocompleteChoice (..),
  InteractionCallbackModal (..),
) where

import Calamity.HTTP.Channel (AllowedMentions, CreateMessageAttachment (..))
import Calamity.HTTP.Internal.Request
import Calamity.HTTP.Internal.Route
import Calamity.Internal.AesonThings
import Calamity.Types.Model.Channel.Component (Component, CustomID)
import Calamity.Types.Model.Channel.Embed (Embed)
import Calamity.Types.Model.Channel.Message (Message)
import Calamity.Types.Model.Interaction
import Calamity.Types.Snowflake
import Control.Lens hiding ((.=))
import Data.Aeson
import Data.Bits (shiftL, (.|.))
import Data.Default.Class
import qualified Data.HashMap.Strict as H
import Data.Maybe (fromMaybe)
import Data.Monoid (First (First, getFirst))
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics
import Network.HTTP.Client.MultipartFormData
import Network.HTTP.Req
import Network.Mime
import PyF
import TextShow
import qualified TextShow.Generic as TSG

data InteractionCallback = InteractionCallback
  { type_ :: InteractionCallbackType
  , data_ :: Maybe Value
  }
  deriving (Show, Generic)
  deriving (ToJSON) via CalamityJSON InteractionCallback

data InteractionCallbackMessageOptions = InteractionCallbackMessageOptions
  { tts :: Maybe Bool
  , content :: Maybe Text
  , embeds :: Maybe [Embed]
  , allowedMentions :: Maybe AllowedMentions
  , ephemeral :: Maybe Bool
  , suppressEmbeds :: Maybe Bool
  , components :: Maybe [Component]
  , attachments :: Maybe [CreateMessageAttachment]
  }
  deriving (Show, Generic, Default)

data CreateMessageAttachmentJson = CreateMessageAttachmentJson
  { id :: Int
  , filename :: Text
  , description :: Maybe Text
  }
  deriving (Show, Generic)
  deriving (ToJSON) via CalamityJSON CreateMessageAttachmentJson

data CreateResponseMessageJson = CreateResponseMessageJson
  { tts :: Maybe Bool
  , content :: Maybe Text
  , embeds :: Maybe [Embed]
  , allowedMentions :: Maybe AllowedMentions
  , flags :: Maybe Int
  , components :: Maybe [Component]
  , attachments :: Maybe [CreateMessageAttachmentJson]
  }
  deriving (Show, Generic)
  deriving (ToJSON) via CalamityJSON CreateResponseMessageJson

newtype InteractionCallbackAutocomplete = InteractionCallbackAutocomplete
  { choices :: [InteractionCallbackAutocompleteChoice]
  }
  deriving stock (Show, Generic)
  deriving (ToJSON) via CalamityJSON InteractionCallbackAutocomplete

data InteractionCallbackAutocompleteChoice = InteractionCallbackAutocompleteChoice
  { name :: Text
  , nameLocalizations :: H.HashMap Text Text
  , -- | Either text or numeric
    value :: Value
  }
  deriving stock (Show, Generic)
  deriving (ToJSON) via CalamityJSON InteractionCallbackAutocompleteChoice

data InteractionCallbackModal = InteractionCallbackModal
  { customID :: CustomID
  , title :: Text
  , components :: [Component]
  }
  deriving stock (Show, Generic)
  deriving (ToJSON) via CalamityJSON InteractionCallbackModal

data InteractionCallbackType
  = PongType
  | ChannelMessageWithSourceType
  | DeferredChannelMessageWithSourceType
  | DeferredUpdateMessageType
  | UpdateMessageType
  | ApplicationCommandAutocompleteResultType
  | ModalType
  deriving (Show, Generic)
  deriving (TextShow) via TSG.FromGeneric InteractionCallbackType

instance ToJSON InteractionCallbackType where
  toEncoding ty = toEncoding @Int $ case ty of
    PongType -> 1
    ChannelMessageWithSourceType -> 4
    DeferredChannelMessageWithSourceType -> 5
    DeferredUpdateMessageType -> 6
    UpdateMessageType -> 7
    ApplicationCommandAutocompleteResultType -> 8
    ModalType -> 9

data InteractionRequest a where
  CreateResponseMessage ::
    (HasID Interaction i) =>
    i ->
    InteractionToken ->
    InteractionCallbackMessageOptions ->
    InteractionRequest ()
  -- | Ack an interaction and defer the response
  --
  -- This route triggers the 'thinking' message
  CreateResponseDefer ::
    (HasID Interaction i) =>
    i ->
    InteractionToken ->
    -- | Ephemeral
    Bool ->
    InteractionRequest ()
  -- | Ack an interaction and defer the response
  --
  -- This route is only usable by component interactions, and doesn't trigger a
  -- 'thinking' message
  CreateResponseDeferComponent ::
    (HasID Interaction i) =>
    i ->
    InteractionToken ->
    InteractionRequest ()
  CreateResponseUpdate ::
    (HasID Interaction i) =>
    i ->
    InteractionToken ->
    InteractionCallbackMessageOptions ->
    InteractionRequest ()
  CreateResponseAutocomplete ::
    (HasID Interaction i) =>
    i ->
    InteractionToken ->
    InteractionCallbackAutocomplete ->
    InteractionRequest ()
  CreateResponseModal ::
    (HasID Interaction i) =>
    i ->
    InteractionToken ->
    InteractionCallbackModal ->
    InteractionRequest ()
  GetOriginalInteractionResponse ::
    (HasID Application i) =>
    i ->
    InteractionToken ->
    InteractionRequest Message
  EditOriginalInteractionResponse ::
    (HasID Application i) =>
    i ->
    InteractionToken ->
    InteractionCallbackMessageOptions ->
    InteractionRequest Message
  DeleteOriginalInteractionResponse ::
    (HasID Application i) =>
    i ->
    InteractionToken ->
    InteractionRequest ()
  CreateFollowupMessage ::
    (HasID Application i) =>
    i ->
    InteractionToken ->
    InteractionCallbackMessageOptions ->
    InteractionRequest ()
  GetFollowupMessage ::
    (HasID Application i, HasID Message m) =>
    i ->
    m ->
    InteractionToken ->
    InteractionRequest Message
  EditFollowupMessage ::
    (HasID Application i, HasID Message m) =>
    i ->
    m ->
    InteractionToken ->
    InteractionCallbackMessageOptions ->
    InteractionRequest ()
  DeleteFollowupMessage ::
    (HasID Application i, HasID Message m) =>
    i ->
    m ->
    InteractionToken ->
    InteractionRequest ()

baseRoute :: Snowflake Application -> InteractionToken -> RouteBuilder _
baseRoute id (InteractionToken token) =
  mkRouteBuilder // S "webhooks" // ID @Application // S token
    & giveID id

foo :: Maybe a -> Maybe a -> (a -> a -> a) -> Maybe a
foo (Just x) (Just y) f = Just (f x y)
foo x y _ = getFirst $ First x <> First y

instance Request (InteractionRequest a) where
  type Result (InteractionRequest a) = a

  route (CreateResponseDefer (getID @Interaction -> iid) (InteractionToken token) _) =
    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"
      & giveID iid
      & buildRoute
  route (CreateResponseDeferComponent (getID @Interaction -> iid) (InteractionToken token)) =
    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"
      & giveID iid
      & buildRoute
  route (CreateResponseMessage (getID @Interaction -> iid) (InteractionToken token) _) =
    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"
      & giveID iid
      & buildRoute
  route (CreateResponseUpdate (getID @Interaction -> iid) (InteractionToken token) _) =
    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"
      & giveID iid
      & buildRoute
  route (CreateResponseAutocomplete (getID @Interaction -> iid) (InteractionToken token) _) =
    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"
      & giveID iid
      & buildRoute
  route (CreateResponseModal (getID @Interaction -> iid) (InteractionToken token) _) =
    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"
      & giveID iid
      & buildRoute
  route (GetOriginalInteractionResponse (getID @Application -> aid) token) =
    baseRoute aid token // S "messages" // S "@original" & buildRoute
  route (EditOriginalInteractionResponse (getID @Application -> aid) token _) =
    baseRoute aid token // S "messages" // S "@original" & buildRoute
  route (DeleteOriginalInteractionResponse (getID @Application -> aid) token) =
    baseRoute aid token // S "messages" // S "@original" & buildRoute
  route (CreateFollowupMessage (getID @Application -> aid) token _) =
    baseRoute aid token & buildRoute
  route (GetFollowupMessage (getID @Application -> aid) (getID @Message -> mid) token) =
    baseRoute aid token // S "messages" // ID @Message & giveID mid & buildRoute
  route (EditFollowupMessage (getID @Application -> aid) (getID @Message -> mid) token _) =
    baseRoute aid token // S "messages" // ID @Message & giveID mid & buildRoute
  route (DeleteFollowupMessage (getID @Application -> aid) (getID @Message -> mid) token) =
    baseRoute aid token // S "messages" // ID @Message & giveID mid & buildRoute

  action (CreateResponseDefer _ _ ephemeral) =
    let jsonBody =
          InteractionCallback
            { type_ = DeferredChannelMessageWithSourceType
            , data_ = if ephemeral then Just . object $ [("flags", Number 64)] else Nothing
            }
     in postWith' (ReqBodyJson jsonBody)
  action (CreateResponseDeferComponent _ _) =
    let jsonBody =
          InteractionCallback
            { type_ = DeferredUpdateMessageType
            , data_ = Nothing
            }
     in postWith' (ReqBodyJson jsonBody)
  action (CreateResponseMessage _ _ cm) = \u o -> do
    let filePart CreateMessageAttachment {filename, content} n =
          (partLBS @IO [fmt|files[{n}]|] content)
            { partFilename = Just (T.unpack filename)
            , partContentType = Just (defaultMimeLookup filename)
            }
        attachmentPart CreateMessageAttachment {filename, description} n =
          CreateMessageAttachmentJson n filename description
        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]
        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments
        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral
        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds
        flags = foo ephemeral suppressEmbeds (.|.)
        jsonData =
          CreateResponseMessageJson
            { content = cm ^. #content
            , tts = cm ^. #tts
            , embeds = cm ^. #embeds
            , allowedMentions = cm ^. #allowedMentions
            , components = cm ^. #components
            , attachments = attachments
            , flags = flags
            }
        jsonBody =
          InteractionCallback
            { type_ = ChannelMessageWithSourceType
            , data_ = Just . toJSON $ jsonData
            }
    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonBody) : files)
    postWith' body u o
  action (CreateResponseUpdate _ _ cm) = \u o -> do
    let filePart CreateMessageAttachment {filename, content} n =
          (partLBS @IO [fmt|files[{n}]|] content)
            { partFilename = Just (T.unpack filename)
            , partContentType = Just (defaultMimeLookup filename)
            }
        attachmentPart CreateMessageAttachment {filename, description} n =
          CreateMessageAttachmentJson n filename description
        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]
        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments
        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral
        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds
        flags = foo ephemeral suppressEmbeds (.|.)
        jsonData =
          CreateResponseMessageJson
            { content = cm ^. #content
            , tts = cm ^. #tts
            , embeds = cm ^. #embeds
            , allowedMentions = cm ^. #allowedMentions
            , components = cm ^. #components
            , attachments = attachments
            , flags = flags
            }
        jsonBody =
          InteractionCallback
            { type_ = UpdateMessageType
            , data_ = Just . toJSON $ jsonData
            }
    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonBody) : files)
    postWith' body u o
  action (CreateResponseAutocomplete _ _ ao) =
    let jsonBody =
          InteractionCallback
            { type_ = ApplicationCommandAutocompleteResultType
            , data_ = Just . toJSON $ ao
            }
     in postWith' (ReqBodyJson jsonBody)
  action (CreateResponseModal _ _ mo) =
    let jsonBody =
          InteractionCallback
            { type_ = ModalType
            , data_ = Just . toJSON $ mo
            }
     in postWith' (ReqBodyJson jsonBody)
  action (GetOriginalInteractionResponse _ _) = getWith
  action (EditOriginalInteractionResponse _ _ cm) = \u o -> do
    let filePart CreateMessageAttachment {filename, content} n =
          (partLBS @IO [fmt|files[{n}]|] content)
            { partFilename = Just (T.unpack filename)
            , partContentType = Just (defaultMimeLookup filename)
            }
        attachmentPart CreateMessageAttachment {filename, description} n =
          CreateMessageAttachmentJson n filename description
        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]
        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments
        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral
        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds
        flags = foo ephemeral suppressEmbeds (.|.)
        jsonData =
          CreateResponseMessageJson
            { content = cm ^. #content
            , tts = cm ^. #tts
            , embeds = cm ^. #embeds
            , allowedMentions = cm ^. #allowedMentions
            , components = cm ^. #components
            , attachments = attachments
            , flags = flags
            }
        jsonBody =
          InteractionCallback
            { type_ = UpdateMessageType
            , data_ = Just . toJSON $ jsonData
            }
    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonBody) : files)
    patchWith' body u o
  action (DeleteOriginalInteractionResponse _ _) = deleteWith
  action (CreateFollowupMessage _ _ cm) = \u o -> do
    let filePart CreateMessageAttachment {filename, content} n =
          (partLBS @IO [fmt|files[{n}]|] content)
            { partFilename = Just (T.unpack filename)
            , partContentType = Just (defaultMimeLookup filename)
            }
        attachmentPart CreateMessageAttachment {filename, description} n =
          CreateMessageAttachmentJson n filename description
        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]
        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments
        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral
        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds
        flags = foo ephemeral suppressEmbeds (.|.)
        jsonData =
          CreateResponseMessageJson
            { content = cm ^. #content
            , tts = cm ^. #tts
            , embeds = cm ^. #embeds
            , allowedMentions = cm ^. #allowedMentions
            , components = cm ^. #components
            , attachments = attachments
            , flags = flags
            }
    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonData) : files)
    postWith' body u o
  action GetFollowupMessage {} = getWith
  action (EditFollowupMessage _ _ _ cm) = \u o -> do
    let filePart CreateMessageAttachment {filename, content} n =
          (partLBS @IO [fmt|files[{n}]|] content)
            { partFilename = Just (T.unpack filename)
            , partContentType = Just (defaultMimeLookup filename)
            }
        attachmentPart CreateMessageAttachment {filename, description} n =
          CreateMessageAttachmentJson n filename description
        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]
        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments
        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral
        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds
        flags = foo ephemeral suppressEmbeds (.|.)
        jsonData =
          CreateResponseMessageJson
            { content = cm ^. #content
            , tts = cm ^. #tts
            , embeds = cm ^. #embeds
            , allowedMentions = cm ^. #allowedMentions
            , components = cm ^. #components
            , attachments = attachments
            , flags = flags
            }
    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonData) : files)
    patchWith' body u o
  action DeleteFollowupMessage {} = deleteWith