packages feed

calamity-0.8.0.0: Calamity/Utils/Permissions.hs

-- | Permission utilities
module Calamity.Utils.Permissions (
  basePermissions,
  applyOverwrites,
  PermissionsIn (..),
  PermissionsIn' (..),
) where

import Calamity.Client.Types
import Calamity.Internal.SnowflakeMap qualified as SM
import Calamity.Types.Model.Channel.Guild
import Calamity.Types.Model.Guild.Guild
import Calamity.Types.Model.Guild.Member
import Calamity.Types.Model.Guild.Overwrite
import Calamity.Types.Model.Guild.Permissions
import Calamity.Types.Model.User
import Calamity.Types.Snowflake
import Calamity.Types.Upgradeable
import Data.Flags
import Data.Foldable (Foldable (foldl'))
import Data.Maybe (mapMaybe)
import Data.Vector.Unboxing qualified as V
import Optics
import Polysemy qualified as P

-- | Calculate a 'Member'\'s 'Permissions' in a 'Guild'
basePermissions :: Guild -> Member -> Permissions
basePermissions g m
  | g ^. #ownerID == getID m = allFlags
  | otherwise =
      let everyoneRole = g ^. #roles % at (coerceSnowflake $ getID @Guild g)
          permsEveryone = maybe noFlags (^. #permissions) everyoneRole
          roleIDs = V.toList $ m ^. #roles
          rolePerms = mapMaybe (\rid -> g ^? #roles % ix rid % #permissions) roleIDs
          perms = foldl' andFlags noFlags (permsEveryone : rolePerms)
       in if perms .<=. administrator
            then allFlags
            else perms

overwrites :: GuildChannel -> SM.SnowflakeMap Overwrite
overwrites (GuildTextChannel c) = c ^. #permissionOverwrites
overwrites (GuildVoiceChannel c) = c ^. #permissionOverwrites
overwrites (GuildCategory c) = c ^. #permissionOverwrites
overwrites _ = SM.empty

-- | Apply any 'Overwrite's for a 'GuildChannel' onto some 'Permissions'
applyOverwrites :: GuildChannel -> Member -> Permissions -> Permissions
applyOverwrites c m p
  | p .<=. administrator = allFlags
  | otherwise =
      let everyoneOverwrite = overwrites c ^. at (coerceSnowflake $ getID @Guild c)
          everyoneAllow = maybe noFlags (^. #allow) everyoneOverwrite
          everyoneDeny = maybe noFlags (^. #deny) everyoneOverwrite
          p' = p .-. everyoneDeny .+. everyoneAllow
          roleOverwriteIDs = map (coerceSnowflake @_ @Overwrite) . V.toList $ m ^. #roles
          roleOverwrites = mapMaybe (\oid -> overwrites c ^? ix oid) roleOverwriteIDs
          roleAllow = foldl' andFlags noFlags (roleOverwrites ^.. traversed % #allow)
          roleDeny = foldl' andFlags noFlags (roleOverwrites ^.. traversed % #deny)
          p'' = p' .-. roleDeny .+. roleAllow
          memberOverwrite = overwrites c ^. at (coerceSnowflake @_ @Overwrite $ getID @Member m)
          memberAllow = maybe noFlags (^. #allow) memberOverwrite
          memberDeny = maybe noFlags (^. #deny) memberOverwrite
          p''' = p'' .-. memberDeny .+. memberAllow
       in p'''

-- | Things that 'Member's have 'Permissions' in
class PermissionsIn a where
  -- | Calculate a 'Member'\'s 'Permissions' in something
  --
  -- If permissions could not be calculated because something couldn't be found
  -- in the cache, this will return an empty set of permissions. Use
  -- 'permissionsIn'' if you want to handle cases where something might not exist
  -- in cache.
  permissionsIn :: a -> Member -> Permissions

-- | A 'Member'\'s 'Permissions' in a channel are their roles and overwrites
instance PermissionsIn (Guild, GuildChannel) where
  permissionsIn (g, c) m = applyOverwrites c m $ basePermissions g m

-- | A 'Member'\'s 'Permissions' in a guild are just their roles
instance PermissionsIn Guild where
  permissionsIn = basePermissions

-- | A variant of 'PermissionsIn' that will use the cache/http.
class PermissionsIn' a where
  -- | Calculate the permissions of something that has a 'User' id
  permissionsIn' :: (BotC r, HasID User u) => a -> u -> P.Sem r Permissions

{- | A 'User''s 'Permissions' in a channel are their roles and overwrites

 This will fetch the guild from the cache or http as needed
-}
instance PermissionsIn' GuildChannel where
  permissionsIn' c (getID @User -> uid) = do
    m <- upgrade (getID @Guild c, coerceSnowflake @_ @Member uid)
    g <- upgrade (getID @Guild c)
    case (m, g) of
      (Just m, Just g') -> pure $ permissionsIn (g', c) m
      _cantFind -> pure noFlags

-- | A 'Member'\'s 'Permissions' in a guild are just their roles
instance PermissionsIn' Guild where
  permissionsIn' g (getID @User -> uid) = do
    m <- upgrade (getID @Guild g, coerceSnowflake @_ @Member uid)
    case m of
      Just m' -> pure $ permissionsIn g m'
      Nothing -> pure noFlags

{- | A 'Member'\'s 'Permissions' in a channel are their roles and overwrites

 This will fetch the guild and channel from the cache or http as needed
-}
instance PermissionsIn' (Snowflake GuildChannel) where
  permissionsIn' cid u = do
    c <- upgrade cid
    case c of
      Just c' -> permissionsIn' c' u
      Nothing -> pure noFlags

{- | A 'Member'\'s 'Permissions' in a guild are just their roles

 This will fetch the guild from the cache or http as needed
-}
instance PermissionsIn' (Snowflake Guild) where
  permissionsIn' gid u = do
    g <- upgrade gid
    case g of
      Just g' -> permissionsIn' g' u
      Nothing -> pure noFlags