├── .editorconfig ├── LICENSE ├── README.md └── lua └── autorun └── sh_cami.lua /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 4 5 | indent_style = space 6 | 7 | charset = utf-8 8 | end_of_line = lf 9 | 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 CAMI Contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | The Common Admin Mod Interface (CAMI) aims to solve two problems: admin mod intercompatibility and the inability of addons to hook into the privilege systems that many admin mods have. The two problems are detailed in the next paragraphs. 3 | 4 | The biggest source of the admin mod incompatibility problem is usergroups. One admin mod creates a usergroup that another admin mod does not know of. Two admin mods thinking a player have different usergroups can cause quite a few bugs and confusion. 5 | 6 | The second problem pertains third party addons. Most of them offer features that only admins or superadmins are allowed to use. Server owners might however want to customise these privileges. For example, they might want their user based `moderator` to be allowed to spawn a _PlayX_ screen entity. Many admin mods have an advanced privilege system, it would be a shame if all third party mods are limited to just user/admin/superadmin. 7 | 8 | The solution is inspired by the [Common Prop Protection Interface](ulyssesmod.net/archive/CPPI_v1-3.pdf) (CPPI) by Ulysses. This document describes a common interface that every admin mod implements and uses so that information can be exchanged in a universal format. 9 | 10 | # Goals 11 | CAMI has two goals: 12 | - Unify interfaces between existing admin mods to allow them to coexist. 13 | - Provide an optional interface to third party addons to create and query privileges. One that works even if no admin mod is installed. 14 | 15 | The following two sections describe what CAMI will do with usergroups and privileges to meet these goals. 16 | 17 | # Usergroups 18 | Usergroups are an existing concept in the Source engine. There are three default usergroups: `user`, `admin` and `superadmin`. 19 | Every player has exactly one usergroup. Custom usergroups must inherit either another custom group or a default usergroup. By default, `admin` inherits from `user` and `superadmin` inherits from `admin`. Mathematically, inheritance is transitive, so `superadmin` also inherits from `user`. 20 | 21 | For convenience, inheritance is not verified, so you need not worry about the order in which usergroups are registered. Admin mods might not have a concept of inheritance in usergroups. In that case, all usergroups are to be registered as inheriting `user`. That should give the highest compatibility with other admin mods. 22 | 23 | # Privileges 24 | A privilege is a witness of permission to perform one or more actions. Privileges can be registered by third party mods to the currently installed admin mod(s). These third party mods can then query the admin mod(s) to see whether a player has the registered privilege. Note that the privilege part of CAMI is optional for admin mods. Fortunately, this is transparent for the third party mod: when no admin mod is installed that implements CAMI privileges, CAMI itself will have a default fallback based on whether the player is a user, admin or superadmin. This means CAMI is useful for third party addons even when no admin mod is installed. 25 | 26 | # API 27 | All functions, hooks and data structures are shared, which thus exist in both the client and server realm. 28 | 29 | ## Data structures 30 | 31 | ### `CAMI_USERGROUP` 32 | - `Name` :: `string` 33 | - `Inherits` :: `string` 34 | 35 | ### `CAMI_PRIVILEGE` 36 | - `Name` :: `string` 37 | - `MinAccess` :: `string` 38 | - _(optional)_ `Description` :: `string` 39 | - _(optional)_ `HasAccess` :: `function(privilege :: CAMI_PRIVILEGE, actor :: Player, target :: Player) :: bool, string` 40 | 41 | ## Functions 42 | This section lists the functions that CAMI provides. It should be used as a quick reference. Detailed descriptions of the functions can be found in the [sh_cami.lua source file](./lua/autorun/sh_cami.lua). 43 | 44 | The ‘::’ indicates the types of the parameters and the return values of the functions. Parameters in square brackets are optional. 45 | 46 | ```lua 47 | CAMI.UsergroupInherits(usergroupName1 :: string, usergroupName2 :: string) :: bool 48 | 49 | CAMI.InheritanceRoot(usergroupName) :: string 50 | 51 | CAMI.RegisterUsergroup(usergroup :: CAMI_USERGROUP, source :: any) :: CAMI_USERGROUP 52 | 53 | CAMI.UnregisterUsergroup(name :: string, source :: any) :: bool 54 | 55 | CAMI.GetUsergroup(usergroupName :: string) :: CAMI_USERGROUP 56 | 57 | CAMI.RegisterPrivilege(privilege :: CAMI_PRIVILEGE) :: CAMI_PRIVILEGE 58 | 59 | CAMI.UnregisterPrivilege(name :: string) :: bool 60 | 61 | CAMI.GetPrivilege(name :: string) :: CAMI_PRIVILEGE 62 | 63 | CAMI.PlayerHasAccess(actor :: Player, privilege :: string, callback :: function(bool, 64 | string)[, target :: Player, extraInfo :: table]) :: nil 65 | 66 | CAMI.GetPlayersWithAccess(privilege :: string, callback :: function(table)[, target :: Player, extraInfo :: Table]) :: nil 67 | 68 | CAMI.SteamIDHasAccess(actor :: SteamID, privilege :: string, callback :: function(bool, string)[, target :: SteamID, extraInfo :: table]) :: nil 69 | 70 | CAMI.GetUsergroups() :: [CAMI_USERGROUP] 71 | 72 | CAMI.GetPrivileges() :: [CAMI_PRIVILEGE] 73 | 74 | CAMI.SignalUserGroupChanged(ply :: Player, old :: string, new :: string, source :: any) 75 | 76 | CAMI.SignalSteamIDUserGroupChanged(steamId :: string , old :: string, new :: string, source :: any) 77 | ``` 78 | 79 | # Hooks 80 | This section provides a list of hooks that CAMI calls. The types in the parentheses indicate the types of values they give the functions in the hook. When a hook is given a type, you are requested to return a value within the hook. E.g. in `CAMI.PlayerHasAccess` and `CAMI.SteamIDHasAccess`, true is to be returned when the admin mod decides whether a player has the privilege. Not returning a value will defer the decision to another admin mod or eventually CAMI itself. 81 | 82 | ```lua 83 | CAMI.OnUsergroupRegistered(CAMI_USERGROUP) 84 | 85 | CAMI.OnUsergroupUnregistered(CAMI_USERGROUP) 86 | 87 | CAMI.OnPrivilegeRegistered(CAMI_PRIVILEGE) 88 | 89 | CAMI.OnPrivilegeUnregistered(CAMI_PRIVILEGE) 90 | 91 | CAMI.PlayerHasAccess(actor :: Player, privilege :: string, callback :: function(bool, string), target :: Player, extraInfo :: table) :: bool/nil 92 | 93 | CAMI.SteamIDHasAccess(actor :: SteamID, privilege :: string, callback :: function(bool, string), target :: Player, extraInfo :: table) :: bool/nil 94 | 95 | CAMI.PlayerUsergroupChanged(ply :: Player, from :: string, to :: string, source :: any) 96 | 97 | CAMI.SteamIDUsergroupChanged(steamId :: string, from :: string, to :: string, source :: any) 98 | ``` 99 | 100 | # What to implement 101 | Both admin mods and third party addons are to ship the [sh_cami.lua source file](./lua/autorun/sh_cami.lua), shared. 102 | Alongside with that, the following things should be implemented: 103 | 104 | ## Admin mods 105 | Please perform the following steps for both the server and the client. Clients need to be in sync with the server. Bugs in CAMI supporting addons are known to occur specifically when clients don’t know they have the right CAMI privileges to perform certain actions. 106 | 107 | - Register custom usergroups with `CAMI.RegisterUsergroup` (not `user`/`admin`/`superadmin`) 108 | - Register the removal of custom usergroups with `CAMI.UnregisterUsergroup.` 109 | - Listen to other admin mods creating usergroups with the `CAMI.OnUsergroupRegistered` and `CAMI.OnUsergroupUnregistered` hooks 110 | - Your admin mod might load after others. When loading, check if new usergroups were created earlier with the `CAMI.GetUsergroups()` function. 111 | - Call `CAMI.SignalUserGroupChanged` when a player’s usergroup is changed through your admin mod. Note: this will cause all admin mods to store the new usergroup of the player. As such it should not be called e.g. when simply receiving a player’s usergroup from a database on InitialSpawn. Call it when it is actually changed. 112 | - Hook to `CAMI.PlayerUsergroupChanged` so your admin mod does not go out of sync with changes made in other admin mods. Note that this hook is called when `CAMI.SignalUserGroupChanged` is called. Keep that in mind when calling that function yourself. You can prevent things from being saved twice using the `source` parameter of the `CAMI.SignalUserGroupChanged` function. 113 | - _(optional, strongly recommended)_ Hook to `CAMI.OnPrivilegeRegistered,` this way you can let your admin mod administrate the permissions added by third party mods. 114 | - Your admin mod might load after others. When loading, check if new privileges were created earlier with the `CAMI.GetPrivileges()` function. 115 | - _(optional)_ Hook to `CAMI.OnPrivilegeUnregistered.` Some third party mods might want to remove privileges once they have become redundant. 116 | - _(optional, strongly recommended)_ Hook to `CAMI.PlayerHasAccess.` This hook allows third party addons to check whether a certain player has a privilege that they have registered. you can probably use existing logic here. Make sure to return true when your admin mod makes a decision. 117 | - _(optional)_ Hook to `CAMI.SteamIDHasAccess` if the admin mod supports offline access queries. Make sure to return true when your admin mod makes a decision. 118 | - **DO NOT** register the admin mod’s privileges (registering privileges is for third party mods, not for sharing privileges between admin mods) 119 | ## Third party addons 120 | - Register custom privileges with `CAMI.RegisterPrivilege`. 121 | - _(optional)_ Unregister custom privilege with `CAMI.UnregisterPrivilege` when one of your privileges has become redundant. 122 | - Call `CAMI.PlayerHasAccess` to see whether a player has a privilege. 123 | -------------------------------------------------------------------------------- /lua/autorun/sh_cami.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | CAMI - Common Admin Mod Interface. 3 | Copyright 2020 CAMI Contributors 4 | 5 | Makes admin mods intercompatible and provides an abstract privilege interface 6 | for third party addons. 7 | 8 | Follows the specification on this page: 9 | https://github.com/glua/CAMI/blob/master/README.md 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | ]] 17 | 18 | -- Version number in YearMonthDay format. 19 | local version = 20211019 20 | 21 | if CAMI and CAMI.Version >= version then return end 22 | 23 | CAMI = CAMI or {} 24 | CAMI.Version = version 25 | 26 | 27 | --- @class CAMI_USERGROUP 28 | --- defines the charactaristics of a usergroup 29 | --- @field Name string @The name of the usergroup 30 | --- @field Inherits string @The name of the usergroup this usergroup inherits from 31 | --- @field CAMI_Source string @The source specified by the admin mod which registered this usergroup (if any, converted to a string) 32 | 33 | --- @class CAMI_PRIVILEGE 34 | --- defines the charactaristics of a privilege 35 | --- @field Name string @The name of the privilege 36 | --- @field MinAccess "'user'" | "'admin'" | "'superadmin'" @Default group that should have this privilege 37 | --- @field Description string | nil @Optional text describing the purpose of the privilege 38 | local CAMI_PRIVILEGE = {} 39 | --- Optional function to check if a player has access to this privilege 40 | --- (and optionally execute it on another player) 41 | --- 42 | --- ⚠ **Warning**: This function may not be called by all admin mods 43 | --- @param actor GPlayer @The player 44 | --- @param target GPlayer | nil @Optional - the target 45 | --- @return boolean @If they can or not 46 | --- @return string | nil @Optional reason 47 | function CAMI_PRIVILEGE:HasAccess(actor, target) 48 | end 49 | 50 | --- Contains the registered CAMI_USERGROUP usergroup structures. 51 | --- Indexed by usergroup name. 52 | --- @type CAMI_USERGROUP[] 53 | local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or { 54 | user = { 55 | Name = "user", 56 | Inherits = "user", 57 | CAMI_Source = "Garry's Mod", 58 | }, 59 | admin = { 60 | Name = "admin", 61 | Inherits = "user", 62 | CAMI_Source = "Garry's Mod", 63 | }, 64 | superadmin = { 65 | Name = "superadmin", 66 | Inherits = "admin", 67 | CAMI_Source = "Garry's Mod", 68 | } 69 | } 70 | 71 | --- Contains the registered CAMI_PRIVILEGE privilege structures. 72 | --- Indexed by privilege name. 73 | --- @type CAMI_PRIVILEGE[] 74 | local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {} 75 | 76 | --- Registers a usergroup with CAMI. 77 | --- 78 | --- Use the source parameter to make sure CAMI.RegisterUsergroup function and 79 | --- the CAMI.OnUsergroupRegistered hook don't cause an infinite loop 80 | --- @param usergroup CAMI_USERGROUP @The structure for the usergroup you want to register 81 | --- @param source any @Identifier for your own admin mod. Can be anything. 82 | --- @return CAMI_USERGROUP @The usergroup given as an argument 83 | function CAMI.RegisterUsergroup(usergroup, source) 84 | if source then 85 | usergroup.CAMI_Source = tostring(source) 86 | end 87 | usergroups[usergroup.Name] = usergroup 88 | 89 | hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source) 90 | return usergroup 91 | end 92 | 93 | --- Unregisters a usergroup from CAMI. This will call a hook that will notify 94 | --- all other admin mods of the removal. 95 | --- 96 | --- ⚠ **Warning**: Call only when the usergroup is to be permanently removed. 97 | --- 98 | --- Use the source parameter to make sure CAMI.UnregisterUsergroup function and 99 | --- the CAMI.OnUsergroupUnregistered hook don't cause an infinite loop 100 | --- @param usergroupName string @The name of the usergroup. 101 | --- @param source any @Identifier for your own admin mod. Can be anything. 102 | --- @return boolean @Whether the unregistering succeeded. 103 | function CAMI.UnregisterUsergroup(usergroupName, source) 104 | if not usergroups[usergroupName] then return false end 105 | 106 | local usergroup = usergroups[usergroupName] 107 | usergroups[usergroupName] = nil 108 | 109 | hook.Call("CAMI.OnUsergroupUnregistered", nil, usergroup, source) 110 | 111 | return true 112 | end 113 | 114 | --- Retrieves all registered usergroups. 115 | --- @return CAMI_USERGROUP[] @Usergroups indexed by their names. 116 | function CAMI.GetUsergroups() 117 | return usergroups 118 | end 119 | 120 | --- Receives information about a usergroup. 121 | --- @param usergroupName string 122 | --- @return CAMI_USERGROUP | nil @Returns nil when the usergroup does not exist. 123 | function CAMI.GetUsergroup(usergroupName) 124 | return usergroups[usergroupName] 125 | end 126 | 127 | --- Checks to see if potentialAncestor is an ancestor of usergroupName. 128 | --- All usergroups are ancestors of themselves. 129 | --- 130 | --- Examples: 131 | --- * `user` is an ancestor of `admin` and also `superadmin` 132 | --- * `admin` is an ancestor of `superadmin`, but not `user` 133 | --- @param usergroupName string @The usergroup to query 134 | --- @param potentialAncestor string @The ancestor to query 135 | --- @return boolean @Whether usergroupName inherits potentialAncestor. 136 | function CAMI.UsergroupInherits(usergroupName, potentialAncestor) 137 | repeat 138 | if usergroupName == potentialAncestor then return true end 139 | 140 | usergroupName = usergroups[usergroupName] and 141 | usergroups[usergroupName].Inherits or 142 | usergroupName 143 | until not usergroups[usergroupName] or 144 | usergroups[usergroupName].Inherits == usergroupName 145 | 146 | -- One can only be sure the usergroup inherits from user if the 147 | -- usergroup isn't registered. 148 | return usergroupName == potentialAncestor or potentialAncestor == "user" 149 | end 150 | 151 | --- Find the base group a usergroup inherits from. 152 | --- 153 | --- This function traverses down the inheritence chain, so for example if you have 154 | --- `user` -> `group1` -> `group2` 155 | --- this function will return `user` if you pass it `group2`. 156 | --- 157 | --- ℹ **NOTE**: All usergroups must eventually inherit either user, admin or superadmin. 158 | --- @param usergroupName string @The name of the usergroup 159 | --- @return "'user'" | "'admin'" | "'superadmin'" @The name of the root usergroup 160 | function CAMI.InheritanceRoot(usergroupName) 161 | if not usergroups[usergroupName] then return end 162 | 163 | local inherits = usergroups[usergroupName].Inherits 164 | while inherits ~= usergroups[usergroupName].Inherits do 165 | usergroupName = usergroups[usergroupName].Inherits 166 | end 167 | 168 | return usergroupName 169 | end 170 | 171 | --- Registers an addon privilege with CAMI. 172 | --- 173 | --- ⚠ **Warning**: This should only be used by addons. Admin mods must *NOT* 174 | --- register their privileges using this function. 175 | --- @param privilege CAMI_PRIVILEGE 176 | --- @return CAMI_PRIVILEGE @The privilege given as argument. 177 | function CAMI.RegisterPrivilege(privilege) 178 | privileges[privilege.Name] = privilege 179 | 180 | hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege) 181 | 182 | return privilege 183 | end 184 | 185 | --- Unregisters a privilege from CAMI. 186 | --- This will call a hook that will notify any admin mods of the removal. 187 | --- 188 | --- ⚠ **Warning**: Call only when the privilege is to be permanently removed. 189 | --- @param privilegeName string @The name of the privilege. 190 | --- @return boolean @Whether the unregistering succeeded. 191 | function CAMI.UnregisterPrivilege(privilegeName) 192 | if not privileges[privilegeName] then return false end 193 | 194 | local privilege = privileges[privilegeName] 195 | privileges[privilegeName] = nil 196 | 197 | hook.Call("CAMI.OnPrivilegeUnregistered", nil, privilege) 198 | 199 | return true 200 | end 201 | 202 | --- Retrieves all registered privileges. 203 | --- @return CAMI_PRIVILEGE[] @All privileges indexed by their names. 204 | function CAMI.GetPrivileges() 205 | return privileges 206 | end 207 | 208 | --- Receives information about a privilege. 209 | --- @param privilegeName string 210 | --- @return CAMI_PRIVILEGE | nil 211 | function CAMI.GetPrivilege(privilegeName) 212 | return privileges[privilegeName] 213 | end 214 | 215 | -- Default access handler 216 | local defaultAccessHandler = {["CAMI.PlayerHasAccess"] = 217 | function(_, actorPly, privilegeName, callback, targetPly, extraInfoTbl) 218 | -- The server always has access in the fallback 219 | if not IsValid(actorPly) then return callback(true, "Fallback.") end 220 | 221 | local priv = privileges[privilegeName] 222 | 223 | local fallback = extraInfoTbl and ( 224 | not extraInfoTbl.Fallback and actorPly:IsAdmin() or 225 | extraInfoTbl.Fallback == "user" and true or 226 | extraInfoTbl.Fallback == "admin" and actorPly:IsAdmin() or 227 | extraInfoTbl.Fallback == "superadmin" and actorPly:IsSuperAdmin()) 228 | 229 | 230 | if not priv then return callback(fallback, "Fallback.") end 231 | 232 | local hasAccess = 233 | priv.MinAccess == "user" or 234 | priv.MinAccess == "admin" and actorPly:IsAdmin() or 235 | priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin() 236 | 237 | if hasAccess and priv.HasAccess then 238 | hasAccess = priv:HasAccess(actorPly, targetPly) 239 | end 240 | 241 | callback(hasAccess, "Fallback.") 242 | end, 243 | ["CAMI.SteamIDHasAccess"] = 244 | function(_, _, _, callback) 245 | callback(false, "No information available.") 246 | end 247 | } 248 | 249 | --- @class CAMI_ACCESS_EXTRA_INFO 250 | --- @field Fallback "'user'" | "'admin'" | "'superadmin'" @Fallback status for if the privilege doesn't exist. Defaults to `admin`. 251 | --- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have. 252 | --- @field CommandArguments table @Extra arguments that were given to the privilege command. 253 | 254 | --- Checks if a player has access to a privilege 255 | --- (and optionally can execute it on targetPly) 256 | --- 257 | --- This function is designed to be asynchronous but will be invoked 258 | --- synchronously if no callback is passed. 259 | --- 260 | --- ⚠ **Warning**: If the currently installed admin mod does not support 261 | --- synchronous queries, this function will throw an error! 262 | --- @param actorPly GPlayer @The player to query 263 | --- @param privilegeName string @The privilege to query 264 | --- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer, or nil for synchronous 265 | --- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban) 266 | --- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod 267 | --- @return boolean | nil @Synchronous only - if the player has the privilege 268 | --- @return string | nil @Synchronous only - optional reason from admin mod 269 | function CAMI.PlayerHasAccess(actorPly, privilegeName, callback, targetPly, 270 | extraInfoTbl) 271 | local hasAccess, reason = nil, nil 272 | local callback_ = callback or function(hA, r) hasAccess, reason = hA, r end 273 | 274 | hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly, 275 | privilegeName, callback_, targetPly, extraInfoTbl) 276 | 277 | if callback ~= nil then return end 278 | 279 | if hasAccess == nil then 280 | local err = [[The function CAMI.PlayerHasAccess was used to find out 281 | whether Player %s has privilege "%s", but an admin mod did not give an 282 | immediate answer!]] 283 | error(string.format(err, 284 | actorPly:IsPlayer() and actorPly:Nick() or tostring(actorPly), 285 | privilegeName)) 286 | end 287 | 288 | return hasAccess, reason 289 | end 290 | 291 | --- Get all the players on the server with a certain privilege 292 | --- (and optionally who can execute it on targetPly) 293 | --- 294 | --- ℹ **NOTE**: This is an asynchronous function! 295 | --- @param privilegeName string @The privilege to query 296 | --- @param callback fun(players: GPlayer[]) @Callback to receive the answer 297 | --- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban) 298 | --- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod 299 | function CAMI.GetPlayersWithAccess(privilegeName, callback, targetPly, 300 | extraInfoTbl) 301 | local allowedPlys = {} 302 | local allPlys = player.GetAll() 303 | local countdown = #allPlys 304 | 305 | local function onResult(ply, hasAccess, _) 306 | countdown = countdown - 1 307 | 308 | if hasAccess then table.insert(allowedPlys, ply) end 309 | if countdown == 0 then callback(allowedPlys) end 310 | end 311 | 312 | for _, ply in ipairs(allPlys) do 313 | CAMI.PlayerHasAccess(ply, privilegeName, 314 | function(...) onResult(ply, ...) end, 315 | targetPly, extraInfoTbl) 316 | end 317 | end 318 | 319 | --- @class CAMI_STEAM_ACCESS_EXTRA_INFO 320 | --- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have. 321 | --- @field CommandArguments table @Extra arguments that were given to the privilege command. 322 | 323 | --- Checks if a (potentially offline) SteamID has access to a privilege 324 | --- (and optionally if they can execute it on a target SteamID) 325 | --- 326 | --- ℹ **NOTE**: This is an asynchronous function! 327 | --- @param actorSteam string | nil @The SteamID to query 328 | --- @param privilegeName string @The privilege to query 329 | --- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer 330 | --- @param targetSteam string | nil @Optional - target SteamID for if the privilege effects another player (eg kick/ban) 331 | --- @param extraInfoTbl CAMI_STEAM_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod 332 | function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback, 333 | targetSteam, extraInfoTbl) 334 | hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam, 335 | privilegeName, callback, targetSteam, extraInfoTbl) 336 | end 337 | 338 | --- Signify that your admin mod has changed the usergroup of a player. This 339 | --- function communicates to other admin mods what it thinks the usergroup 340 | --- of a player should be. 341 | --- 342 | --- Listen to the hook to receive the usergroup changes of other admin mods. 343 | --- @param ply GPlayer @The player for which the usergroup is changed 344 | --- @param old string @The previous usergroup of the player. 345 | --- @param new string @The new usergroup of the player. 346 | --- @param source any @Identifier for your own admin mod. Can be anything. 347 | function CAMI.SignalUserGroupChanged(ply, old, new, source) 348 | hook.Call("CAMI.PlayerUsergroupChanged", nil, ply, old, new, source) 349 | end 350 | 351 | --- Signify that your admin mod has changed the usergroup of a disconnected 352 | --- player. This communicates to other admin mods what it thinks the usergroup 353 | --- of a player should be. 354 | --- 355 | --- Listen to the hook to receive the usergroup changes of other admin mods. 356 | --- @param steamId string @The steam ID of the player for which the usergroup is changed 357 | --- @param old string @The previous usergroup of the player. 358 | --- @param new string @The new usergroup of the player. 359 | --- @param source any @Identifier for your own admin mod. Can be anything. 360 | function CAMI.SignalSteamIDUserGroupChanged(steamId, old, new, source) 361 | hook.Call("CAMI.SteamIDUsergroupChanged", nil, steamId, old, new, source) 362 | end 363 | --------------------------------------------------------------------------------