packages feed

calamity-0.8.0.0: Calamity/Commands/Context.hs

{-# LANGUAGE TemplateHaskell #-}

-- | Command invokation context
module Calamity.Commands.Context (
  CalamityCommandContext (..),
  FullContext (..),
  useFullContext,
  LightContext (..),
  useLightContext,
) where

import Calamity.Cache.Eff
import Calamity.Commands.Types
import Calamity.Internal.Utils
import Calamity.Types.Model.Channel
import Calamity.Types.Model.Guild
import Calamity.Types.Model.User
import Calamity.Types.Snowflake
import Calamity.Types.Tellable
import CalamityCommands.Context qualified as CC
import Control.Applicative
import Control.Monad
import Data.Text qualified as T
import Optics
import Polysemy qualified as P
import Polysemy.Fail qualified as P
import TextShow qualified

class CommandContext c => CalamityCommandContext c where
  -- | The id of the channel that invoked this command
  ctxChannelID :: c -> Snowflake Channel

  -- | The id of the guild the command was invoked in, if in a guild
  ctxGuildID :: c -> Maybe (Snowflake Guild)

  -- | The id of the user that invoked this command
  ctxUserID :: c -> Snowflake User

  -- | The message that triggered this command
  ctxMessage :: c -> Message

-- | Invokation context for commands
data FullContext = FullContext
  { message :: Message
  -- ^ The message that the command was invoked from
  , guild :: Maybe Guild
  -- ^ If the command was sent in a guild, this will be present
  , member :: Maybe Member
  -- ^ The member that invoked the command, if in a guild
  --
  -- Note: If discord sent a member with the message, this is used; otherwise
  -- we try to fetch the member from the cache.
  , channel :: Channel
  -- ^ The channel the command was invoked from
  , user :: User
  -- ^ The user that invoked the command
  , command :: Command FullContext
  -- ^ The command that was invoked
  , prefix :: T.Text
  -- ^ The prefix that was used to invoke the command
  , unparsedParams :: T.Text
  -- ^ The message remaining after consuming the prefix
  }
  deriving (Show)
  deriving (TextShow.TextShow) via TextShow.FromStringShow FullContext
  deriving (HasID Channel) via HasIDField "channel" FullContext
  deriving (HasID Message) via HasIDField "message" FullContext
  deriving (HasID User) via HasIDField "user" FullContext

$(makeFieldLabelsNoPrefix ''FullContext)

instance CC.CommandContext IO FullContext () where
  ctxPrefix = (^. #prefix)
  ctxCommand = (^. #command)
  ctxUnparsedParams = (^. #unparsedParams)

instance CalamityCommandContext FullContext where
  ctxChannelID = getID . (^. #channel)
  ctxGuildID c = getID <$> c ^. #guild
  ctxUserID = getID . (^. #user)
  ctxMessage = (^. #message)

instance Tellable FullContext where
  getChannel = pure . ctxChannelID

useFullContext :: P.Member CacheEff r => P.Sem (CC.ConstructContext (Message, User, Maybe Member) FullContext IO () ': r) a -> P.Sem r a
useFullContext =
  P.interpret
    ( \case
        CC.ConstructContext (pre, cmd, up) (msg, usr, mem) -> buildContext msg usr mem pre cmd up
    )

buildContext :: P.Member CacheEff r => Message -> User -> Maybe Member -> T.Text -> Command FullContext -> T.Text -> P.Sem r (Maybe FullContext)
buildContext msg usr mem prefix command unparsed = (rightToMaybe <$>) . P.runFail $ do
  guild <- join <$> getGuild `traverse` (msg ^. #guildID)
  let member = mem <|> guild ^? _Just % #members % ix (coerceSnowflake $ getID @User msg)
  let gchan = guild ^? _Just % #channels % ix (coerceSnowflake $ getID @Channel msg)
  Just channel <- case gchan of
    Just chan -> pure . pure $ GuildChannel' chan
    Nothing -> DMChannel' <<$>> getDM (coerceSnowflake $ getID @Channel msg)

  pure $ FullContext msg guild member channel usr command prefix unparsed

-- | A lightweight context that doesn't need any cache information
data LightContext = LightContext
  { message :: Message
  -- ^ The message that the command was invoked from
  , guildID :: Maybe (Snowflake Guild)
  -- ^ If the command was sent in a guild, this will be present
  , channelID :: Snowflake Channel
  -- ^ The channel the command was invoked from
  , user :: User
  -- ^ The user that invoked the command
  , member :: Maybe Member
  -- ^ The member that triggered the command.
  --
  -- Note: Only sent if discord sent the member object with the message.
  , command :: Command LightContext
  -- ^ The command that was invoked
  , prefix :: T.Text
  -- ^ The prefix that was used to invoke the command
  , unparsedParams :: T.Text
  -- ^ The message remaining after consuming the prefix
  }
  deriving (Show)
  deriving (TextShow.TextShow) via TextShow.FromStringShow LightContext
  deriving (HasID Channel) via HasIDField "channelID" LightContext
  deriving (HasID Message) via HasIDField "message" LightContext
  deriving (HasID User) via HasIDField "user" LightContext

$(makeFieldLabelsNoPrefix ''LightContext)

instance CC.CommandContext IO LightContext () where
  ctxPrefix = (^. #prefix)
  ctxCommand = (^. #command)
  ctxUnparsedParams = (^. #unparsedParams)

instance CalamityCommandContext LightContext where
  ctxChannelID = (^. #channelID)
  ctxGuildID = (^. #guildID)
  ctxUserID = (^. #user % #id)
  ctxMessage = (^. #message)

instance Tellable LightContext where
  getChannel = pure . ctxChannelID

useLightContext :: P.Sem (CC.ConstructContext (Message, User, Maybe Member) LightContext IO () ': r) a -> P.Sem r a
useLightContext =
  P.interpret
    ( \case
        CC.ConstructContext (pre, cmd, up) (msg, usr, mem) ->
          pure . Just $ LightContext msg (msg ^. #guildID) (msg ^. #channelID) usr mem cmd pre up
    )