-- | 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 Data.Default.Class import Data.Monoid import Data.Text qualified as T import Data.Text.Lazy qualified as L import Optics import Polysemy qualified as P import Polysemy.Error qualified as P -- | A wrapper type for allowing mentions newtype TMention a = TMention (Snowflake a) deriving stock (Show) {- | 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