calamity-0.3.0.0: Calamity/Types/Tellable.hs
-- | Things that are messageable
module Calamity.Types.Tellable (
ToMessage (..),
Tellable (..),
TMention (..),
tell,
reply,
runToMessage,
) where
import Calamity.Client.Types
import Calamity.HTTP.Channel (
AllowedMentions,
ChannelRequest (CreateMessage),
CreateMessageAttachment,
CreateMessageOptions,
)
import Calamity.HTTP.Internal.Request (invoke)
import Calamity.HTTP.Internal.Types (RestError)
import Calamity.HTTP.User (UserRequest (CreateDM))
import Calamity.Types.Model.Channel
import Calamity.Types.Model.Guild.Member (Member)
import Calamity.Types.Model.Guild.Role (Role)
import Calamity.Types.Model.User
import Calamity.Types.Snowflake
import Control.Lens
import Data.Default.Class
import Data.Monoid
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
import GHC.Generics
import qualified Polysemy as P
import qualified Polysemy.Error as P
-- | A wrapper type for allowing mentions
newtype TMention a = TMention (Snowflake a)
deriving stock (Show, Generic)
{- | Things that can be used to send a message
Can be used to compose text, embeds, and files. /e.g./
@
'intoMsg' @'L.Text' "A message" '<>' 'intoMsg' @'Embed' ('def' '&' #description '?~' "Embed description")
@
-}
class ToMessage a where
-- | Turn @a@ into a 'CreateMessageOptions' builder
intoMsg :: a -> Endo CreateMessageOptions
-- | Message content, '(<>)' concatenates the content
instance ToMessage L.Text where
intoMsg t = Endo (#content %~ (<> Just (L.toStrict t)))
-- | Message content, '(<>)' concatenates the content
instance ToMessage T.Text where
intoMsg t = Endo (#content %~ (<> Just t))
-- | Message content, '(<>)' concatenates the content
instance ToMessage String where
intoMsg t = Endo (#content %~ (<> Just (T.pack t)))
-- | Message embed, '(<>)' appends a new embed
instance ToMessage Embed where
intoMsg e = Endo (#embeds %~ (<> Just [e]))
-- | Message attachments, '(<>)' appends a new file
instance ToMessage CreateMessageAttachment where
intoMsg a = Endo (#attachments %~ (<> Just [a]))
-- | Allowed mentions, '(<>)' combines allowed mentions
instance ToMessage AllowedMentions where
intoMsg m = Endo (#allowedMentions %~ (<> Just m))
-- | Add a 'User' id to the list of allowed user mentions
instance ToMessage (TMention User) where
intoMsg (TMention s) = intoMsg (def @AllowedMentions & #users <>~ [s])
-- | Add a 'Member' id to the list of allowed user mentions
instance ToMessage (TMention Member) where
intoMsg (TMention s) = intoMsg (def @AllowedMentions & #users <>~ [coerceSnowflake s])
-- | Add a 'Role' id to the list of allowed role mentions
instance ToMessage (TMention Role) where
intoMsg (TMention s) = intoMsg (def @AllowedMentions & #roles <>~ [s])
fixupActionRow :: Component -> Component
fixupActionRow r@(ActionRow' _) = r
fixupActionRow x = ActionRow' [x]
{- | Add many components to a message.
Each component will be wrapped in a singleton ActionRow if not already
-}
instance ToMessage [Component] where
intoMsg c = Endo (#components %~ (<> Just (map fixupActionRow c)))
-- | Add an row of 'Button's to the message
instance ToMessage [Button] where
intoMsg c = Endo (#components %~ (<> Just [ActionRow' . map Button' $ c]))
-- | Add an row of 'LinkButton's to the message
instance ToMessage [LinkButton] where
intoMsg c = Endo (#components %~ (<> Just [ActionRow' . map LinkButton' $ c]))
-- | Add an row of 'Select's to the message
instance ToMessage [Select] where
intoMsg c = Endo (#components %~ (<> Just [ActionRow' . map Select' $ c]))
{- | Add a singleton row containing a 'Component' to the message
If the component is not already an actionrow, it is wrapped in a singleton row
-}
instance ToMessage Component where
intoMsg c = Endo (#components %~ (<> Just [fixupActionRow c]))
-- | Add a singleton row containing a 'Button' to the message,
instance ToMessage Button where
intoMsg c = Endo (#components %~ (<> Just [ActionRow' [Button' c]]))
-- | Add a singleton row containing a 'LinkButton' to the message,
instance ToMessage LinkButton where
intoMsg c = Endo (#components %~ (<> Just [ActionRow' [LinkButton' c]]))
-- | Add a singleton row containing a 'Select' to the message,
instance ToMessage Select where
intoMsg c = Endo (#components %~ (<> Just [ActionRow' [Select' c]]))
-- | Set a 'MessageReference' as the message to reply to
instance ToMessage MessageReference where
intoMsg ref = Endo (#messageReference ?~ ref)
instance ToMessage (Endo CreateMessageOptions) where
intoMsg = Prelude.id
instance ToMessage (CreateMessageOptions -> CreateMessageOptions) where
intoMsg = Endo
instance ToMessage CreateMessageOptions where
intoMsg = Endo . const
class Tellable a where
getChannel :: (BotC r, P.Member (P.Error RestError) r) => a -> P.Sem r (Snowflake Channel)
runToMessage :: ToMessage a => a -> CreateMessageOptions
runToMessage = flip appEndo def . intoMsg
{- | Send a message to something that is messageable
To send a string literal you'll probably want to use @TypeApplication@ to
specify the type of @msg@
==== Examples
Sending a string:
@
'void' $ 'tell' @'Text' m ("Somebody told me to tell you about: " '<>' s)
@
-}
tell :: forall msg r t. (BotC r, ToMessage msg, Tellable t) => t -> msg -> P.Sem r (Either RestError Message)
tell target (runToMessage -> msg) = P.runError $ do
cid <- getChannel target
r <- invoke $ CreateMessage cid msg
P.fromEither r
{- | Create a reply to an existing message in the same channel
To send a string literal you'll probably want to use @TypeApplication@ to
specify the type of @msg@
==== Examples
Sending a string:
@
'void' $ 'reply' @'Text' msgToReplyTo ("Somebody told me to tell you about: " '<>' s)
@
-}
reply :: forall msg r t. (BotC r, ToMessage msg, HasID Channel t, HasID Message t) => t -> msg -> P.Sem r (Either RestError Message)
reply target msg = P.runError $ do
let msg' = runToMessage (intoMsg msg <> intoMsg (MessageReference (Just $ getID @Message target) (Just $ getID @Channel target) Nothing False))
r <- invoke $ CreateMessage (getID @Channel target) msg'
P.fromEither r
instance Tellable DMChannel where
getChannel = pure . getID
instance Tellable (Snowflake Channel) where
getChannel = pure
instance Tellable Channel where
getChannel = pure . getID
instance Tellable (Snowflake DMChannel) where
getChannel = pure . coerceSnowflake
instance Tellable TextChannel where
getChannel = pure . getID
instance Tellable (Snowflake TextChannel) where
getChannel = pure . coerceSnowflake
instance Tellable Message where
getChannel = pure . getID
messageUser :: (BotC r, P.Member (P.Error RestError) r, HasID User a) => a -> P.Sem r (Snowflake Channel)
messageUser (getID @User -> uid) = do
c <- invoke $ CreateDM uid
getID <$> P.fromEither c
instance Tellable (Snowflake Member) where
getChannel = messageUser . coerceSnowflake @_ @User
instance Tellable Member where
getChannel = messageUser
instance Tellable User where
getChannel = messageUser
instance Tellable (Snowflake User) where
getChannel = messageUser