calamity-0.8.0.0: Calamity/Types/Model/Channel/Component.hs
{-# LANGUAGE TemplateHaskell #-}
-- | Message components
module Calamity.Types.Model.Channel.Component (
CustomID (..),
Component (..),
Button (..),
LinkButton (..),
button,
button',
lbutton,
lbutton',
ButtonStyle (..),
Select (..),
select,
SelectOption (..),
sopt,
TextInput (..),
TextInputStyle (..),
textInput,
ComponentType (..),
componentType,
) where
import Calamity.Internal.Utils (CalamityToJSON (..), CalamityToJSON' (..), (.=), (.?=))
import Calamity.Types.Model.Guild.Emoji
import Control.Monad (replicateM)
import Data.Aeson ((.!=), (.:), (.:?))
import Data.Aeson qualified as Aeson
import Data.Maybe (catMaybes)
import Data.Scientific (toBoundedInteger)
import Data.Text qualified as T
import Optics.TH
import System.Random (Uniform)
import System.Random.Stateful (Uniform (uniformM), UniformRange (uniformRM))
import TextShow.TH
newtype CustomID = CustomID T.Text
deriving stock (Eq, Ord, Show)
deriving (Aeson.ToJSON, Aeson.FromJSON) via T.Text
$(deriveTextShow ''CustomID)
instance Uniform CustomID where
uniformM = ((CustomID . T.pack) <$>) . replicateM 16 . uniformRM ('a', 'z')
data ComponentType
= ActionRowType
| ButtonType
| SelectType
| TextInputType
deriving (Eq, Show)
$(deriveTextShow ''ComponentType)
instance Aeson.ToJSON ComponentType where
toJSON x = Aeson.toJSON @Int $ case x of
ActionRowType -> 1
ButtonType -> 2
SelectType -> 3
TextInputType -> 4
toEncoding x = Aeson.toEncoding @Int $ case x of
ActionRowType -> 1
ButtonType -> 2
SelectType -> 3
TextInputType -> 4
instance Aeson.FromJSON ComponentType where
parseJSON = Aeson.withScientific "Components.ComponentType" $ \n -> case toBoundedInteger @Int n of
Just 1 -> pure ActionRowType
Just 2 -> pure ButtonType
Just 3 -> pure SelectType
Just 4 -> pure TextInputType
_ -> fail $ "Invalid ComponentType: " <> show n
data ButtonStyle
= ButtonPrimary
| ButtonSecondary
| ButtonSuccess
| ButtonDanger
| ButtonLink
deriving (Eq, Show)
$(deriveTextShow ''ButtonStyle)
instance Aeson.ToJSON ButtonStyle where
toJSON t = Aeson.toJSON @Int $ case t of
ButtonPrimary -> 1
ButtonSecondary -> 2
ButtonSuccess -> 3
ButtonDanger -> 4
ButtonLink -> 5
toEncoding t = Aeson.toEncoding @Int $ case t of
ButtonPrimary -> 1
ButtonSecondary -> 2
ButtonSuccess -> 3
ButtonDanger -> 4
ButtonLink -> 5
instance Aeson.FromJSON ButtonStyle where
parseJSON = Aeson.withScientific "Components.ButtonStyle" $ \n -> case toBoundedInteger @Int n of
Just v -> case v of
1 -> pure ButtonPrimary
2 -> pure ButtonSecondary
3 -> pure ButtonSuccess
4 -> pure ButtonDanger
5 -> pure ButtonLink
_ -> fail $ "Invalid ButtonStyle: " <> show n
Nothing -> fail $ "Invalid ButtonStyle: " <> show n
data Button = Button
{ style :: ButtonStyle
, label :: Maybe T.Text
, emoji :: Maybe RawEmoji
, disabled :: Bool
, customID :: CustomID
}
deriving (Show)
deriving (Aeson.ToJSON) via CalamityToJSON Button
instance CalamityToJSON' Button where
toPairs Button {..} =
[ "style" .= style
, "label" .?= label
, "emoji" .?= emoji
, "disabled" .= disabled
, "custom_id" .= customID
, "type" .= ButtonType
]
$(deriveTextShow ''Button)
$(makeFieldLabelsNoPrefix ''Button)
instance Aeson.FromJSON Button where
parseJSON = Aeson.withObject "Components.Button" $ \v ->
Button
<$> v .: "style"
<*> v .:? "label"
<*> v .:? "emoji"
<*> v .:? "disabled" .!= False
<*> v .: "custom_id"
data LinkButton = LinkButton
{ style :: ButtonStyle
, label :: Maybe T.Text
, emoji :: Maybe RawEmoji
, url :: T.Text
, disabled :: Bool
}
deriving (Show)
deriving (Aeson.ToJSON) via CalamityToJSON LinkButton
instance CalamityToJSON' LinkButton where
toPairs LinkButton {..} =
[ "style" .= style
, "label" .?= label
, "emoji" .?= emoji
, "url" .= url
, "disabled" .= disabled
, "type" .= ButtonType
]
instance Aeson.FromJSON LinkButton where
parseJSON = Aeson.withObject "Components.Linkbutton" $ \v ->
LinkButton
<$> v .: "style"
<*> v .:? "label"
<*> v .:? "emoji"
<*> v .: "url"
<*> v .:? "disabled" .!= False
$(deriveTextShow ''LinkButton)
$(makeFieldLabelsNoPrefix ''LinkButton)
{- | Constuct a non-disabled 'Button' with the given 'ButtonStyle' and 'CustomID',
all other fields are set to 'Nothing'
-}
button :: ButtonStyle -> CustomID -> Button
button s = Button s Nothing Nothing False
{- | Constuct a non-disabled 'Button' with the given 'ButtonStyle', 'CustomID',
and label, all other fields are set to 'Nothing'
-}
button' :: ButtonStyle -> T.Text -> CustomID -> Button
button' s l = Button s (Just l) Nothing False
{- | Constuct a non-disabled 'LinkButton' with the given 'ButtonStyle', link, all
other fields are set to 'Nothing'
-}
lbutton ::
ButtonStyle ->
-- | The link to use
T.Text ->
LinkButton
lbutton s lnk = LinkButton s Nothing Nothing lnk False
{- | Constuct a non-disabled 'LinkButton' with the given 'ButtonStyle', link,
and label, all other fields are set to 'Nothing'
-}
lbutton' ::
ButtonStyle ->
-- | The link to use
T.Text ->
-- | The label to use
T.Text ->
LinkButton
lbutton' s lnk lbl = LinkButton s (Just lbl) Nothing lnk False
data SelectOption = SelectOption
{ label :: T.Text
, value :: T.Text
, description :: Maybe T.Text
, emoji :: Maybe RawEmoji
, default_ :: Bool
}
deriving (Show)
deriving (Aeson.ToJSON) via CalamityToJSON SelectOption
instance CalamityToJSON' SelectOption where
toPairs SelectOption {..} =
[ "label" .= label
, "value" .= value
, "description" .?= description
, "emoji" .?= emoji
, "default" .= default_
]
instance Aeson.FromJSON SelectOption where
parseJSON = Aeson.withObject "Components.SelectOption" $ \v ->
SelectOption
<$> v .: "label"
<*> v .: "value"
<*> v .:? "description"
<*> v .:? "emoji"
<*> v .:? "default" .!= False
$(deriveTextShow ''SelectOption)
$(makeFieldLabelsNoPrefix ''SelectOption)
data Select = Select
{ options :: [SelectOption]
, placeholder :: Maybe T.Text
, minValues :: Maybe Int
, maxValues :: Maybe Int
, disabled :: Bool
, customID :: CustomID
}
deriving (Show)
deriving (Aeson.ToJSON) via CalamityToJSON Select
instance CalamityToJSON' Select where
toPairs Select {..} =
[ "options" .= options
, "placeholder" .?= placeholder
, "min_values" .?= minValues
, "max_values" .?= maxValues
, "disabled" .= disabled
, "custom_id" .= customID
, "type" .= SelectType
]
instance Aeson.FromJSON Select where
parseJSON = Aeson.withObject "Components.Select" $ \v ->
Select
<$> v .: "options"
<*> v .:? "placeholder"
<*> v .:? "min_values"
<*> v .:? "max_values"
<*> v .:? "disabled" .!= False
<*> v .: "custom_id"
$(deriveTextShow ''Select)
$(makeFieldLabelsNoPrefix ''Select)
select :: [SelectOption] -> CustomID -> Select
select o = Select o Nothing Nothing Nothing False
sopt ::
-- | Label
T.Text ->
-- | Value
T.Text ->
SelectOption
sopt l v = SelectOption l v Nothing Nothing False
data TextInputStyle
= TextInputShort
| TextInputParagraph
deriving (Show)
$(deriveTextShow ''TextInputStyle)
instance Aeson.ToJSON TextInputStyle where
toJSON t = Aeson.toJSON @Int $ case t of
TextInputShort -> 1
TextInputParagraph -> 2
toEncoding t = Aeson.toEncoding @Int $ case t of
TextInputShort -> 1
TextInputParagraph -> 2
instance Aeson.FromJSON TextInputStyle where
parseJSON = Aeson.withScientific "Components.TextInputStyle" $ \n -> case toBoundedInteger @Int n of
Just v -> case v of
1 -> pure TextInputShort
2 -> pure TextInputParagraph
_ -> fail $ "Invalid TextInputStyle: " <> show n
Nothing -> fail $ "Invalid TextInputStyle: " <> show n
data TextInput = TextInput
{ style :: TextInputStyle
, label :: T.Text
, minLength :: Maybe Int
, maxLength :: Maybe Int
, required :: Bool
, value :: Maybe T.Text
, placeholder :: Maybe T.Text
, customID :: CustomID
}
deriving (Show)
deriving (Aeson.ToJSON) via CalamityToJSON TextInput
instance CalamityToJSON' TextInput where
toPairs TextInput {..} =
[ "style" .= style
, "label" .= label
, "min_length" .= minLength
, "max_length" .= maxLength
, "required" .= required
, "value" .= value
, "placeholder" .= placeholder
, "custom_id" .= customID
, "type" .= TextInputType
]
instance Aeson.FromJSON TextInput where
parseJSON = Aeson.withObject "Components.TextInput" $ \v ->
TextInput
<$> v .: "style"
<*> v .: "label"
<*> v .:? "min_length"
<*> v .:? "max_length"
<*> v .:? "required" .!= False
<*> v .:? "value"
<*> v .:? "placeholder"
<*> v .: "custom_id"
$(deriveTextShow ''TextInput)
$(makeFieldLabelsNoPrefix ''TextInput)
textInput ::
TextInputStyle ->
-- | Label
T.Text ->
CustomID ->
TextInput
textInput s l = TextInput s l Nothing Nothing True Nothing Nothing
data Component
= ActionRow' [Component]
| Button' Button
| LinkButton' LinkButton
| Select' Select
| TextInput' TextInput
deriving (Show)
$(deriveTextShow ''Component)
instance Aeson.ToJSON Component where
toJSON t =
case t of
ActionRow' xs -> Aeson.object . catMaybes $ ["components" .= xs, "type" .= ActionRowType]
Button' b -> Aeson.toJSON b
LinkButton' lb -> Aeson.toJSON lb
Select' s -> Aeson.toJSON s
TextInput' ti -> Aeson.toJSON ti
toEncoding t =
case t of
ActionRow' xs -> Aeson.pairs . mconcat . catMaybes $ ["components" .= xs, "type" .= ActionRowType]
Button' b -> Aeson.toEncoding b
LinkButton' lb -> Aeson.toEncoding lb
Select' s -> Aeson.toEncoding s
TextInput' ti -> Aeson.toEncoding ti
instance Aeson.FromJSON Component where
parseJSON = Aeson.withObject "Component" $ \v -> do
type_ :: ComponentType <- v .: "type"
case type_ of
ActionRowType -> ActionRow' <$> v .: "components"
ButtonType -> do
cid :: Maybe CustomID <- v .:? "custom_id"
url :: Maybe T.Text <- v .:? "url"
case (cid, url) of
(Just _, _) -> Button' <$> Aeson.parseJSON (Aeson.Object v)
(_, Just _) -> LinkButton' <$> Aeson.parseJSON (Aeson.Object v)
_ -> fail $ "Impossible button: " <> show v
SelectType -> Select' <$> Aeson.parseJSON (Aeson.Object v)
TextInputType -> TextInput' <$> Aeson.parseJSON (Aeson.Object v)
componentType :: Component -> ComponentType
componentType (ActionRow' _) = ActionRowType
componentType (Button' _) = ButtonType
componentType (LinkButton' _) = ButtonType
componentType (Select' _) = SelectType
componentType (TextInput' _) = TextInputType