calamity-0.3.0.0: Calamity/Types/Model/Channel/Component.hs
-- | 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.AesonThings
import Calamity.Types.Model.Guild.Emoji
import Control.Monad (replicateM)
import Data.Aeson
import Data.Scientific (toBoundedInteger)
import qualified Data.Text as T
import GHC.Generics
import System.Random (Uniform)
import System.Random.Stateful (Uniform (uniformM), UniformRange (uniformRM))
import TextShow
import qualified TextShow.Generic as TSG
newtype CustomID = CustomID T.Text
deriving stock (Eq, Ord, Show, Generic)
deriving (TextShow) via TSG.FromGeneric CustomID
deriving (ToJSON, FromJSON) via T.Text
instance Uniform CustomID where
uniformM = ((CustomID . T.pack) <$>) . replicateM 16 . uniformRM ('a', 'z')
data Button = Button
{ style :: ButtonStyle
, label :: Maybe T.Text
, emoji :: Maybe RawEmoji
, disabled :: Bool
, customID :: CustomID
}
deriving (Show, Generic)
deriving (TextShow) via TSG.FromGeneric Button
deriving (ToJSON) via CalamityJSONKeepNothing Button
deriving
(FromJSON)
via WithSpecialCases
'["disabled" `IfNoneThen` DefaultToFalse]
Button
data LinkButton = LinkButton
{ stype :: ButtonStyle
, label :: Maybe T.Text
, emoji :: Maybe RawEmoji
, url :: T.Text
, disabled :: Bool
}
deriving (Show, Generic)
deriving (TextShow) via TSG.FromGeneric LinkButton
deriving (ToJSON) via CalamityJSONKeepNothing LinkButton
deriving
(FromJSON)
via WithSpecialCases
'["disabled" `IfNoneThen` DefaultToFalse]
LinkButton
data ButtonStyle
= ButtonPrimary
| ButtonSecondary
| ButtonSuccess
| ButtonDanger
| ButtonLink
deriving (Eq, Show, Generic)
deriving (TextShow) via TSG.FromGeneric ButtonStyle
instance ToJSON ButtonStyle where
toJSON t = toJSON @Int $ case t of
ButtonPrimary -> 1
ButtonSecondary -> 2
ButtonSuccess -> 3
ButtonDanger -> 4
ButtonLink -> 5
instance FromJSON ButtonStyle where
parseJSON = withScientific "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
{- | 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 Select = Select
{ options :: [SelectOption]
, placeholder :: Maybe T.Text
, minValues :: Maybe Int
, maxValues :: Maybe Int
, disabled :: Bool
, customID :: CustomID
}
deriving (Show, Generic)
deriving (TextShow) via TSG.FromGeneric Select
deriving (ToJSON) via CalamityJSONKeepNothing Select
deriving
(FromJSON)
via WithSpecialCases
'["disabled" `IfNoneThen` DefaultToFalse]
Select
data SelectOption = SelectOption
{ label :: T.Text
, value :: T.Text
, description :: Maybe T.Text
, emoji :: Maybe RawEmoji
, default_ :: Bool
}
deriving (Show, Generic)
deriving (TextShow) via TSG.FromGeneric SelectOption
deriving (ToJSON) via CalamityJSONKeepNothing SelectOption
deriving
(FromJSON)
via WithSpecialCases
'["default" `IfNoneThen` DefaultToFalse]
SelectOption
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 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, Generic)
deriving (TextShow) via TSG.FromGeneric TextInput
deriving (ToJSON) via CalamityJSONKeepNothing TextInput
deriving
(FromJSON)
via WithSpecialCases
'["required" `IfNoneThen` DefaultToFalse]
TextInput
data TextInputStyle
= TextInputShort
| TextInputParagraph
deriving (Show, Generic)
deriving (TextShow) via TSG.FromGeneric TextInputStyle
instance ToJSON TextInputStyle where
toJSON t = toJSON @Int $ case t of
TextInputShort -> 1
TextInputParagraph -> 2
instance FromJSON TextInputStyle where
parseJSON = withScientific "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
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, Generic)
deriving (TextShow) via TSG.FromGeneric Component
instance ToJSON Component where
toJSON t =
let (Object inner, type_) = case t of
ActionRow' xs -> (Object ("components" .= xs), 1 :: Int)
Button' b -> (toJSON b, 2 :: Int)
LinkButton' lb -> (toJSON lb, 2 :: Int)
Select' s -> (toJSON s, 3 :: Int)
TextInput' ti -> (toJSON ti, 4 :: Int)
in Object (inner <> ("type" .= type_))
instance FromJSON Component where
parseJSON = withObject "Component" $ \v -> do
type_ :: Int <- v .: "type"
case type_ of
1 -> ActionRow' <$> v .: "components"
2 -> do
cid :: Maybe CustomID <- v .:? "custom_id"
url :: Maybe T.Text <- v .:? "url"
case (cid, url) of
(Just _, _) -> Button' <$> parseJSON (Object v)
(_, Just _) -> LinkButton' <$> parseJSON (Object v)
_ -> fail $ "Impossible button: " <> show v
3 -> Select' <$> parseJSON (Object v)
4 -> TextInput' <$> parseJSON (Object v)
_ -> fail $ "Invalid ComponentType: " <> show type_
componentType :: Component -> ComponentType
componentType (ActionRow' _) = ActionRowType
componentType (Button' _) = ButtonType
componentType (LinkButton' _) = ButtonType
componentType (Select' _) = SelectType
componentType (TextInput' _) = TextInputType
data ComponentType
= ActionRowType
| ButtonType
| SelectType
| TextInputType
deriving (Eq, Show, Generic)
deriving (TextShow) via TSG.FromGeneric ComponentType
instance ToJSON ComponentType where
toJSON x = toJSON @Int $ case x of
ActionRowType -> 1
ButtonType -> 2
SelectType -> 3
TextInputType -> 4
instance FromJSON ComponentType where
parseJSON = withScientific "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