C#:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine;
using System.Linq;
using System.Text;
using Facepunch.Rust;
namespace Oxide.Plugins
{
[Info("Command Spam Blocker", "Khan", "1.0.1")]
[Description("Prevents spamming of chat & console commands")]
public class CommandSpamBlocker : RustPlugin
{
#region Fields
private static Configuration _config;
private const string Chat = "chat.say";
[Flags]
private enum Type
{
All = Chat | Console,
Chat = 1,
Console = 2,
}
private static float _cd;
private static float _cdm;
private HashSet<string> _commands = new HashSet<string>();
private Hash<ulong, float> _cooldowns = new Hash<ulong, float>();
private static HashSet<ulong> _ignore;
#endregion
#region Config
private class Configuration
{
[JsonProperty("Add commands to block ( Must be lowercased do not add the slash! / ) ( 0 = Block Both | 1 = Block Chat | 2 = Block Console )")]
public Hash<string, Type> Commands;
[JsonProperty("Sets the time between command usage")]
public float CoolDownTime = 3f;
[JsonProperty("Sets the max accumulated cooldown time limit before kicking the player if they keep trying to spam")]
public float CoolDownTimeMax = 120.0f;
[JsonProperty("Exclude players from being blocked ( Add their steam 64 id's to the list )", ObjectCreationHandling = ObjectCreationHandling.Auto)]
public HashSet<ulong> Ignore;
[JsonProperty("Message Responses")]
public Lang Msg = new Lang();
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
}
private class Lang
{
[JsonProperty("( Chat Icon ) Just insert a valid steam 64 ID to set")]
public ulong ChatIcon = 0;
public string CommandBlocked = "You are on command cooldown, try again in {0} seconds";
public string KickReason = "Kicked for excessive command spam!";
}
protected override void LoadDefaultConfig() => _config = new Configuration();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config == null)
throw new JsonException();
if (!_config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys))
{
PrintWarning("Configuration appears to be outdated; Updating and saving");
SaveConfig();
}
}
catch
{
PrintError($"Configuration file {Name}.json is invalid; using defaults");
LoadDefaultConfig();
}
}
protected override void SaveConfig()
{
PrintToConsole($"Configuration changes saved to {Name}.json");
Config.WriteObject(_config, true);
}
private void Init() => Unsubscribe(nameof (OnPlayerCommand));
private void Loaded()
{
if (_config.Ignore.IsNullOrEmpty()) _config.Ignore = new HashSet<ulong>();
if (_config.Commands.IsNullOrEmpty()) _config.Commands = new Hash<string, Type> { { "command", Type.All }, };
SaveConfig();
_ignore = _config.Ignore;
if (!_ignore.IsNullOrEmpty())
{
StringBuilder sb = new StringBuilder();
int count = 0;
for (int i = 0; i < _config.Ignore.Count; i++)
{
ulong dingus = _config.Ignore.ElementAt(i);
if (!dingus.IsSteamId())
{
sb.Append($"line {i} {dingus}\n");
count++;
}
}
PrintError($"{count} of {_config.Ignore.Count} steam ID's are not valid please double check and fix them!\n{sb}");
sb.Clear();
}
_cd = _config.CoolDownTime;
_cdm = _config.CoolDownTimeMax;
string name = _config.Commands.First().Key;
if (_config.Commands.Count == 1 && (name == "command" || string.IsNullOrEmpty(name))) return;
foreach (var c in _config.Commands)
_commands.Add(c.Key);
Subscribe(nameof (OnPlayerCommand));
}
private void Unload()
{
_commands.Clear();
_ignore.Clear();
_config = null;
}
#endregion
#region Oxide
private void OnPlayerDisconnected(BasePlayer player) => _cooldowns.Remove(player.userID.Get());
private object OnPlayerCommand(BasePlayer player, string command, string[] args) => player == null ? null : _ignore.Contains(player.userID.Get()) ? null : CanDo(command, player);
private object OnServerCommand(ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Player();
if (player != null && arg.cmd.FullName != Chat)
{
string command = arg.cmd.Name;
string fullCommand = arg.cmd.FullName;
if (!string.IsNullOrEmpty(arg.FullString.ToString()))
{
command += $" {arg.FullString}";
fullCommand += $" {arg.FullString}";
}
foreach (var c in _config.Commands)
{
if (c.Value != Type.Console && c.Value != Type.All) continue;
if ((command.StartsWith(c.Key, StringComparison.OrdinalIgnoreCase) || fullCommand.StartsWith(c.Key, StringComparison.OrdinalIgnoreCase)) && HasCommandCooldown(player))
return true;
}
}
return null;
}
#endregion
#region Helpers
private object CanDo(string command, BasePlayer player)
{
foreach (string c in _commands)
{
if (!c.Contains(command, StringComparison.OrdinalIgnoreCase)) continue;
command = command.ToLower();
if ((_config.Commands[command].HasFlag(Type.Chat) || _config.Commands[command].HasFlag(Type.Console)) && HasCommandCooldown(player))
return true;
break;
}
return null;
}
private bool HasCommandCooldown(BasePlayer player)
{
ulong user = player.userID.Get();
float current = Time.realtimeSinceStartup;
float cooldown;
if (!_cooldowns.TryGetValue(user, out cooldown)) _cooldowns[user] = cooldown = current - 30f;
if (cooldown > current)
{
_cooldowns[user] = cooldown += 1f;
float remaining = cooldown - current;
if (remaining <= _cdm) Response(player, _config.Msg.CommandBlocked, $"{remaining:0}");
else player.Kick(_config.Msg.KickReason);
return true;
}
_cooldowns[user] = current + _cd;
return false;
}
private static void Response(BasePlayer player, string message, params object[] args)
{
if (player == null) return;
ConsoleNetwork.SendClientCommand(player.Connection, "chat.add", 2, _config.Msg.ChatIcon, string.Format(message, args));
}
#endregion
}
}