C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
namespace Oxide.Plugins
{
[Info("UFilter", "Wulf/lukespragg", "5.1.2")]
[Description("Prevents advertising and/or profanity and optionally punishes player")]
public class UFilter : CovalencePlugin
{
#region Configuration
private Configuration config;
public class Configuration
{
[JsonProperty("Check for advertising (true/false)")]
public bool CheckForAdvertising = true;
[JsonProperty("Check for profanity (true/false)")]
public bool CheckForProfanity = true;
[JsonProperty("Check player chat (true/false)")]
public bool CheckChat = true;
[JsonProperty("Check player names (true/false)")]
public bool CheckNames = true;
[JsonProperty("Log advertising (true/false)")]
public bool LogAdvertising = false;
[JsonProperty("Log profanity (true/false)")]
public bool LogProfanity = false;
[JsonProperty("Log to console (true/false)")]
public bool LogToConsole = false;
[JsonProperty("Log to file (true/false)")]
public bool LogToFile = false;
[JsonProperty("Warn player in chat (true/false)")]
public bool WarnInChat = true;
[JsonProperty("Action for advertising")]
public string ActionForAdvertising = "block";
[JsonProperty("Action for profanity")]
public string ActionForProfanity = "censor";
[JsonProperty("Word or symbol to use for censoring")]
public string CensorText = "*";
[JsonProperty("Allowed advertisements")]
public List<string> AllowedAds = new List<string>();
[JsonProperty("Allowed profanity")]
public List<string> AllowedProfanity = new List<string>();
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<Configuration>();
if (config == null)
{
LoadDefaultConfig();
}
}
catch
{
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig()
{
string configPath = $"{Interface.Oxide.ConfigDirectory}{Path.DirectorySeparatorChar}{Name}.json";
LogWarning($"Could not load a valid configuration file, creating a new configuration file at {configPath}");
config = new Configuration();
}
protected override void SaveConfig() => Config.WriteObject(config);
#endregion Configuration
#region Localization
private new void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["CommandUsage"] = "Usage:\n{0} <add/remove> <word or phrase>\n{0} <list> shows profanity list",
["NoAdvertising"] = "Advertising is not allowed on this server",
["NoData"] = "The profanity list is empty",
["NoProfanity"] = "Profanity is not allowed on this server",
["NotAllowed"] = "You are not allowed to use the '{0}' command",
["WordAdded"] = "Word '{0}' was added to the profanity list",
["WordListed"] = "Word '{0}' is already in the profanity list",
["WordNotListed"] = "Word '{0}' is not in the profanity list",
["WordRemoved"] = "Word '{0}' was removed from the profanity list",
}, this);
}
#endregion Localization
#region Initialization
[PluginReference]
private readonly Plugin BetterChat, Slap;
private const string permAdmin = "ufilter.admin";
private const string permBypass = "ufilter.bypass";
private static readonly Regex ipRegex = new Regex(@"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d{2,5})?)");
private static readonly Regex domainRegex = new Regex(@"(\w{2,}\.\w{2,3}\.\w{2,3}|\w{2,}\.\w{2,3}(:\d{2,5})?)$");
#region Blocked Words
private StoredData storedData;
private class StoredData
{
public readonly HashSet<string> Profanities = new HashSet<string>();
}
#endregion Blocked Words
private void OnServerInitialized()
{
storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
permission.RegisterPermission(permAdmin, this);
permission.RegisterPermission(permBypass, this);
if (BetterChat != null && BetterChat.IsLoaded)
{
#if RUST
Unsubscribe(nameof(OnPlayerChat));
#else
Unsubscribe(nameof(OnUserChat));
#endif
}
if (!config.CheckNames)
{
Unsubscribe(nameof(OnUserRespawned));
Unsubscribe(nameof(OnUserSpawned));
}
}
private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, storedData);
#endregion Initialization
#region Filter Matching
private string[] Advertisements(string text)
{
IEnumerable<string> ips = ipRegex.Matches(text).OfType<Match>().Select(m => m.Value);
IEnumerable<string> domains = domainRegex.Matches(text.ToLower()).OfType<Match>().Select(m => m.Value.ToLower());
return ips.Concat(domains).Where(a => !config.AllowedAds.Contains(a)).ToArray();
}
private string[] Profanities(string text)
{
return Regex.Split(text, @"\W").Where(w => storedData.Profanities.Contains(w.ToLower()) && !config.AllowedProfanity.Contains(w.ToLower())).ToArray();
}
#endregion Filter Matching
#region Text Processing
private string ProcessText(string text, IPlayer player)
{
if (player.HasPermission(permBypass))
{
return text;
}
string[] profanities = Profanities(text);
string[] advertisements = Advertisements(text);
if (config.CheckForProfanity && profanities.Length > 0)
{
if (config.WarnInChat)
{
Message(player, "NoProfanity", player.Id);
}
if (config.LogProfanity)
{
foreach (string profanity in profanities)
{
Log($"{player.Name} ({player.Id}) {DateTime.Now}: {profanity}", "profanity");
}
}
return TakeAction(player, text, profanities, config.ActionForProfanity, GetLang("NoProfanity", player.Id));
}
if (config.CheckForAdvertising && advertisements.Length > 0)
{
if (config.WarnInChat)
{
Message(player, "NoAdvertising", player.Id);
}
if (config.LogAdvertising)
{
foreach (string advertisement in advertisements)
{
Log($"{player.Name} ({player.Id}) {DateTime.Now}: {advertisement}", "ads");
}
}
return TakeAction(player, text, advertisements, config.ActionForAdvertising, GetLang("NoAdvertising", player.Id));
}
return text;
}
#endregion Text Processing
#region Action Processing
private string TakeAction(IPlayer player, string text, string[] list, string action, string reason)
{
if (string.IsNullOrEmpty(action))
{
return string.Empty;
}
switch (action.ToLower().Trim())
{
case "ban":
player.Ban(reason);
return string.Empty;
case "censor":
foreach (string word in list)
{
text = text.Replace(word, config.CensorText.Length == 1 ? new string(config.CensorText[0], word.Length) : config.CensorText);
}
return text;
case "kick":
player.Kick(reason);
return string.Empty;
case "kill":
player.Kill();
return string.Empty;
case "slap":
if (Slap != null && Slap.IsLoaded)
{
Slap.Call("SlapPlayer", player);
}
else
{
LogWarning("Slap plugin is not installed; slap action will not work");
}
return string.Empty;
default:
return string.Empty;
}
}
#endregion Action Processing
#region Chat Handling
private string HandleChat(IPlayer player, string message)
{
if (player == null || string.IsNullOrEmpty(message))
{
return null;
}
string processed = ProcessText(message, player);
if (processed == string.Empty)
{
return processed;
}
if (!string.Equals(message, processed))
{
if (BetterChat != null && BetterChat.IsLoaded)
{
return processed;
}
// Rust colors: Admin/moderator = #aaff55, Developer = #ffaa55, Player = #55aaff
string prefix = covalence.FormatText($"[{(player.IsAdmin ? "#aaff55" : "#55aaff")}]{player.Name}[/#]");
return $"{prefix}: {processed}";
}
return null;
}
private object OnBetterChat(Dictionary<string, object> data)
{
string processed = HandleChat(data["Player"] as IPlayer, data["Message"] as string);
if (!Equals(data["Message"] as string, processed) && !string.IsNullOrEmpty(processed))
{
data["Message"] = processed;
return data;
}
if (processed == string.Empty)
{
data["CancelOption"] = 2;
return data;
}
return null;
}
#if RUST
private object OnPlayerChat(BasePlayer basePlayer, string message, ConVar.Chat.ChatChannel channel)
{
string processed = HandleChat(basePlayer.IPlayer, message);
if (!string.IsNullOrEmpty(processed))
{
Broadcast(basePlayer.IPlayer, processed, (int)channel);
return true;
}
if (processed == string.Empty)
{
return true;
}
return null;
}
#else
private object OnUserChat(IPlayer player, string message)
{
if (BetterChat == null || !BetterChat.IsLoaded)
{
string processed = HandleChat(player, message);
if (!string.IsNullOrEmpty(processed))
{
Broadcast(player, processed);
return true;
}
if (processed == string.Empty)
{
return true;
}
}
return null;
}
#endif
#endregion Chat Handling
#region Name Handling
private void ProcessName(IPlayer player)
{
string processed = ProcessText(player.Name, player);
if (player.Name != processed)
{
player.Rename(processed);
}
else if (string.IsNullOrEmpty(processed))
{
player.Rename("Unnamed" + new System.Random()); // TODO: Config option
}
}
private void OnUserRespawned(IPlayer player) => ProcessName(player);
private void OnUserSpawned(IPlayer player) => ProcessName(player);
#endregion Name Handling
#region API
private string[] GetProfanities() => storedData?.Profanities.ToArray();
private string[] GetAllowedProfanity() => config?.AllowedProfanity.ToArray();
private bool AddProfanity(string profanity)
{
if (storedData == null || storedData.Profanities.Contains(profanity))
{
return false;
}
storedData.Profanities.Add(profanity);
Interface.CallHook("OnProfanityAdded", profanity);
return true;
}
private bool RemoveProfanity(string profanity)
{
if (storedData == null || storedData.Profanities.Contains(profanity))
{
return false;
}
storedData.Profanities.Remove(profanity);
Interface.CallHook("OnProfanityRemoved", profanity);
return true;
}
#endregion API
#region Commands
[Command("ufilter")] // TODO: Localization
private void FilterCommand(IPlayer player, string command, string[] args)
{
if (!player.HasPermission(permAdmin))
{
Message(player, "NotAllowed", command);
return;
}
if (args.Length < 1 || args.Length < 2 && args[0].ToLower() != "list")
{
Message(player, "CommandUsage", command);
return;
}
string argList = string.Join(" ", args.Skip(1).ToArray());
switch (args[0].ToLower())
{
case "+":
case "add":
if (!AddProfanity(argList))
{
Message(player, "WordListed", argList);
break;
}
Message(player, "WordAdded", argList);
break;
case "-":
case "del":
case "delete":
case "remove":
if (!RemoveProfanity(argList))
{
Message(player, "WordNotListed", argList);
break;
}
Message(player, "WordRemoved", argList);
break;
case "list":
string message = string.Join(", ", storedData.Profanities);
Message(player, string.IsNullOrEmpty(message) ? "NoData" : message);
break;
default:
Message(player, "CommandUsage", command);
break;
}
}
#endregion Commands
#region Helpers
private void AddLocalizedCommand(string key, string command)
{
foreach (string language in lang.GetLanguages(this))
{
Dictionary<string, string> messages = lang.GetMessages(language, this);
foreach (KeyValuePair<string, string> message in messages.Where(m => m.Key.Equals(key)))
{
if (!string.IsNullOrEmpty(message.Value))
{
AddCovalenceCommand(message.Value, command);
}
}
}
}
private void Broadcast(IPlayer sender, string text, int channel = 0)
{
#if RUST
foreach (IPlayer target in players.Connected)
{
target.Command("chat.add", channel, sender.Id, text);
}
#else
server.Broadcast(text);
#endif
}
private string GetLang(string langKey, string playerId = null, params object[] args)
{
return string.Format(lang.GetMessage(langKey, this, playerId), args);
}
private void Message(IPlayer player, string textOrLang, params object[] args)
{
if (player.IsConnected)
{
string message = GetLang(textOrLang, player.Id, args);
player.Reply(message != textOrLang ? message : textOrLang);
}
}
private void Log(string text, string filename)
{
if (config.LogToConsole)
{
Puts(text);
}
if (config.LogToFile)
{
LogToFile(filename, $"[{DateTime.Now}] {text}", this);
}
}
#endregion Helpers
}
}