packages feed

calamity-0.8.0.0: Calamity/HTTP/Guild.hs

{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-partial-type-signatures #-}

-- | Guild endpoints
module Calamity.HTTP.Guild (
  GuildRequest (..),
  CreateGuildData (..),
  ModifyGuildData (..),
  ChannelCreateData (..),
  ChannelPosition (..),
  ListMembersOptions (..),
  AddGuildMemberData (..),
  ModifyGuildMemberData,
  modifyGuildMemberNick,
  modifyGuildMemberRoles,
  modifyGuildMemberMute,
  modifyGuildMemberDeaf,
  modifyGuildMemberChannelID,
  CreateGuildBanData (..),
  ModifyGuildRoleData,
  modifyGuildRoleName,
  modifyGuildRolePermissions,
  modifyGuildRoleColour,
  modifyGuildRoleHoist,
  modifyGuildRoleMentionable,
  ModifyGuildRolePositionsData (..),
) where

import Calamity.HTTP.Internal.Request
import Calamity.HTTP.Internal.Route
import Calamity.Internal.IntColour
import Calamity.Internal.Utils (CalamityToJSON (..), CalamityToJSON' (..), (.=), (.?=))
import Calamity.Types.Model.Channel
import Calamity.Types.Model.Guild
import Calamity.Types.Model.User
import Calamity.Types.Model.Voice
import Calamity.Types.Snowflake
import Data.Aeson qualified as Aeson
import Data.Aeson.KeyMap qualified as K
import Data.Colour (Colour)
import Data.Default.Class
import Data.Text (Text)
import Network.HTTP.Req
import Optics

data CreateGuildData = CreateGuildData
  { name :: Text
  , region :: Text
  , icon :: Text
  , verificationLevel :: Integer -- TODO: enums for these
  , defaultMessageNotifications :: Integer
  , explicitContentFilter :: Integer
  , roles :: [Role]
  , channels :: [Partial Channel]
  }
  deriving (Show)
  deriving (Aeson.ToJSON) via CalamityToJSON CreateGuildData

instance CalamityToJSON' CreateGuildData where
  toPairs CreateGuildData {..} =
    [ "name" .= name
    , "region" .= region
    , "icon" .= icon
    , "verification_level" .= verificationLevel
    , "default_message_notifications" .= defaultMessageNotifications
    , "explicit_content_filter" .= explicitContentFilter
    , "roles" .= roles
    , "channels" .= channels
    ]

data ModifyGuildData = ModifyGuildData
  { name :: Maybe Text
  , region :: Maybe Text
  , icon :: Maybe Text
  , verificationLevel :: Maybe Integer -- TODO: enums for these
  , defaultMessageNotifications :: Maybe Integer
  , explicitContentFilter :: Maybe Integer
  , afkChannelID :: Maybe (Snowflake GuildChannel)
  , afkTimeout :: Maybe Integer
  , ownerID :: Maybe (Snowflake User)
  , splash :: Maybe Text
  , banner :: Maybe Text
  , systemChannelID :: Maybe (Snowflake GuildChannel)
  }
  deriving (Show)
  deriving (Aeson.ToJSON) via CalamityToJSON ModifyGuildData

instance Default ModifyGuildData where
  def =
    ModifyGuildData
      Nothing
      Nothing
      Nothing
      Nothing
      Nothing
      Nothing
      Nothing
      Nothing
      Nothing
      Nothing
      Nothing
      Nothing

-- TODO: Make this work properly, a la modifyguildmember
instance CalamityToJSON' ModifyGuildData where
  toPairs ModifyGuildData {..} =
    [ "name" .?= name
    , "region" .?= region
    , "icon" .?= icon
    , "verification_level" .?= verificationLevel
    , "default_message_notifications" .?= defaultMessageNotifications
    , "explicit_content_filter" .?= explicitContentFilter
    , "afk_timeout" .?= afkTimeout
    , "owner_id" .?= ownerID
    , "splash" .?= splash
    , "banner" .?= banner
    , "system_channel_id" .?= systemChannelID
    ]

data ChannelCreateData = ChannelCreateData
  { name :: Text
  , type_ :: Maybe ChannelType
  , topic :: Maybe Text
  , bitrate :: Maybe Integer
  , userLimit :: Maybe Integer
  , rateLimitPerUser :: Maybe Integer
  , position :: Maybe Integer
  , permissionOverwrites :: Maybe [Overwrite]
  , parentID :: Maybe (Snowflake Category)
  , nsfw :: Maybe Bool
  }
  deriving (Show)
  deriving (Aeson.ToJSON) via CalamityToJSON ChannelCreateData

instance CalamityToJSON' ChannelCreateData where
  toPairs ChannelCreateData {..} =
    [ "name" .= name
    , "type" .?= type_
    , "topic" .?= topic
    , "bitrate" .?= bitrate
    , "user_limit" .?= userLimit
    , "rate_limit_per_user" .?= rateLimitPerUser
    , "position" .?= position
    , "permission_overwrites" .?= permissionOverwrites
    , "parent_id" .?= parentID
    , "nsfw" .?= nsfw
    ]

data ChannelPosition = ChannelPosition
  { id :: Snowflake GuildChannel
  , position :: Maybe Integer
  , lockPermissions :: Maybe Bool
  , parentID :: Snowflake GuildChannel
  }
  deriving (Show)
  deriving (Aeson.ToJSON) via CalamityToJSON ChannelPosition

instance CalamityToJSON' ChannelPosition where
  toPairs ChannelPosition {..} =
    [ "id" .= id
    , "position" .= position
    , "lock_permissions" .= lockPermissions
    , "parent_id" .= parentID
    ]

data ListMembersOptions = ListMembersOptions
  { limit :: Maybe Integer
  , after :: Maybe (Snowflake User)
  }
  deriving (Show)
  deriving (Aeson.ToJSON) via CalamityToJSON ListMembersOptions

instance Default ListMembersOptions where
  def = ListMembersOptions Nothing Nothing

instance CalamityToJSON' ListMembersOptions where
  toPairs ListMembersOptions {..} =
    [ "limit" .?= limit
    , "after" .?= after
    ]

data SearchMembersOptions = SearchMembersOptions
  { limit :: Maybe Integer
  , query :: Text
  }
  deriving (Show)
  deriving (Aeson.ToJSON) via CalamityToJSON SearchMembersOptions

instance CalamityToJSON' SearchMembersOptions where
  toPairs SearchMembersOptions {..} =
    [ "limit" .?= limit
    , "query" .= query
    ]

data AddGuildMemberData = AddGuildMemberData
  { accessToken :: Text
  , nick :: Maybe Text
  , roles :: Maybe [Snowflake Role]
  , mute :: Maybe Bool
  , deaf :: Maybe Bool
  }
  deriving (Show)
  deriving (Aeson.ToJSON) via CalamityToJSON AddGuildMemberData

instance CalamityToJSON' AddGuildMemberData where
  toPairs AddGuildMemberData {..} =
    [ "access_token" .= accessToken
    , "nick" .?= nick
    , "roles" .?= roles
    , "mute" .?= mute
    , "deaf" .?= deaf
    ]

{- | Parameters to the Modify Guild Member endpoint.

 Use the provided methods (@modifyGuildMemberX@) to create a value with the
 field set, use the Semigroup instance to combine modifications.

 ==== Examples

 >>> encode $ modifyGuildMemberNick (Just "test") <> modifyGuildMemberDeaf Nothing
 "{\"nick\":\"test\",\"deaf\":null}"
-}
newtype ModifyGuildMemberData = ModifyGuildMemberData Aeson.Object
  deriving stock (Show)
  deriving newtype (Aeson.ToJSON, Semigroup, Monoid)

modifyGuildMemberNick :: Maybe Text -> ModifyGuildMemberData
modifyGuildMemberNick v = ModifyGuildMemberData $ K.fromList [("nick", Aeson.toJSON v)]

modifyGuildMemberRoles :: Maybe [Snowflake Role] -> ModifyGuildMemberData
modifyGuildMemberRoles v = ModifyGuildMemberData $ K.fromList [("roles", Aeson.toJSON v)]

modifyGuildMemberMute :: Maybe Bool -> ModifyGuildMemberData
modifyGuildMemberMute v = ModifyGuildMemberData $ K.fromList [("mute", Aeson.toJSON v)]

modifyGuildMemberDeaf :: Maybe Bool -> ModifyGuildMemberData
modifyGuildMemberDeaf v = ModifyGuildMemberData $ K.fromList [("deaf", Aeson.toJSON v)]

modifyGuildMemberChannelID :: Maybe (Snowflake VoiceChannel) -> ModifyGuildMemberData
modifyGuildMemberChannelID v = ModifyGuildMemberData $ K.fromList [("channel_id", Aeson.toJSON v)]

data GetGuildBansOptions = GetGuildBansOptions
  { limit :: Maybe Int
  , before :: Maybe Int
  , after :: Maybe Int
  }
  deriving (Show)

instance Default GetGuildBansOptions where
  def = GetGuildBansOptions Nothing Nothing Nothing

data CreateGuildBanData = CreateGuildBanData
  { deleteMessageDays :: Maybe Integer
  , reason :: Maybe Text
  }
  deriving (Show)

instance Default CreateGuildBanData where
  def = CreateGuildBanData Nothing Nothing

{- | Parameters to the Modify Guild Role endpoint.

 Use the provided methods (@modifyGuildRoleX@) to create a value with the
 field set, use the Semigroup instance to combine parameters.

 ==== Examples

 >>> encode $ modifyGuildRoleName (Just "test") <> modifyGuildRolePermissions Nothing
 "{\"name\":\"test\",\"permissions\":null}"
-}
newtype ModifyGuildRoleData = ModifyGuildRoleData Aeson.Object
  deriving stock (Show)
  deriving newtype (Aeson.ToJSON, Semigroup, Monoid)

modifyGuildRoleName :: Maybe Text -> ModifyGuildRoleData
modifyGuildRoleName v = ModifyGuildRoleData $ K.fromList [("name", Aeson.toJSON v)]

modifyGuildRolePermissions :: Maybe Permissions -> ModifyGuildRoleData
modifyGuildRolePermissions v = ModifyGuildRoleData $ K.fromList [("permissions", Aeson.toJSON v)]

modifyGuildRoleColour :: Maybe (Colour Double) -> ModifyGuildRoleData
modifyGuildRoleColour v = ModifyGuildRoleData $ K.fromList [("colour", Aeson.toJSON (IntColour <$> v))]

modifyGuildRoleHoist :: Maybe Bool -> ModifyGuildRoleData
modifyGuildRoleHoist v = ModifyGuildRoleData $ K.fromList [("hoist", Aeson.toJSON v)]

modifyGuildRoleMentionable :: Maybe Bool -> ModifyGuildRoleData
modifyGuildRoleMentionable v = ModifyGuildRoleData $ K.fromList [("mentionable", Aeson.toJSON v)]

data ModifyGuildRolePositionsData = ModifyGuildRolePositionsData
  { id :: Snowflake Role
  , position :: Maybe Integer
  }
  deriving (Show)
  deriving (Aeson.ToJSON) via CalamityToJSON ModifyGuildRolePositionsData

instance CalamityToJSON' ModifyGuildRolePositionsData where
  toPairs ModifyGuildRolePositionsData {..} =
    [ "id" .= id
    , "postition" .?= position
    ]

data GuildRequest a where
  CreateGuild :: CreateGuildData -> GuildRequest Guild
  GetGuild :: HasID Guild g => g -> GuildRequest Guild
  ModifyGuild :: HasID Guild g => g -> ModifyGuildData -> GuildRequest Guild
  DeleteGuild :: HasID Guild g => g -> GuildRequest ()
  GetGuildChannels :: HasID Guild g => g -> GuildRequest [Channel]
  CreateGuildChannel :: HasID Guild g => g -> ChannelCreateData -> GuildRequest Channel
  ModifyGuildChannelPositions :: HasID Guild g => g -> [ChannelPosition] -> GuildRequest ()
  GetGuildMember :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest Member
  ListGuildMembers :: HasID Guild g => g -> ListMembersOptions -> GuildRequest [Member]
  SearchGuildMembers :: HasID Guild g => g -> SearchMembersOptions -> GuildRequest [Member]
  AddGuildMember :: (HasID Guild g, HasID User u) => g -> u -> AddGuildMemberData -> GuildRequest (Maybe Member)
  ModifyGuildMember :: (HasID Guild g, HasID User u) => g -> u -> ModifyGuildMemberData -> GuildRequest ()
  ModifyCurrentUserNick :: HasID Guild g => g -> Maybe Text -> GuildRequest ()
  AddGuildMemberRole :: (HasID Guild g, HasID User u, HasID Role r) => g -> u -> r -> GuildRequest ()
  RemoveGuildMemberRole :: (HasID Guild g, HasID User u, HasID Role r) => g -> u -> r -> GuildRequest ()
  RemoveGuildMember :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest ()
  GetGuildBans :: HasID Guild g => g -> GetGuildBansOptions -> GuildRequest [BanData]
  GetGuildBan :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest BanData
  CreateGuildBan :: (HasID Guild g, HasID User u) => g -> u -> CreateGuildBanData -> GuildRequest ()
  RemoveGuildBan :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest ()
  GetGuildRoles :: HasID Guild g => g -> GuildRequest [Role]
  CreateGuildRole :: HasID Guild g => g -> ModifyGuildRoleData -> GuildRequest Role
  ModifyGuildRolePositions :: HasID Guild g => g -> ModifyGuildRolePositionsData -> GuildRequest [Role]
  ModifyGuildRole :: (HasID Guild g, HasID Role r) => g -> r -> ModifyGuildRoleData -> GuildRequest Role
  DeleteGuildRole :: (HasID Guild g, HasID Role r) => g -> r -> GuildRequest ()
  GetGuildPruneCount :: HasID Guild g => g -> Integer -> GuildRequest Integer
  BeginGuildPrune :: HasID Guild g => g -> Integer -> Bool -> GuildRequest (Maybe Integer)
  GetGuildVoiceRegions :: HasID Guild g => g -> GuildRequest [VoiceRegion]
  GetGuildInvites :: HasID Guild g => g -> GuildRequest [Invite]

baseRoute :: Snowflake Guild -> RouteBuilder _
baseRoute id =
  mkRouteBuilder // S "guilds" // ID @Guild
    & giveID id

instance Request (GuildRequest a) where
  type Result (GuildRequest a) = a

  route (CreateGuild _) =
    mkRouteBuilder // S "guilds"
      & buildRoute
  route (GetGuild (getID -> gid)) =
    baseRoute gid
      & buildRoute
  route (ModifyGuild (getID -> gid) _) =
    baseRoute gid
      & buildRoute
  route (DeleteGuild (getID -> gid)) =
    baseRoute gid
      & buildRoute
  route (GetGuildChannels (getID -> gid)) =
    baseRoute gid // S "channels"
      & buildRoute
  route (CreateGuildChannel (getID -> gid) _) =
    baseRoute gid // S "channels"
      & buildRoute
  route (ModifyGuildChannelPositions (getID -> gid) _) =
    baseRoute gid // S "channels"
      & buildRoute
  route (GetGuildMember (getID -> gid) (getID @User -> uid)) =
    baseRoute gid // S "members" // ID @User
      & giveID uid
      & buildRoute
  route (ListGuildMembers (getID -> gid) _) =
    baseRoute gid // S "members"
      & buildRoute
  route (SearchGuildMembers (getID -> gid) _) =
    baseRoute gid // S "members" // S "search"
      & buildRoute
  route (AddGuildMember (getID -> gid) (getID @User -> uid) _) =
    baseRoute gid // S "members" // ID @User
      & giveID uid
      & buildRoute
  route (ModifyGuildMember (getID -> gid) (getID @User -> uid) _) =
    baseRoute gid // S "members" // ID @User
      & giveID uid
      & buildRoute
  route (ModifyCurrentUserNick (getID -> gid) _) =
    baseRoute gid // S "members" // S "@me" // S "nick"
      & buildRoute
  route (AddGuildMemberRole (getID -> gid) (getID @User -> uid) (getID @Role -> rid)) =
    baseRoute gid // S "members" // ID @User // S "roles" // ID @Role
      & giveID uid
      & giveID rid
      & buildRoute
  route (RemoveGuildMemberRole (getID -> gid) (getID @User -> uid) (getID @Role -> rid)) =
    baseRoute gid // S "members" // ID @User // S "roles" // ID @Role
      & giveID uid
      & giveID rid
      & buildRoute
  route (RemoveGuildMember (getID -> gid) (getID @User -> uid)) =
    baseRoute gid // S "members" // ID @User
      & giveID uid
      & buildRoute
  route (GetGuildBans (getID -> gid) _) =
    baseRoute gid // S "bans"
      & buildRoute
  route (GetGuildBan (getID -> gid) (getID @User -> uid)) =
    baseRoute gid // S "bans" // ID @User
      & giveID uid
      & buildRoute
  route (CreateGuildBan (getID -> gid) (getID @User -> uid) _) =
    baseRoute gid // S "bans" // ID @User
      & giveID uid
      & buildRoute
  route (RemoveGuildBan (getID -> gid) (getID @User -> uid)) =
    baseRoute gid // S "bans" // ID @User
      & giveID uid
      & buildRoute
  route (GetGuildRoles (getID -> gid)) =
    baseRoute gid // S "roles"
      & buildRoute
  route (CreateGuildRole (getID -> gid) _) =
    baseRoute gid // S "roles"
      & buildRoute
  route (ModifyGuildRolePositions (getID -> gid) _) =
    baseRoute gid // S "roles"
      & buildRoute
  route (ModifyGuildRole (getID -> gid) (getID @Role -> rid) _) =
    baseRoute gid // S "roles" // ID @Role
      & giveID rid
      & buildRoute
  route (DeleteGuildRole (getID -> gid) (getID @Role -> rid)) =
    baseRoute gid // S "roles" // ID @Role
      & giveID rid
      & buildRoute
  route (GetGuildPruneCount (getID -> gid) _) =
    baseRoute gid // S "prune"
      & buildRoute
  route (BeginGuildPrune (getID -> gid) _ _) =
    baseRoute gid // S "prune"
      & buildRoute
  route (GetGuildVoiceRegions (getID -> gid)) =
    baseRoute gid // S "regions"
      & buildRoute
  route (GetGuildInvites (getID -> gid)) =
    baseRoute gid // S "invites"
      & buildRoute

  action (CreateGuild o) = postWith' (ReqBodyJson o)
  action (GetGuild _) = getWith
  action (ModifyGuild _ o) = patchWith' (ReqBodyJson o)
  action (DeleteGuild _) = deleteWith
  action (GetGuildChannels _) = getWith
  action (CreateGuildChannel _ o) = postWith' (ReqBodyJson o)
  action (ModifyGuildChannelPositions _ o) = postWith' (ReqBodyJson o)
  action (GetGuildMember _ _) = getWith
  action (ListGuildMembers _ ListMembersOptions {limit, after}) =
    getWithP
      ("limit" =:? limit <> "after" =:? (fromSnowflake <$> after))
  action (SearchGuildMembers _ SearchMembersOptions {limit, query}) =
    getWithP
      ("limit" =:? limit <> "query" =: query)
  action (AddGuildMember _ _ o) = putWith' (ReqBodyJson o)
  action (ModifyGuildMember _ _ o) = patchWith' (ReqBodyJson o)
  action (ModifyCurrentUserNick _ nick) = patchWith' (ReqBodyJson $ Aeson.object ["nick" Aeson..= nick])
  action AddGuildMemberRole {} = putEmpty
  action RemoveGuildMemberRole {} = deleteWith
  action (RemoveGuildMember _ _) = deleteWith
  action (GetGuildBans _ GetGuildBansOptions {limit, before, after}) =
    getWithP
      ("limit" =:? limit <> "before" =:? before <> "after" =:? after)
  action (GetGuildBan _ _) = getWith
  action (CreateGuildBan _ _ CreateGuildBanData {deleteMessageDays}) =
    putEmptyP
      ("delete_message_days" =:? deleteMessageDays)
  action (RemoveGuildBan _ _) = deleteWith
  action (GetGuildRoles _) = getWith
  action (CreateGuildRole _ o) = postWith' (ReqBodyJson o)
  action (ModifyGuildRolePositions _ o) = patchWith' (ReqBodyJson o)
  action (ModifyGuildRole _ _ o) = patchWith' (ReqBodyJson o)
  action (DeleteGuildRole _ _) = deleteWith
  action (GetGuildPruneCount _ d) = getWithP ("days" =: d)
  action (BeginGuildPrune _ d r) = postEmptyP ("days" =: d <> "compute_prune_count" =: r)
  action (GetGuildVoiceRegions _) = getWith
  action (GetGuildInvites _) = getWith

  -- this is a bit of a hack
  -- TODO: add something to allow for contextual parsing
  modifyResponse _ = Prelude.id

$(makeFieldLabelsNoPrefix ''CreateGuildData)
$(makeFieldLabelsNoPrefix ''ModifyGuildData)
$(makeFieldLabelsNoPrefix ''ChannelCreateData)
$(makeFieldLabelsNoPrefix ''ChannelPosition)
$(makeFieldLabelsNoPrefix ''ListMembersOptions)
$(makeFieldLabelsNoPrefix ''AddGuildMemberData)
$(makeFieldLabelsNoPrefix ''ModifyGuildRolePositionsData)
$(makeFieldLabelsNoPrefix ''CreateGuildBanData)