Files
CounterStrikeSharp/managed/CounterStrikeSharp.API/Core/Commands/CommandManager.cs

168 lines
7.4 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core.Commands;
public class CommandManager : ICommandManager
{
private readonly Dictionary<string, IList<CommandDefinition>> _commandDefinitions =
new(StringComparer.InvariantCultureIgnoreCase);
private readonly ILogger<CommandManager> _logger;
private readonly FunctionReference _internalFunctionReference;
public CommandManager(ILogger<CommandManager> logger)
{
_logger = logger;
_internalFunctionReference = FunctionReference.Create(HandleCommandInternal);
}
public void RegisterCommand(CommandDefinition definition)
{
bool isRegistered = true;
if (!_commandDefinitions.ContainsKey(definition.Name))
{
_commandDefinitions.Add(definition.Name, new List<CommandDefinition>());
isRegistered = false;
}
_commandDefinitions[definition.Name].Add(definition);
_logger.LogDebug("Registering command {Command}", definition.Name);
if (!isRegistered)
{
NativeAPI.AddCommand(definition.Name, definition.Description,
definition.ExecutableBy == CommandUsage.SERVER_ONLY,
(int)ConCommandFlags.FCVAR_LINKED_CONCOMMAND, _internalFunctionReference);
}
}
public void RemoveCommand(CommandDefinition definition)
{
_logger.LogDebug("Removing command {Command}", definition.Name);
if (_commandDefinitions.TryGetValue(definition.Name, out var commandDefinition))
{
commandDefinition.Remove(definition);
}
if (_commandDefinitions[definition.Name].Count == 0)
{
NativeAPI.RemoveCommand(definition.Name, _internalFunctionReference);
_commandDefinitions.Remove(definition.Name);
}
}
private void HandleCommandInternal(int playerSlot, IntPtr commandInfo)
{
var caller = (playerSlot != -1) ? Utilities.GetPlayerFromSlot(playerSlot) : null;
var info = new CommandInfo(commandInfo, caller);
var name = info.GetArg(0).ToLower();
using var temporaryCulture = new WithTemporaryCulture(caller.GetLanguage());
if (_commandDefinitions.TryGetValue(name, out var handler))
{
foreach (var command in handler.ToList())
{
if (!handler.Contains(command))
{
continue;
}
var methodInfo = command.Callback?.GetMethodInfo();
// We do not need to do permission checks on commands executed from the server console.
// The server will always be allowed to execute commands (unless marked as client only like above)
if (caller != null)
{
// Do not execute command if we do not have the correct permissions.
var adminData = AdminManager.GetPlayerAdminData(caller!.AuthorizedSteamID);
var permissionsToCheck = new List<BaseRequiresPermissions>();
// If our command is overriden, we dynamically create a new permissions attribute
// based on the data that is stored in admin_overrides.json.
if (AdminManager.CommandIsOverriden(name))
{
var data = AdminManager.GetCommandOverrideData(name);
if (data != null)
{
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
if (attr != null) permissionsToCheck.Add(attr);
}
}
// The permissions for this command are not being overriden here, so we
// grab the permissions to check straight from the attribute.
else
{
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null) permissionsToCheck.AddRange(permissions);
}
foreach (var attr in permissionsToCheck)
{
if (attr.Permissions.Count == 0)
{
continue;
}
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
var responseStr = (attr.GetType() == typeof(RequiresPermissions)) ?
"You are missing the correct permissions" : "You do not have one of the correct permissions";
var flags = attr.Permissions.Except(adminData?.GetAllFlags() ?? new HashSet<string>());
flags = flags.Except(adminData?.Groups ?? new HashSet<string>());
info.ReplyToCommand($"[CSS] {responseStr} ({string.Join(", ", flags)}) to execute this command.");
return;
}
}
}
// Do not execute if we shouldn't be calling this command.
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
if (helperAttribute != null)
{
switch (helperAttribute.WhoCanExcecute)
{
case CommandUsage.CLIENT_AND_SERVER: break; // Allow command through.
case CommandUsage.CLIENT_ONLY:
if (caller == null || !caller.IsValid) { info.ReplyToCommand("[CSS] This command can only be executed by clients."); return; }
break;
case CommandUsage.SERVER_ONLY:
if (caller != null && caller.IsValid) { info.ReplyToCommand("[CSS] This command can only be executed by the server."); return; }
break;
default: throw new ArgumentException("Unrecognised CommandUsage value passed in CommandHelperAttribute.");
}
// Technically the command itself counts as the first argument,
// but we'll just ignore that for this check.
if (helperAttribute.MinArgs != 0 && info.ArgCount - 1 < helperAttribute.MinArgs)
{
// Remove the "css_" from the beginning of the command name if it's present.
// Most of the time, users will be calling commands from chat.
var commandCalled = info.ArgByIndex(0);
var properCommandName = (commandCalled.StartsWith("css_")) ? commandCalled.Replace("css_", "") : commandCalled;
info.ReplyToCommand($"[CSS] Expected usage: \"!{properCommandName} {helperAttribute.Usage}\".");
return;
}
}
command.Callback?.Invoke(caller, info);
}
}
}
}