[SNIPPET] Anti command spam.

Started by ., Jun 20, 2017, 11:35 AM

Previous topic - Next topic

.

I was asked by someone new to the plugin how to proceed in making something like this. And because I thought it could be useful to more people I decided to make the snippet public. This example shows how the built-in command system can be used to create create such protection:

This is initialization stuff stuff that you usually do anyway:
// ------------------------------------------------------------------------------------------------
// We need to store 2 variables in the player .Data property so we can do it when he connects
// The 2 variables in question are 1) the last executed command name and the execution counter
SqCore.On().PlayerCreated.Connect(this, function(player, header, payload) {
    // Normally, everyone should store a table in the .Data property so that evereyone else can
    // store other stuff in that table. So we should have this somewhere:
    player.Data = { }; // making sure .Data is a table
    // Now we can create our variables in that table
    player.Data.LastCommand <- ""; // Empty for now
    player.Data.CommandExec <- 0; // Start from 0
});
// ------------------------------------------------------------------------------------------------
SqCore.On().PlayerCommand.Connect(this, function(player, command) {
    // Forward this command to the command manager
    g_Cmd.Run(player, command);
});

This is the actual example:
// ------------------------------------------------------------------------------------------------
// We need a command manager to be able to make and run commands.
g_Cmd <- SqCmd.Manager();
// ------------------------------------------------------------------------------------------------
// This is our global function we give to the command manager to tell whether someone is allowed
// or not to execute a certain function. If this function returns false, the command is ignored
function CmdGlobalPlayerAuth(player, command)
{
    // I guess we can use the default authority level to make the decision :/
    return (player.Authority >= command.Authority);
}
g_Cmd.BindAuth(getroottable(), CmdGlobalPlayerAuth);
// ------------------------------------------------------------------------------------------------
// This is a secondary function that we use as a proxy between the above authentication function
// Since each command can have it's own dedicate authentication function instead of just relying on a global one
// They only fall back to the global one if a dedicated one was not bound
function CmdSpamBlockPlayerAuth(player, command)
{
    // Grab the current time in seconds
    local tm = time();
    // We can store this local reference to avoid too much typing and table lookups
    // it'll be equal as doing command.Data each time we use cdata. except faster
    local cdata = command.Data;
    // Is there a temporary ban for this player on this command?
    if (cdata[player.ID] > tm)
    {
        player.Message("You are blocked from using this command for %d more second(s)!", cdata[player.ID] - tm);
        // Block the request to execute this command
        return false;
    }
    // We can store this local reference to avoid too much typing and table lookups
    // it'll be equal as doing player.Data each time we use pdata. except faster
    local pdata = player.Data;
    // Is the player trying to execute the last command again?
    if (pdata.LastCommand == command.Name)
    {
        // Increase the number that we use to tell how many times a command was used consecutively
        ++pdata.CommandExec;
    }
    else
    {
        // Store this command name so we can track it next time
        pdata.LastCommand = command.Name;
        // Reset the counter as this is a new command
        pdata.CommandExec = 0;
    }
    // Did the player executed this command more than 5 times consecutively?
    if (pdata.CommandExec > 5)
    {
        player.Message("You are blocked from using this command for 60 seconds as a result of spam!");
        // Add 60 seconds to the current time and set that as the cool down time
        cdata[player.ID] = tm + 60;
        Warning! if you sate that value too high and the player disconnects then connects back and someone else connects with his ID
        the new player gets the temporary ban while he can continue to use the command.
        If you need a better and more persistent protection than you need to be a little more creative than this.
        // Block the request to execute this command
        return false;
    }
    // If we reached there then fall back to the global function and let that decide the rest
    return CmdGlobalPlayerAuth(player, command)
}
// ------------------------------------------------------------------------------------------------
// Command instance
Test1 <- g_Cmd.Create("test1", "g", ["greeting"], 1, 1, -1, true, true);
// Stuff
Test1.Help = "This is a test command";
// We can use the .Data member within each command to store an array of integers/time-stamps
// that we can use to find out when is the next time that a player can use the command
Test1.Data = array(SqServer.GetMaxPlayers(), 0);
// Command function
Test1.BindExec(Test1, function(player, args)
{
    player.Message("You greeted with: %s", args.greeting);
    return true;
});
// Bind our spam detection function in the authentication stage
Test1.BindAuth(getroottable(), CmdSpamBlockPlayerAuth);
// ------------------------------------------------------------------------------------------------
// Command instance
Test2 <- g_Cmd.Create("test2", "g", ["announcement"], 1, 1, -1, true, true);
// Stuff
Test2.Help = "This is a test command";
// Same stuff explained above ^
Test2.Data = array(SqServer.GetMaxPlayers(), 0);
// Bind our spam detection function in the authentication stage
Test2.BindAuth(getroottable(), CmdSpamBlockPlayerAuth);
// ------------------------------------------------------------------------------------------------
Test2.BindExec(Test2, function(player, args)
{
    player.Message("You announcted that: %s", args.announcement);
    return true;
});
// ------------------------------------------------------------------------------------------------
// Command instance
Test3 <- g_Cmd.Create("test3", "g", ["message"], 1, 1, -1, true, true);
// Stuff
Test3.Help = "This is a test command";
// Same stuff explained above ^^
Test3.Data = array(SqServer.GetMaxPlayers(), 0);
// Bind our spam detection function in the authentication stage
Test3.BindAuth(getroottable(), CmdSpamBlockPlayerAuth);
// ------------------------------------------------------------------------------------------------
Test3.BindExec(Test3, function(player, args)
{
    player.Message("You said: %s", args.message);
    return true;
});

And this is a general purpose function bound to a command manager to report back errors:
/* ------------------------------------------------------------------------------------------------
 * General purpose error handler for a command manager.
 * Otherwise the player doesn't know why a command failed.
*/
g_Cmd.BindFail(getroottable(), function(type, msg, payload) {
    // Retrieve the player that executed the command
    local player = g_Cmd.Invoker;
    // See if the invoker even exists
    if (!player || typeof(player) != "SqPlayer")
    {
        return; // No one to report!
    }
    // pu this somewhere else in the script
    local _OWNER_CONTACT_ = "[email protected]";
    // Identify the error type
    switch (type)
    {
        // The command failed for unknown reasons
        case SqCmdErr.Unknown:
        {
            player.Message("Unable to execute the command for reasons unknown");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // The command failed to execute because there was nothing to execute
        case SqCmdErr.EmptyCommand:
        {
            player.Message("Cannot execute an empty command");
        } break;
        // The command failed to execute because the command name was invalid after processing
        case SqCmdErr.InvalidCommand:
        {
            player.Message("The specified command name is invalid");
        } break;
        // The command failed to execute because there was a syntax error in the arguments
        case SqCmdErr.SyntaxError:
        {
            player.Message("There was a syntax error in argument: %d", payload);
        } break;
        // The command failed to execute because there was no such command
        case SqCmdErr.UnknownCommand:
        {
            player.Message("The specified command does no exist");
        } break;
        // The command failed to execute because the it's currently suspended
        case SqCmdErr.ListenerSuspended:
        {
            player.Message("The requested command is currently suspended");
        } break;
        // The command failed to execute because the invoker does not have the proper authority
        case SqCmdErr.InsufficientAuth:
        {
            player.Message("You don't have the proper authority to execute this command");
        } break;
        // The command failed to execute because there was no callback to handle the execution
        case SqCmdErr.MissingExecuter:
        {
            player.Message("The specified command is not being processed");
        } break;
        // The command was unable to execute because the argument limit was not reached
        case SqCmdErr.IncompleteArgs:
        {
            player.Message("The specified command requires at least %d arguments", payload);
        } break;
        // The command was unable to execute because the argument limit was exceeded
        case SqCmdErr.ExtraneousArgs:
        {
            player.Message("The specified command requires no more than %d arguments", payload);
        } break;
        // Command was unable to execute due to argument type mismatch
        case SqCmdErr.UnsupportedArg:
        {
            player.Message("Argument %d requires a different type than the one you specified", payload);
        } break;
        // The command arguments contained more data than the internal buffer can handle
        case SqCmdErr.BufferOverflow:
        {
            player.Message("An internal error occurred and the execution was aborted");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // The command failed to complete execution due to a runtime exception
        case SqCmdErr.ExecutionFailed:
        {
            player.Message("The command failed to complete the execution properly");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // The command completed the execution but returned a negative result
        case SqCmdErr.ExecutionAborted:
        {
            player.Message("The command execution was aborted and therefore had no effect");
        } break;
        // The post execution callback failed to execute due to a runtime exception
        case SqCmdErr.PostProcessingFailed:
        {
            player.Message("The command post-processing stage failed to complete properly");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // The callback that was supposed to deal with the failure also failed due to a runtime exception
        case SqCmdErr.UnresolvedFailure:
        {
            player.Message("Unable to resolve the failures during command execution");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // Something bad happened and no one knows what
        default:
            SqLog.Inf("Command failed to execute because [%s][%s]", msg, ""+payload);
    }
});

The example uses some hard-coded values because that's what it is, an example. In reality, you shouldn't do that. Because it becomes annoying later to change.
.

DizzasTeR

I have made one or basically ported it from official squirrel plugin :D Useful for newcomers.

KrOoB_

@Doom_Kill3R i can't find your code antispam command