C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
#if RUST
using Newtonsoft.Json.Linq;
#endif
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Time = Oxide.Core.Libraries.Time;
namespace Oxide.Plugins {
[Info("Server Armour", "Pho3niX90", "0.2.8")]
[Description("Protect your server! Auto ban known hacker, scripter and griefer accounts, and notify server owners of threats.")]
class ServerArmour : CovalencePlugin {
#region Variables
Dictionary<string, ISAPlayer> _playerData = new Dictionary<string, ISAPlayer>();
private Time _time = GetLibrary<Time>();
private double cacheLifetime = 15;
// minutes
string[] groups;
private SAConfig config;
string specifier = "G";
int secondsBetweenWebRequests;
CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
StringComparison defaultCompare = StringComparison.InvariantCultureIgnoreCase;
const string DATE_FORMAT = "yyyy/MM/dd HH:mm";
#region Permissions
const string PermissionToBan = "serverarmour.ban";
const string PermissionToUnBan = "serverarmour.unban";
const string PermissionWhitelistRecentVacKick = "serverarmour.whitelist.recentvac";
const string PermissionWhitelistBadIPKick = "serverarmour.whitelist.badip";
const string PermissionWhitelistKeywordKick = "serverarmour.whitelist.keyword";
const string PermissionWhitelistVacCeilingKick = "serverarmour.whitelist.vacceiling";
const string PermissionWhitelistServerCeilingKick = "serverarmour.whitelist.banceiling";
const string PermissionWhitelistGameBanCeilingKick = "serverarmour.whitelist.gamebanceiling";
const string PermissionWhitelistHardwareOwnsBloody = "serverarmour.whitelist.hardware.ownsbloody";
const string PermissionHardwareOwnsBloody = "serverarmour.hardware.ownsbloody";
#endregion
#endregion
#region Plugins
[PluginReference]
Plugin BetterChat;
[PluginReference]
Plugin DiscordMessages;
[PluginReference]
Plugin Arkan;
// [PluginReference] Plugin Ember; //TODO add support
// [PluginReference] Plugin EnhancedBanSystem; //TODO add support
void DiscordSend(ISAPlayer iPlayer, string report) {
DiscordSend(players.FindPlayer(iPlayer.steamid), new EmbedFieldList() {
name = "Report", value = report, inline = true
}
);
}
void DiscordSend(IPlayer iPlayer, EmbedFieldList report, int color = 39423) {
if (config.DiscordWebhookURL.Length == 0 && !config.DiscordWebhookURL.Equals("https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks")) {
Puts("Discord webhook not setup.");
return;
}
List<EmbedFieldList> fields = new List<EmbedFieldList>();
if (config.DiscordQuickConnect) {
fields.Add(new EmbedFieldList() {
name = server.Name, value = $"[steam://connect/{server.Address}:{server.Port}](steam://connect/{server.Address}:{server.Port})", inline = true
}
);
}
fields.Add(new EmbedFieldList() {
name = "Player ", value = $"[{iPlayer.Name}\n{iPlayer.Id}](https://steamcommunity.com/profiles/{iPlayer.Id})", inline = !config.DiscordQuickConnect
}
);
fields.Add(report);
var fieldsObject = fields.Cast<object>().ToArray();
string json = JsonConvert.SerializeObject(fieldsObject);
DiscordMessages?.Call("API_SendFancyMessage", config.DiscordWebhookURL, "Server Armour Report: ", color, json);
}
#endregion
#region Hooks
void Init() {
config = new SAConfig(this);
LoadData();
Puts("Server Armour is being initialized.");
SaveConfig();
CheckGroups();
permission.RegisterPermission(PermissionToBan, this);
permission.RegisterPermission(PermissionToUnBan, this);
permission.RegisterPermission(PermissionWhitelistBadIPKick, this);
permission.RegisterPermission(PermissionWhitelistKeywordKick, this);
permission.RegisterPermission(PermissionWhitelistRecentVacKick, this);
permission.RegisterPermission(PermissionWhitelistServerCeilingKick, this);
permission.RegisterPermission(PermissionWhitelistVacCeilingKick, this);
permission.RegisterPermission(PermissionWhitelistGameBanCeilingKick, this);
permission.RegisterPermission(PermissionHardwareOwnsBloody, this);
}
void OnServerInitialized() {
CheckOnlineUsers();
CheckLocalBans();
Puts("Server Armour finished initializing.");
RegisterTag();
}
void Unload() {
Puts("Server Armour unloading, will now save all data.");
SaveData();
_playerData.Clear();
Puts("Server Armour finished unloaded.");
}
void OnUserConnected(IPlayer player) {
GetPlayerBans(player, true, "C");
if (config.ShowProtectedMsg) player.Reply(GetMsg("Protected MSG"));
}
void OnUserDisconnected(IPlayer player) {
GetPlayerBans(player, true, "D");
SaveThenPurge(player.Id);
}
void OnPluginLoaded(Plugin plugin) {
if (plugin.Title == "BetterChat") RegisterTag();
}
void OnUserUnbanned(string name, string id, string ipAddress) {
Puts($"Player {name} ({id}) at {ipAddress} was unbanned");
IPlayer iPlayer = players.FindPlayer(id);
Unban(iPlayer);
}
void OnUserKicked(IPlayer player, string reason) {
Puts($"Player {player.Name} ({player.Id}) was kicked, {reason}");
if (reason.ToLower().Contains("bloody") || reason.ToLower().Contains("a4") || reason.ToLower().Contains("blacklisted")) {
permission.GrantUserPermission(player.Id, PermissionHardwareOwnsBloody, this);
webrequest.Enqueue("https://io.serverarmour.com/addBloodyKicks" + ServerGetString("?"), $"steamid={player.Id}&reason={reason}", (code, response) => {
if (code != 200 || response == null) {
Puts(GetMsg("No Response From API", new Dictionary<string, string> {
["code"]
= code.ToString(), ["response"] = response
}
));
return;
}
}
, this, RequestMethod.POST);
}
}
#endregion
#region API_Hooks
#endregion
#region WebRequests
bool reportSent = false;
void GetPlayerBans(IPlayer player, bool reCache = false, string type = "C") {
if (type == "C" && IsCacheValid(player?.Id)) KickIfBanned(GetPlayerCache(player?.Id));
_webCheckPlayer(player.Name, player.Id, player.Address, player.IsConnected, type);
}
void _webCheckPlayer(string name, string id, string address, Boolean connected, string type) {
string playerName = Uri.EscapeDataString(name);
string url = $"https://io.serverarmour.com/checkUser?steamid={id}&username={playerName}&ip={address}&t={type}" + ServerGetString();
//Puts(url);
webrequest.Enqueue(url, null, (code, response) => {
if (code != 200 || response == null) {
Puts(url);
Puts(GetMsg("No Response From API", new Dictionary<string, string> {
["code"]
= code.ToString(), ["response"] = response
}
));
return;
}
ISAPlayer isaPlayer = JsonConvert.DeserializeObject<ISAPlayer>(response);
isaPlayer.cacheTimestamp = _time.GetUnixTimestamp();
isaPlayer.lastConnected = _time.GetUnixTimestamp();
if (isaPlayer == null || string.IsNullOrEmpty(isaPlayer.steamid)) {
Puts("Something went wrong, please inform the developer (PM Pho3niX90 on https://umod.org).Info to follow:");
Puts(response.ToString());
Puts(url);
return;
}
// add cache for player
if (!IsPlayerCached(isaPlayer.steamid)) {
AddPlayerCached(isaPlayer);
}
else {
UpdatePlayerData(isaPlayer);
}
// lets check bans first
try {
KickIfBanned(isaPlayer);
}
catch (ArgumentNullException ane) {
Puts("An ArgumentNullException occured.Please notify the developer along with the below information: ");
Puts($"PlayerName `{playerName}`\nUrl: `{url}`\nIsaPlayer? {isaPlayer != null}\nIsLender {isaPlayer?.lendersteamid}");
}
// now lets check for a recent vac
if (config.AutoKickOn && !HasPermission(isaPlayer.steamid, PermissionWhitelistRecentVacKick) && HasRecentVac(isaPlayer?.steamid)) {
int vacLast = GetPlayerCache(isaPlayer?.steamid).steamData.DaysSinceLastBan;
int until = config.DissallowVacBanDays - vacLast;
Interface.CallHook("OnSARecentVacKick", isaPlayer.steamid, vacLast, until);
string msg = GetMsg("Reason: VAC Ban Too Fresh", new Dictionary<string, string> {
["daysago"]
= vacLast.ToString(), ["daysto"] = until.ToString()
}
);
KickPlayer(isaPlayer?.steamid, msg, type);
}
// lets check if this user is using VPN
if (config.AutoKickOn && !HasPermission(isaPlayer.steamid, PermissionWhitelistBadIPKick) && config.AutoKick_BadIp && IsVpn(isaPlayer)) {
Interface.CallHook("OnSAVPNKick", isaPlayer.steamid, isaPlayer.ipRating);
KickPlayer(isaPlayer?.steamid, GetMsg("Reason: Bad IP"), type);
}
// does the user contain a keyword ban
if (!HasPermission(isaPlayer.steamid, PermissionWhitelistKeywordKick) && HasKeywordBan(isaPlayer)) {
Interface.CallHook("OnSAKeywordKick", isaPlayer.steamid);
KickPlayer(isaPlayer?.steamid, GetMsg("Keyword Kick"), type);
}
// Kick players with too many VACs
if (!HasPermission(isaPlayer.steamid, PermissionWhitelistVacCeilingKick) && HasReachedVacCeiling(isaPlayer)) {
Interface.CallHook("OnSATooManyVacKick", isaPlayer.steamid, isaPlayer.steamData.NumberOfVACBans);
KickPlayer(isaPlayer?.steamid, GetMsg("VAC Ceiling Kick"), type);
}
// Kick players with too many game bans
if (!HasPermission(isaPlayer.steamid, PermissionWhitelistGameBanCeilingKick) && HasReachedGameBanCeiling(isaPlayer)) {
Interface.CallHook("OnSATooManyGameBansKick", isaPlayer.steamid, isaPlayer.steamData.NumberOfGameBans);
KickPlayer(isaPlayer?.steamid, GetMsg("Too Many Previous Game Bans"), type);
}
// Kick bloody/a4 owners
if (!HasPermission(isaPlayer.steamid, PermissionWhitelistHardwareOwnsBloody) && (OwnsBloody(isaPlayer) || HasPermission(isaPlayer.steamid, PermissionHardwareOwnsBloody))) {
Interface.CallHook("OnSABloodyKick", isaPlayer.steamid);
KickPlayer(isaPlayer?.steamid, GetMsg("Kick Bloody"), type);
}
// Kick players with too many bans
if (!HasPermission(isaPlayer.steamid, PermissionWhitelistServerCeilingKick) && HasReachedServerCeiling(isaPlayer)) {
Interface.CallHook("OnSATooManyBans", isaPlayer.steamid, config.AutoKickCeiling, isaPlayer.serverBanCount);
KickPlayer(isaPlayer?.steamid, GetMsg("Too Many Previous Bans"), type);
}
GetPlayerReport(isaPlayer, connected);
}
, this, RequestMethod.GET);
}
void KickIfBanned(ISAPlayer isaPlayer) {
if (isaPlayer == null) return;
ISABan ban = IsBanned(isaPlayer?.steamid);
if (ban != null) KickPlayer(isaPlayer?.steamid, ban.reason, "C");
if (!isaPlayer.lendersteamid.Equals("0")) {
ISABan lenderBan = IsBanned(isaPlayer?.lendersteamid);
if (lenderBan != null) KickPlayer(isaPlayer?.steamid, GetMsg("Lender Banned"), "C");
}
}
void AddBan(IPlayer player, ISABan thisBan) {
DateTime now = DateTime.Now;
string url = $"https://io.serverarmour.com/addBan?steamid={player.Id}&username={player.Name}&ip={player.Address}&reason={thisBan.reason}&dateTime={thisBan.date}&dateUntil={thisBan.banUntil}" + ServerGetString();
webrequest.Enqueue(url, null, (code, response) => {
if (code != 200 || response == null) {
Puts(GetMsg("No Response From API", new Dictionary<string, string> {
["code"]
= code.ToString(), ["response"] = response
}
));
return;
}
// ISABan thisBan = new ISABan { serverName = server.Name, date = dateTime, reason = banreason, serverIp = thisServerIp, banUntil = dateBanUntil };
if (IsPlayerCached(player.Id)) {
LogDebug($"{player.Name} has ban cached, now updating.");
AddPlayerData(player, thisBan);
}
else {
LogDebug($"{player.Name} had no ban data cached, now creating.");
AddPlayerCached(player, new ISAPlayer {
steamid = player.Id, username = player.Name, serverBanCount = 1, cacheTimestamp = _time.GetUnixTimestamp(), lastConnected = _time.GetUnixTimestamp(), serverBanData = new List<ISABan> {
thisBan
}
}
);
}
//SaveData();
}
, this, RequestMethod.GET);
}
string GetBanReason(ISAPlayer isaPlayer) {
return isaPlayer?.serverBanData.First(x => x.serverIp.Equals(server.Address)).reason;
}
#endregion
#region Commands
[Command("sa.clb", "getreport")]
void SCmdCheckLocalBans(IPlayer player, string command, string[] args) {
CheckLocalBans();
}
[Command("unban", "playerunban", "sa.unban")]
void SCmdUnban(IPlayer player, string command, string[] args) {
LogDebug("Will now unban");
if (!HasPermission(player.Id, PermissionToUnBan) && !player.IsServer) {
player.Reply(GetMsg("NoPermission"));
return;
}
if (args == null || (args.Length != 1)) {
player.Reply(GetMsg("UnBan Syntax"));
return;
}
IPlayer iPlayer = players.FindPlayer(args[0]);
if (iPlayer == null) {
GetMsg("Player Not Found", new Dictionary<string, string> {
["player"]
= args[0]
}
);
return;
}
if (!IsPlayerCached(iPlayer.Id) || !ContainsMyBan(iPlayer.Id)) {
player.Reply(GetMsg("Player Not Banned"));
return;
}
Unban(iPlayer);
}
[Command("ban", "playerban", "sa.ban")]
void SCmdBan(IPlayer player, string command, string[] args) {
if (!HasPermission(player.Id, PermissionToBan) && !player.IsServer) {
player.Reply(GetMsg("NoPermission"));
return;
}
int argsLength = args.Length;
/***
* Length 2: player, reason
* Length 3: player, reason, time
***/
if (args == null || (argsLength < 2 || argsLength > 3)) {
player.Reply(GetMsg("Ban Syntax"));
return;
}
string banPlayer = args[0];
string errMsg = "";
IPlayer iPlayer = null;
IEnumerable<IPlayer> playersFound = players.FindPlayers(banPlayer);
int playersFoundCount = playersFound.Count();
switch (playersFoundCount) {
case 0 : errMsg = GetMsg("Player Not Found", new Dictionary<string, string> {
["player"]
= banPlayer
}
);
break;
case 1 : iPlayer = players.FindPlayer(banPlayer);
break;
default : List<string> playersFoundNames = new List<string>();
for (int i = 0; i < playersFoundCount; i++) playersFoundNames.Add(playersFound.ElementAt(i).Name);
string playersFoundNamesString = String.Join(", ", playersFoundNames.ToArray());
errMsg = GetMsg("Multiple Players Found", new Dictionary<string, string> {
["players"]
= playersFoundNamesString
}
);
break;
}
if (iPlayer == null || !errMsg.Equals("")) {
player.Reply(errMsg);
return;
}
string banReason = args[1];
/***
* If time specified, default to 100 years
***/
string lengthOfBan = argsLength != 3 ? "100y" : args[2];
ISAPlayer isaPlayer;
if (!IsPlayerCached(iPlayer.Id)) {
isaPlayer = new ISAPlayer().CreatePlayer(iPlayer);
AddPlayerCached(isaPlayer);
}
else {
isaPlayer = GetPlayerCache(banPlayer);
}
DateTime now = DateTime.Now;
string dateTime = now.ToString(DATE_FORMAT);
string dateBanUntil = _BanUntil(lengthOfBan).ToString(DATE_FORMAT);
if (BanPlayer(iPlayer, new ISABan {
serverName = server.Name, serverIp = server.Address.ToString(), reason = banReason, date = dateTime, banUntil = dateBanUntil
}
)) {
string msg;
//msg = GetMsg("Player Now Banned", new Dictionary<string, string> { ["player"] = iPlayer.Name, ["reason"] = args[1] });
msg = GetMsg("Player Now Banned Perma", new Dictionary<string, string> {
["player"]
= iPlayer.Name, ["reason"] = args[1], ["length"] = BanFor(lengthOfBan)
}
);
if (config.DiscordBanReport) {
DiscordSend(iPlayer, new EmbedFieldList() {
name = "Player Banned", value = msg, inline = true
}
, 13459797);
}
if (config.BroadcastNewBans) {
server.Broadcast(msg);
}
else {
player.Reply(msg);
}
}
}
[Command("sa.cp")]
void SCmdCheckPlayer(IPlayer player, string command, string[] args) {
string playerArg = (args.Length == 0) ? player.Id : args[0];
IPlayer playerToCheck = players.FindPlayer(playerArg.Trim());
if (playerToCheck == null) {
player.Reply(GetMsg("Player Not Found", new Dictionary<string, string> {
["player"]
= playerArg
}
));
return;
}
GetPlayerReport(playerToCheck, player);
}
#endregion
#region VPN/Proxy
#endregion
#region Ban System
DateTime _BanUntil(string banLength) {
int digit = int.Parse(new string(banLength.Where(char.IsDigit).ToArray()));
string del = new string(banLength.Where(char.IsLetter).ToArray());
DateTime now = DateTime.Now;
string dateTime = now.ToString(DATE_FORMAT);
DateTime dateBanUntil;
switch (del.ToUpper()) {
case "MI" : dateBanUntil = now.AddMinutes(digit);
break;
case "H" : dateBanUntil = now.AddHours(digit);
break;
case "D" : dateBanUntil = now.AddDays(digit);
break;
case "M" : dateBanUntil = now.AddMonths(digit);
break;
case "Y" : dateBanUntil = now.AddYears(digit);
break;
default : dateBanUntil = now.AddDays(digit);
break;
}
return dateBanUntil;
}
string BanFor(string banLength) {
int digit = int.Parse(new string(banLength.Where(char.IsDigit).ToArray()));
string del = new string(banLength.Where(char.IsLetter).ToArray());
DateTime now = DateTime.Now;
string dateTime = now.ToString(DATE_FORMAT);
string dateBanUntil;
switch (del.ToUpper()) {
case "MI" : dateBanUntil = digit + " Minutes";
break;
case "H" : dateBanUntil = digit + " Hours";
break;
case "D" : dateBanUntil = digit + " Days";
break;
case "M" : dateBanUntil = digit + " Months";
break;
case "Y" : dateBanUntil = digit + " Years";
break;
default : dateBanUntil = digit + " Days";
break;
}
return dateBanUntil;
}
void Unban(IPlayer iPlayer) {
if (iPlayer.IsBanned) iPlayer.Unban();
RemoveBans(iPlayer.Id);
}
void RemoveBans(string id) {
if (_playerData.ContainsKey(id)) {
_playerData[id].serverBanData.RemoveAll(x => x.serverIp == server.Address.ToString());
SavePlayerData(id);
}
}
bool BanPlayer(IPlayer iPlayer, ISABan ban) {
AddBan(iPlayer, ban);
KickPlayer(iPlayer.Id, ban.reason, "U");
return true;
}
#endregion
#region IEnumerators
void CheckOnlineUsers() {
IEnumerable<IPlayer> allPlayers = players.Connected;
int allPlayersCount = allPlayers.Count();
int allPlayersCounter = 0;
float waitTime = 1f;
if (allPlayersCount > 0) timer.Repeat(waitTime, allPlayersCount, () => {
LogDebug("Will now inspect all online users, time etimation: " + (allPlayersCount * waitTime) + " seconds");
LogDebug($"Inpecting online user {allPlayersCounter + 1} of {allPlayersCount} for infractions");
IPlayer player = allPlayers.ElementAt(allPlayersCounter);
if (player != null) GetPlayerBans(player, true, "U");
if (allPlayersCounter < allPlayersCount) LogDebug("Inspection completed.");
allPlayersCounter++;
}
);
}
void CheckLocalBans() {
#if RUST
IEnumerable<ServerUsers.User> bannedUsers = ServerUsers.GetAll(ServerUsers.UserGroup.Banned);
int BannedUsersCount = bannedUsers.Count();
int BannedUsersCounter = 0;
float waitTime = 1f;
if (BannedUsersCount > 0) timer.Repeat(waitTime, BannedUsersCount, () => {
ServerUsers.User usr = bannedUsers.ElementAt(BannedUsersCounter);
LogDebug($"Checking local user ban {BannedUsersCounter + 1} of {BannedUsersCounter}");
if (IsBanned(usr.steamid.ToString(specifier, culture)) == null) {
IPlayer player = covalence.Players.FindPlayer(usr.steamid.ToString(specifier, culture));
AddBan(player, new ISABan {
serverName = server.Name, serverIp = server.Address.ToString(), reason = usr.notes, date = DateTime.Now.ToString(DATE_FORMAT), banUntil = usr.expiry.ToString()
}
);
}
BannedUsersCounter++;
}
);
#endif
}
#endregion
#region Data Handling
string GetFamilyShareLenderSteamId(string steamid) {
return GetPlayerCache(steamid)?.lendersteamid;
}
bool IsPlayerDirty(ISAPlayer isaPlayer) {
return isaPlayer != null && (isaPlayer?.serverBanCount > 0 || isaPlayer?.steamData?.CommunityBanned > 0 || isaPlayer?.steamData?.NumberOfGameBans > 0 || isaPlayer?.steamData?.VACBanned > 0);
}
bool IsPlayerDirty(string steamid) {
ISAPlayer isaPlayer = GetPlayerCache(steamid);
return isaPlayer != null && IsPlayerCached(steamid) && (isaPlayer?.serverBanCount > 0 || isaPlayer?.steamData?.CommunityBanned > 0 || isaPlayer?.steamData?.NumberOfGameBans > 0 || isaPlayer?.steamData?.VACBanned > 0);
}
bool IsPlayerCached(string steamid) {
return _playerData != null && _playerData.Count > 0 && _playerData.ContainsKey(steamid);
}
void AddPlayerCached(ISAPlayer isaplayer) => _playerData.Add(isaplayer.steamid, isaplayer);
void AddPlayerCached(IPlayer iplayer, ISAPlayer isaplayer) => _playerData.Add(iplayer.Id, isaplayer);
ISAPlayer GetPlayerCache(string steamid) {
LoadPlayerData(steamid);
return IsPlayerCached(steamid) ? _playerData[steamid] : null;
}
List<ISABan> GetPlayerBanData(string steamid) => _playerData[steamid].serverBanData;
int GetPlayerBanDataCount(string steamid) => _playerData[steamid].serverBanData.Count;
void UpdatePlayerData(ISAPlayer isaplayer) => _playerData[isaplayer.steamid] = isaplayer;
void AddPlayerData(IPlayer iplayer, ISABan isaban) => _playerData[iplayer.Id].serverBanData.Add(isaban);
IPlayer FindIPlayer(string identifier) => players.FindPlayer(identifier);
bool IsCacheValid(string id) {
LoadPlayerData(id);
if (!_playerData.ContainsKey(id)) return false;
uint currentTimestamp = _time.GetUnixTimestamp();
double minutesOld = Math.Round((currentTimestamp - _playerData[id].cacheTimestamp) / 60.0);
return minutesOld < cacheLifetime;
}
void GetPlayerReport(IPlayer player) {
ISAPlayer isaPlayer = GetPlayerCache(player.Id);
if (isaPlayer != null) GetPlayerReport(isaPlayer, player.IsConnected);
}
void GetPlayerReport(IPlayer player, IPlayer cmdplayer) {
ISAPlayer isaPlayer = GetPlayerCache(player.Id);
if (isaPlayer != null) GetPlayerReport(isaPlayer, player.IsConnected, true, cmdplayer);
}
void GetPlayerReport(IPlayer player, bool isCommand = false) {
ISAPlayer isaPlayer = GetPlayerCache(player.Id);
if (isaPlayer != null) GetPlayerReport(isaPlayer, player.IsConnected, isCommand);
}
void GetPlayerReport(ISAPlayer isaPlayer, bool isConnected = true, bool isCommand = false, IPlayer cmdPlayer = null) {
Dictionary<string, string> data = new Dictionary<string, string> {
["status"]
= IsPlayerDirty(isaPlayer.steamid) ? "dirty" : "clean", ["steamid"] = isaPlayer.steamid, ["username"] = isaPlayer.username, ["serverBanCount"] = isaPlayer.serverBanCount.ToString(), ["NumberOfGameBans"] = isaPlayer.steamData.NumberOfGameBans.ToString(), ["NumberOfVACBans"] = isaPlayer.steamData.NumberOfVACBans.ToString() + (isaPlayer.steamData.NumberOfVACBans > 0 ? $" Last({isaPlayer.steamData.DaysSinceLastBan}) days ago" : ""), ["EconomyBan"] = (!isaPlayer.steamData.EconomyBan.Equals("none")).ToString(), ["FamShare"] = IsFamilyShare(isaPlayer.steamid).ToString() + (IsFamilyShare(isaPlayer.steamid) ? IsLenderDirty(isaPlayer.steamid) ? "DIRTY" : "CLEAN" : "")
}
;
if (IsPlayerDirty(isaPlayer.steamid) || isCommand) {
string report = GetMsg("User Dirty MSG", data);
if (config.BroadcastPlayerBanReport && isConnected && !isCommand && config.BroadcastPlayerBanReportVacDays > isaPlayer.steamData.DaysSinceLastBan) {
server.Broadcast(report.Replace(isaPlayer.steamid + ":", string.Empty).Replace(isaPlayer.steamid, string.Empty));
}
if (isCommand) {
cmdPlayer.Reply(report.Replace(isaPlayer.steamid + ":", string.Empty).Replace(isaPlayer.steamid, string.Empty));
}
}
if ((config.DiscordOnlySendDirtyReports && IsPlayerDirty(isaPlayer.steamid)) || !config.DiscordOnlySendDirtyReports) DiscordSend(isaPlayer, GetMsg("User Dirty DISCORD MSG", data));
}
private void LogDebug(string txt) {
if (config.Debug) Puts(txt);
}
void LoadData() {
IEnumerable<IPlayer> connectedPlayers = players.Connected;
int playerCount = connectedPlayers.Count();
int playerCounter = 0;
/**
* not as pretty as foreach, but faster
*/
while (playerCounter < playerCount) {
LoadPlayerData(connectedPlayers.ElementAt(playerCounter)?.Id);
playerCounter++;
}
}
void SaveData() {
int dataCount = _playerData.Count();
int dataCounter = 0;
/**
* not as pretty as foreach, but faster
*/
while (dataCounter < dataCount) {
SavePlayerData(_playerData.ElementAt(dataCounter).Value.steamid);
dataCounter++;
}
}
void LoadPlayerData(string id) {
if (!string.IsNullOrEmpty(id) && !_playerData.ContainsKey(id)) {
ISAPlayer playerData = Interface.Oxide.DataFileSystem.ReadObject<ISAPlayer>($"ServerArmour/{id}");
if (playerData != null) {
_playerData.Add(id, playerData);
}
}
}
void SaveThenPurge(string id) {
SavePlayerData(id);
_playerData.Remove(id);
}
void SavePlayerData(string id) {
if (!string.IsNullOrEmpty(id) && _playerData.ContainsKey(id)) {
Interface.Oxide.DataFileSystem.WriteObject($"ServerArmour/{id}", _playerData[id], true);
}
}
void CheckGroups() {
LogDebug("Registering groups");
string[] groups = permission.GetGroups();
LogDebug("Checking if config groups exists.");
if (!permission.GroupExists(config.AutoKickGroup)) {
permission.CreateGroup(config.AutoKickGroup, "Server Armour AutoKicks", 0);
}
}
string ServerGetString() {
return ServerGetString("&");
}
string ServerGetString(string start) {
return start + $"sip={server.Address}&sn={server.Name}&sp={server.Port}&an={config.ServerAdminName}&ae={config.ServerAdminEmail}&auid={config.OwnerSteamId}&gameId={covalence.ClientAppId}&gameName={covalence.Game}";
}
#endregion
#region Kicking
void KickPlayer(string steamid, string reason, string type) {
IPlayer player = players.FindPlayerById(steamid);
if (player == null) return;
if (config.DiscordKickReport && type == "C") {
DiscordSend(player, new EmbedFieldList() {
name = "Player Kicked", value = reason, inline = true
}
, 13459797);
}
if (player.IsConnected) player?.Kick(reason);
if (config.BroadcastNewBans && type == "C") {
server.Broadcast(GetMsg("Player Kicked", new Dictionary<string, string> {
["player"]
= player.Name, ["reason"] = reason
}
));
}
}
bool ContainsMyBan(string steamid) {
return IsBanned(steamid) != null;
}
bool IsVpn(ISAPlayer isaPlayer) {
return (config.AutoKick_BadIp && isaPlayer.ipRating >= config.AutoKick_BadIp_Sensitivity);
}
ISABan IsBanned(string steamid) {
if (steamid == null || steamid.Equals("0")) return null;
if (!IsPlayerCached(steamid)) return null;
try {
ISAPlayer isaPlayer = GetPlayerCache(steamid);
return isaPlayer?.serverBanData.Count() > 0 ? isaPlayer?.serverBanData?.First(x => x.serverIp.Equals(server.Address.ToString())) : null;
}
catch (InvalidOperationException ioe) {
return null;
}
}
bool IsLenderDirty(string steamid) {
string lendersteamid = GetFamilyShareLenderSteamId(steamid);
return lendersteamid != "0" ? IsPlayerDirty(lendersteamid) : false;
}
bool HasRecentVac(string playerid) {
if (!IsPlayerCached(playerid)) return false;
ISASteamData cache = GetPlayerCache(playerid).steamData;
return (cache.VACBanned > 0 || cache.NumberOfVACBans > 0) ? cache.DaysSinceLastBan < config.DissallowVacBanDays : false;
}
bool HasReachedVacCeiling(ISAPlayer isaPlayer) {
return config.AutoKickOn && config.AutoVacBanCeiling < isaPlayer.steamData.NumberOfVACBans;
}
bool HasReachedGameBanCeiling(ISAPlayer isaPlayer) {
return config.AutoKickOn && config.AutoGameBanCeiling < isaPlayer.steamData.NumberOfGameBans;
}
bool OwnsBloody(ISAPlayer isaPlayer) {
return config.AutoKickOn && config.AutoKick_Hardware_Bloody && isaPlayer.bloodyScriptsCount > 0;
}
bool HasReachedServerCeiling(ISAPlayer isaPlayer) {
return config.AutoKickOn && config.AutoKickCeiling < isaPlayer.serverBanCount;
}
bool IsFamilyShare(string steamid) {
ISAPlayer player = GetPlayerCache(steamid);
return !player.lendersteamid.Equals("0");
}
bool HasKeywordBan(ISAPlayer isaPlayer) {
bool keywordBan = false;
bool keywordBanCheck = config.AutoKickOn && (config.AutoKick_Reason_Keyword_Aimbot || config.AutoKick_Reason_Keyword_Cheat || config.AutoKick_Reason_Keyword_EspHack || config.AutoKick_Reason_Keyword_Hack || config.AutoKick_Reason_Keyword_Insult || config.AutoKick_Reason_Keyword_Ping || config.AutoKick_Reason_Keyword_Racism || config.AutoKick_Reason_Keyword_Script || config.AutoKick_Reason_Keyword_Toxic);
if (keywordBanCheck) {
foreach (ISABan ban in isaPlayer.serverBanData) {
if (config.AutoKick_Reason_Keyword_Aimbot && ban.isAimbot) keywordBan = true;
if (config.AutoKick_Reason_Keyword_Cheat && ban.isCheat) keywordBan = true;
if (config.AutoKick_Reason_Keyword_EspHack && ban.isEspHack) keywordBan = true;
if (config.AutoKick_Reason_Keyword_Hack && ban.isHack) keywordBan = true;
if (config.AutoKick_Reason_Keyword_Insult && ban.isInsult) keywordBan = true;
if (config.AutoKick_Reason_Keyword_Ping && ban.isPing) keywordBan = true;
if (config.AutoKick_Reason_Keyword_Racism && ban.isRacism) keywordBan = true;
if (config.AutoKick_Reason_Keyword_Script && ban.isScript) keywordBan = true;
if (config.AutoKick_Reason_Keyword_Toxic && ban.isToxic) keywordBan = true;
if (keywordBan) return true;
}
}
return false;
}
#endregion
#region API Hooks
private int API_GetServerBanCount(string steamid) => IsPlayerCached(steamid) ? GetPlayerBanDataCount(steamid) : 0;
private bool API_GetIsVacBanned(string steamid) => IsPlayerCached(steamid) ? GetPlayerCache(steamid).steamData.VACBanned == 1 : false;
private bool API_GetIsCommunityBanned(string steamid) => IsPlayerCached(steamid) ? GetPlayerCache(steamid).steamData.CommunityBanned == 1 : false;
private int API_GetVacBanCount(string steamid) => IsPlayerCached(steamid) ? GetPlayerCache(steamid).steamData.NumberOfVACBans : 0;
private int API_GetDaysSinceLastVacBan(string steamid) => IsPlayerCached(steamid) ? GetPlayerCache(steamid).steamData.DaysSinceLastBan : 0;
private int API_GetGameBanCount(string steamid) => IsPlayerCached(steamid) ? GetPlayerCache(steamid).steamData.NumberOfGameBans : 0;
private string API_GetEconomyBanStatus(string steamid) => IsPlayerCached(steamid) ? GetPlayerCache(steamid).steamData.EconomyBan : "none";
private bool API_GetIsPlayerDirty(string steamid) => IsPlayerDirty(steamid);
private bool API_GetIsFamilyShareLenderDirty(string steamid) => IsLenderDirty(steamid);
private bool API_GetIsFamilyShare(string steamid) => IsFamilyShare(steamid);
private string API_GetFamilyShareLenderSteamId(string steamid) => GetFamilyShareLenderSteamId(steamid);
#endregion
#region Localization
string GetMsg(string msg, Dictionary<string, string> rpls = null) {
string message = lang.GetMessage(msg, this);
if (rpls != null) foreach (var rpl in rpls) message = message.Replace($"{{{rpl.Key}}}", rpl.Value);
return message;
}
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
["Protected MSG"]
= "Server protected by [#008080ff]ServerArmour[/#]", ["User Dirty MSG"] = "[#008080ff]Server Armour Report:\n {steamid}:{username}[/#] is {status}.\n [#ff0000ff]Server Bans:[/#] {serverBanCount}\n [#ff0000ff]Game Bans:[/#] {NumberOfGameBans}\n [#ff0000ff]Vac Bans:[/#] {NumberOfVACBans}\n [#ff0000ff]Economy Banned:[/#] {EconomyBan}\n [#ff0000ff]Family Share:[/#] {FamShare}", ["User Dirty DISCORD MSG"] = "**Server Bans:** {serverBanCount}\n **Game Bans:** {NumberOfGameBans}\n **Vac Bans:** {NumberOfVACBans}\n **Economy Banned:** {EconomyBan}\n **Family Share:** {FamShare}", ["Command sa.cp Error"] = "Wrong format, example: /sa.cp usernameORsteamid trueORfalse", ["Arkan No Recoil Violation"] = "[#ff0000]{player}[/#] received an Arkan no recoil violation.\n[#ff0000]Violation[/#] #{violationNr}, [#ff0000]Weapon:[/#] {weapon}, [#ff0000]Ammo:[/#] {ammo}, [#ff0000]Shots count:[/#] {shots}\n Admins will investigate ASAP, please have handcams ready.\n This might be a false-positive, but all violations need to be investigated.", ["Arkan Aimbot Violation"] = "[#ff0000]{player}[/#] received an Arkan aimbot violation.\n[#ff0000]Violation[/#] #{violationNr}, [#ff0000]Weapon:[/#] {weapon}, [#ff0000]Ammo:[/#] {ammo}\n Admins will investigate ASAP, please have handcams ready.\n This might be a false-positive, but all violations need to be investigated.", ["Arkan In Rock Violation"] = "[#ff0000]{player}[/#] received an Arkan in rock violation.\n[#ff0000]Violation[/#] #{violationNr}, [#ff0000]Weapon:[/#] {weapon}, [#ff0000]Ammo:[/#] {ammo}\n Admins will investigate ASAP, please have handcams ready.\n This might be a false-positive, but all violations need to be investigated.", ["Player Now Banned"] = "[#ff0000]{player}[/#] has been banned\n[#ff0000]Reason: [/#] {reason}", ["Player Now Banned Perma"] = "[#ff0000]{player}[/#] has been banned\n[#ff0000]Reason:[/#] {reason}\n[#ff0000]Length:[/#] {length}", ["Reason: Bad IP"] = "Bad IP Detected, either due to a VPN/Proxy", ["Player Not Found"] = "Player wasn't found", ["Multiple Players Found"] = "Multiple players found with that name ({players}), please try something more unique like a steamid", ["Ban Syntax"] = "sa.ban <playerNameOrID> \"<reason>\" [duration days: default 3650]", ["UnBan Syntax"] = "sa.unban <playerNameOrID>", ["No Response From API"] = "Couldn't get an answer from ServerArmour.com! Error: {code} {response}", ["Player Not Banned"] = "Player not banned", ["Broadcast Player Banned"] = "{tag} {username} wasn't allowed to connect\nReason: {reason}", ["Reason: VAC Ban Too Fresh"] = "VAC ban received {daysago} days ago, wait another {daysto} days", ["Lender Banned"] = "The lender account contained a ban", ["Keyword Kick"] = "Due to your past behaviour on other servers, you aren't allowed in.", ["Too Many Previous Bans"] = "You have too many previous bans (other servers included).Appeal in discord", ["Too Many Previous Game Bans"] = "You have too many previous game bans.Appeal in discord", ["Kick Bloody"] = "You own a bloody/a4 tech device.Appeal in discord", ["VAC Ceiling Kick"] = "You have too many VAC bans.Appeal in discord", ["Player Kicked"] = "[#ff0000]{player} Kicked[/#] - Reason\n{reason}"
}
, this, "en");
}
bool HasPermission(string steamid, string permissionName) {
return permission.UserHasPermission(steamid, permissionName);
}
#endregion
#region Plugins methods
string GetChatTag() => "[<#008080ff][Server Armour]:[/#] ";
void RegisterTag() {
if (BetterChat != null && config.BetterChatDirtyPlayerTag != null && config.BetterChatDirtyPlayerTag.Length > 0) BetterChat?.Call("API_RegisterThirdPartyTitle", new object[] {
this, new Func<IPlayer, string>(GetTag)
}
);
}
string GetTag(IPlayer player) {
if (BetterChat != null && IsPlayerDirty(player.Id) && config.BetterChatDirtyPlayerTag.Length > 0) {
return $"[#FFA500][{config.BetterChatDirtyPlayerTag}][/#]";
}
else {
return string.Empty;
}
}
#endregion
#region Helpers
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static uint ConvertToTimestamp(string value) {
return ConvertToTimestamp(ConverToDateTime(value));
}
private static uint ConvertToTimestamp(DateTime value) {
TimeSpan elapsedTime = value - Epoch;
return (uint)elapsedTime.TotalSeconds;
}
private static DateTime ConverToDateTime(string stringDate) {
return DateTime.ParseExact(stringDate, DATE_FORMAT, null);
}
#endregion
#region Classes
public class EmbedFieldList {
public string name {
get;
set;
}
public string value {
get;
set;
}
public bool inline {
get;
set;
}
}
public class ISAPlayer {
public string steamid {
get;
set;
}
public int bloodyScriptsCount {
get;
set;
}
public string lendersteamid {
get;
set;
}
public ISASteamData lenderSteamData {
get;
set;
}
public string username {
get;
set;
}
public ISASteamData steamData {
get;
set;
}
public int serverBanCount {
get;
set;
}
public List<ISABan> serverBanData {
get;
set;
}
public uint cacheTimestamp {
get;
set;
}
public uint lastConnected {
get;
set;
}
public double ipRating {
get;
set;
}
public string address {
get;
set;
}
public ISAPlayer CreatePlayer(IPlayer bPlayer) {
steamid = bPlayer.Id;
bloodyScriptsCount = 0;
username = bPlayer.Name;
address = bPlayer.Address;
cacheTimestamp = new Time().GetUnixTimestamp();
lendersteamid = "0";
lastConnected = new Time().GetUnixTimestamp();
lenderSteamData = new ISASteamData();
steamData = new ISASteamData();
serverBanData = new List<ISABan>();
return this;
}
}
public class ISABan {
public string banId;
public string serverName;
public string serverIp;
public string reason;
public string date;
public string banUntil;
public bool isAimbot;
public bool isHack;
public bool isEspHack;
public bool isScript;
public bool isCheat;
public bool isToxic;
public bool isInsult;
public bool isPing;
public bool isRacism;
}
public class ISASteamData {
public int CommunityBanned {
get;
set;
}
public int VACBanned {
get;
set;
}
public int NumberOfVACBans {
get;
set;
}
public int DaysSinceLastBan {
get;
set;
}
public int NumberOfGameBans {
get;
set;
}
public string EconomyBan {
get;
set;
}
public string BansLastCheckUTC {
get;
set;
}
}
#endregion
#region Plugin Classes & Hooks Rust
#region Arkan
#if RUST
private void API_ArkanOnNoRecoilViolation(BasePlayer player, int NRViolationsNum, string jString) {
if (jString != null) {
JObject aObject = JObject.Parse(jString);
string shotsCnt = aObject.GetValue("ShotsCnt").ToString();
string violationProbability = aObject.GetValue("violationProbability").ToString();
string ammoShortName = aObject.GetValue("ammoShortName").ToString();
st
// ... [Code preview truncated due to length. Full source in attached file] ...