Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
57 kB
14
Indexable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Network;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Libraries;
using UnityEngine;

// ReSharper disable UnusedMember.Local
// ReSharper disable UnusedMember.Global

namespace Oxide.Plugins
{
    [Info("Ban System", "Bombardir, Moscow.OVH", "0.4.29")]
    [Description("Ban System")]
    public class BanSystem : RustPlugin
    {
        private static readonly string SyncPermission = "bansystem.sync";
        private static readonly string KickPermission = "bansystem.kick";
        private static readonly string BanPermission = "bansystem.ban";
        private static readonly string StrikePermission = "bansystem.strike";
        private static readonly string UnbanPermission = "bansystem.unban";
        private readonly Core.Libraries.Plugins _plugins = GetLibrary<Core.Libraries.Plugins>();
        private readonly Permission _permission = GetLibrary<Permission>();
        private readonly Lang _lang = GetLibrary<Lang>();
        private readonly Covalence _covalence = GetLibrary<Covalence>();
        private ExConfig _exConfig;
        private WebApi _webApi;
        private GameObject _mainObject;
        private BansData _bansData;
        private static BanSystem _plugin;
        private bool _initialized = false;

        protected override void LoadDefaultConfig() { } // Совместимость с .cs плагинами, создание пустого файла конфигурации.

        public override void HandleRemovedFromManager(PluginManager manager)
        {
            if (_mainObject != null)
            {
                UnityEngine.Object.Destroy(_mainObject);
            }

            base.HandleRemovedFromManager(manager);
        }

        private static readonly Regex RegexStringTime = new Regex(@"(\d+)([dhms])", RegexOptions.Compiled);
        private static bool ConvertToSeconds(string time, out uint seconds)
        {
            seconds = 0;
            if (time == "0" || string.IsNullOrEmpty(time))
            {
                return true;
            }

            MatchCollection matches = RegexStringTime.Matches(time);
            if (matches.Count == 0)
            {
                return false;
            }

            for (var i = 0; i < matches.Count; i++)
            {
                Match match = matches[i];
                switch (match.Groups[2].Value)
                {
                    case "d":
                        {
                            seconds += uint.Parse(match.Groups[1].Value) * 24 * 60 * 60;
                            break;
                        }
                    case "h":
                        {
                            seconds += uint.Parse(match.Groups[1].Value) * 60 * 60;
                            break;
                        }
                    case "m":
                        {
                            seconds += uint.Parse(match.Groups[1].Value) * 60;
                            break;
                        }
                    case "s":
                        {
                            seconds += uint.Parse(match.Groups[1].Value);
                            break;
                        }
                }
            }
            return true;
        }

        private static string GetNiceTime(ulong seconds, string day, string hour, string min, string sec)
        {
            TimeSpan span = TimeSpan.FromSeconds(seconds).Duration();

            string formatted = string.Empty;
            if (span.Days > 0)
            {
                formatted += span.Days + day;
            }

            if (span.Hours > 0)
            {
                formatted += span.Hours + hour;
            }

            if (span.Minutes > 0)
            {
                formatted += span.Minutes + min;
            }

            if (span.Seconds > 0)
            {
                formatted += span.Seconds + sec;
            }

            if (string.IsNullOrEmpty(formatted))
            {
                formatted = "0 сек";
            }

            return formatted;
        }

        private void LogAdmin(string action, ulong steamId, string playerName, string playerSteam)
        {
            string initiatorStr;
            if (steamId == 0)
            {
                initiatorStr = "через консоль";
            }
            else
            {
                string steamStr = steamId.ToString();
                string adminName = _covalence.Players.FindPlayerById(steamId.ToString())?.Name;
                initiatorStr = "администратором " + (string.IsNullOrEmpty(adminName) ? steamStr : $"{adminName} ({steamStr})");
            }

            string log = $"[{DateTime.Now.ToString(CultureInfo.InvariantCulture)}] Игрок {playerName} ({playerSteam}) {action} {initiatorStr}\n";

            Log.Debug(log);
            Log.File(log, "actions");
        }

        private void SyncStandartBans(string fileName, bool doMove)
        {
            string filePath = ConVar.Server.GetServerFolder("cfg") + "/" + fileName;

            if (!File.Exists(filePath))
            {
                if (!doMove)
                {
                    Log.Debug("Нет банов для синхронизации");
                }

                return;
            }

            string fileText = File.ReadAllText(filePath);
            if (string.IsNullOrEmpty(fileText))
            {
                if (!doMove)
                {
                    Log.Debug("Нет банов для синхронизации");
                }

                return;
            }

            var banRegex = new Regex("banid\\s+(\\d+)\\s+\"[^\"]*\"\\s+\"([^\"]*)\"", RegexOptions.Compiled);

            List<string> bans = fileText.Split('\n').ToList();
            for (int i = bans.Count - 1; i >= 0; i--)
            {
                Match match = banRegex.Match(bans[i]);
                if (!match.Success)
                {
                    continue;
                }

                string reason = match.Groups[2].Value;
                if (reason == "VAC" || reason == "EAC" || reason == "GAMEBAN")
                {
                    if (doMove)
                    {
                        bans.RemoveAt(i);
                    }

                    continue;
                }
                ulong steam = ulong.Parse(match.Groups[1].Value);
                _bansData.AddBan(steam, reason, 0, 0, "0.0.0.0", 0, 0);
            }

            if (doMove)
            {
                try
                {
                    string backupFilePath = filePath + ".backup";
                    if (!File.Exists(backupFilePath))
                    {
                        File.WriteAllLines(backupFilePath, bans.ToArray());
                    }
                    else
                    {
                        List<string> backupLines = File.ReadAllLines(backupFilePath).ToList();
                        foreach (string newLine in bans)
                        {
                            if (backupLines.Contains(newLine))
                            {
                                continue;
                            }

                            backupLines.Add(newLine);
                        }
                        File.WriteAllLines(backupFilePath, backupLines.ToArray());
                    }

                    File.Delete(filePath);
                }
                catch
                {
                    // ignored
                }
            }

            Log.Debug($"Баны из файла {fileName} синхронизированы локально, идет синхронизация с сайтом");
            SyncBanList(_bansData.noSyncBans, (success) =>
            {
                Log.Debug(success ? "Баны успешно синхронизированы" :
                    "Во время синхронизации банов произошла ошибка, синхронизация будет повторена позже");
            });
        }

        public static string FormatString(int str, string first, string second, string third)
        {
            string formatted = str + " ";
            if (str > 100)
            {
                str = str % 100;
            }

            if (str > 9 && str < 21)
            {
                formatted += third;
            }
            else
            {
                switch (str % 10)
                {
                    case 1:
                        formatted += first;
                        break;
                    case 2:
                    case 3:
                    case 4:
                        formatted += second;
                        break;
                    default:
                        formatted += third;
                        break;
                }
            }

            return formatted;
        }

        [HookMethod("Init")]
        private void Init()
        {
            _plugin = this;
            try { _bansData = Interface.Oxide.DataFileSystem.ReadObject<BansData>("BanSystem"); }
            catch
            {
                // ignored
            }

            if (_bansData == null)
            {
                _bansData = new BansData();
            }

            _exConfig = ExConfig.Parse(this);
            _permission.RegisterPermission(BanPermission, this);
            _permission.RegisterPermission(KickPermission, this);
            _permission.RegisterPermission(SyncPermission, this);
            _permission.RegisterPermission(UnbanPermission, this);
            _permission.RegisterPermission(StrikePermission, this);

            foreach (string hookName in Hooks.Keys)
            {
                if (hookName == nameof(OnServerInitialized) || hookName == nameof(OnPluginLoaded))
                {
                    continue;
                }

                Unsubscribe(hookName);
            }
        }

        private string GetLangMessage(string key, string userId) => _lang.GetMessage(key, this, userId);

        private string GetLangMessage(string key, BasePlayer player) => GetLangMessage(key, player.UserIDString);

        [HookMethod("OnPluginLoaded")]
        private void OnPluginLoaded(Plugin plugin)
        {
            if (plugin.Name == "RustStore" && _initialized)
            {
                PostInitialize();
            }
        }

        private void PostInitialize()
        {
            if (_mainObject != null)
                return;

            _initialized = true;

            Plugin store = _plugins.Find("RustStore");
            if (store == null || !store.IsLoaded)
            {
                Log.Warning("Не найден обязательный плагин RustStore, ожидается его загрузка.");
                return;
            }

            Log.Debug("Синхронизация с магазином...");

            string storeId;
            string serverId;
            string serverKey;

            try
            {
                storeId = store.Config.Get<string>("номер магазина");
                serverId = store.Config.Get<string>("номер сервера");
                serverKey = store.Config.Get<string>("ключ сервера");
            }
            catch
            {
                Log.Error("Ошибка в данных для авторизации (конфиг RustStore).");
                Interface.Oxide.UnloadPlugin("BanSystem");
                return;
            }

            _mainObject = new GameObject("BanSystem" + Version);
            _webApi = new WebApi(_mainObject.AddComponent<WWWRequests>(), storeId, serverId, serverKey);
            _webApi.PostRequest(_webApi.InitData, (r, c) =>
            {
                ConsoleSystem.Run(ConsoleSystem.Option.Unrestricted, "server.writecfg");
                ///SyncStandartBans("bans.cfg", true);
            });

            var comLib = GetLibrary<Command>();
            comLib.AddConsoleCommand("bs.ban", this, BanConsoleCmd);
            comLib.AddConsoleCommand("bs.unban", this, UnbanConsoleCmd);
            comLib.AddConsoleCommand("bs.sync", this, SyncConsoleCmd);
            comLib.AddConsoleCommand("bs.kick", this, KickConsoleCmd);
            comLib.AddConsoleCommand("bs.strike", this, StrikeConsoleCmd);
            comLib.AddChatCommand("kick", this, KickChatCmd);
            comLib.AddChatCommand("ban", this, BanChatCmd);
            comLib.AddChatCommand("unban", this, UnbanChatCmd);
            comLib.AddChatCommand("strike", this, StrikeChatCmd);

            foreach (string hookName in Hooks.Keys)
            {
                Subscribe(hookName);
            }
        }

        [HookMethod("OnServerInitialized")]
        private void OnServerInitialized()
        {
            Interface.Oxide.NextTick(PostInitialize);
        }

        private void SyncBanList(IEnumerable<BansData.SyncBanData> syncList, Action<bool> onSync = null)
        {
            BansData.SyncBanData[] syncCopy = syncList.ToArray();
            if (syncCopy.Length == 0)
            {
                return;
            }

            _webApi.AddBanListData["data"] = JsonConvert.SerializeObject(syncCopy);
            _webApi.PostRequest(_webApi.AddBanListData, (success, res) =>
            {
                if (!success)
                {
                    onSync?.Invoke(false);
                    return;
                }
                foreach (BansData.SyncBanData sync in syncCopy)
                {
                    _bansData.SyncBan(sync.steamID, 0, false);
                    ServerUsers.Remove(sync.steamID);
                }
                _bansData.Save();
                onSync?.Invoke(true);
            });
        }

        [HookMethod("OnServerSave")]
        private void OnServerSave()
        {
            SyncBanList(_bansData.noSyncBans);
        }

        private void KickConnectionBan(Connection connection, string reason)
        {
            string steam = connection.userid.ToString();
            string rejectMessage = string.IsNullOrEmpty(reason)
                ? GetLangMessage("BAN.MESSAGE", steam) :
                  GetLangMessage("BAN.MESSAGE.REASON", steam).Replace("{reason}", reason);
            ConnectionAuth.Reject(connection, rejectMessage);
        }

        [HookMethod("OnPlayerConnected")]
        private void OnPlayerConnected(BasePlayer player)
        {
            Connection connection = player.Connection;
            if (connection == null)
            {
                return;
            }

            ulong steam = connection.userid;
            ulong owner = connection.ownerid;

            if (owner == steam)
            {
                owner = 0;
            }

            BansData.SyncBanData noSynced = _bansData.noSyncBans.FirstOrDefault(p => p.steamID == steam || p.familyShare == steam ||
                                owner > 0 && (p.familyShare == owner || p.steamID == owner));
            if (noSynced != default(BansData.SyncBanData))
            {
                KickConnectionBan(connection, noSynced.reason);
                return;
            }

            _webApi.CheckBanData["steamID"] = steam.ToString();
            _webApi.CheckBanData["familyShare"] = owner.ToString();
            _webApi.CheckBanData["ip"] = connection.ipaddress?.Remove(connection.ipaddress.Length - 6, 6) ?? string.Empty;
            _webApi.PostRequest(_webApi.CheckBanData, (success, resp) =>
            {
                bool isLocalBanned = _bansData.IsPlayerBanned(steam);

                if (!success)
                {
                    if (isLocalBanned)
                    {
                        KickConnectionBan(connection, string.Empty);
                    }

                    return;
                }

                if (resp.message != "banned")
                {
                    if (isLocalBanned)
                    {
                        _bansData.RemoveBan(steam);
                    }

                    return;
                }

                if (!isLocalBanned)
                {
                    _bansData.SyncBan(steam, 0);
                }

                KickConnectionBan(connection, resp.data.ToString());
            });
        }

        private bool FindPlayer(string pattern, out ulong steamId, out string name)
        {
            if (ulong.TryParse(pattern, out steamId) && steamId > 76561190000000000 && steamId < 76561199999999999)
            {
                name = _covalence.Players.FindPlayerById(steamId.ToString())?.Name ?? string.Empty;
                return true;
            }

            List<BasePlayer> players = BasePlayer.activePlayerList.Where(pl => !pl.IsSleeping() && pl.displayName.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) != -1).ToList();
            players.AddRange(BasePlayer.sleepingPlayerList.Where(pl => pl.displayName.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) != -1));

            if (players.Count == 0)
            {
                name = "Игрок с таким именем не найден";
                return false;
            }

            if (players.Count > 1)
            {
                name = $"Найдены совпадения по имени: {string.Join(" ", players.Select(p => p.displayName).ToArray())}";
                return false;
            }

            steamId = players[0].userID;
            name = players[0].displayName;

            return true;
        }

        private string KickPlayer(ulong initiator, string pattern, string reason)
        {
            if (reason == "-")
            {
                reason = string.Empty;
            }

            List<BasePlayer> players = BasePlayer.activePlayerList.Where(pl => pl.displayName.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) != -1).ToList();
            if (players.Count == 0)
            {
               return "Игрок с таким именем не найден";
            }

            if (players.Count > 1)
            {
                return $"Найдены совпадения по имени: {string.Join(" ", players.Select(p => p.displayName).ToArray())}";
            }

            BasePlayer ply = players[0];
            string name = ply.displayName;
            string steamStr = ply.UserIDString;

            Interface.Oxide.CallHook("OnBanSystemKick", ply, initiator);
            LogAdmin("был кикнут", initiator, name, steamStr);

            if (_exConfig.showGlobalKickMessage)
            {
                foreach (BasePlayer brodPlayer in BasePlayer.activePlayerList)
                {
                    brodPlayer.ChatMessage(
                        GetLangMessage(string.IsNullOrEmpty(reason) ? "KICK.BROADCAST" : "KICK.BROADCAST.REASON", brodPlayer)
                        .Replace("{reason}", reason)
                        .Replace("{name}", name)
                        .Replace("{steamID}", steamStr));
                }
            }

            ply.Kick(reason);
            return $"Игрок {name} {steamStr} кикнут с сервера.";
        }

        private void BanPlayer(string player, string reason, string banTimeStr, ulong bannedBy, int single, Action<string> onMessage)
        {
            uint banTime;
            if (!ConvertToSeconds(banTimeStr, out banTime))
            {
                onMessage("Ошибка в формате времени");
                return;
            }

            ulong steam;
            string name;
            if (!FindPlayer(player, out steam, out name))
            {
                onMessage(name);
                return;
            }
            if (!string.IsNullOrEmpty(name))
            {
                name += " ";
            }

            BanPlayer(name, steam, reason, banTime, bannedBy, single, onMessage);
        }

        private void BanPlayer(string name, ulong steam, string reason, uint banTime, ulong bannedBy, int single, Action<string> onMessage)
        {
            if (reason == "-")
            {
                reason = string.Empty;
            }

            string steamStr = steam.ToString();
            ulong owner = 0;
            string banTimeString = banTime == 0 ? "навсегда" : $"на {GetNiceTime(banTime, "д.", "ч.", "мин.", "сек.")}";
            var ip = "0.0.0.0";
            Connection con = BasePlayer.FindByID(steam)?.net?.connection;
            if (con != null)
            {
                ip = con.ipaddress;
                ip = ip.Remove(ip.IndexOf(':'));
                owner = con.ownerid;
                if (owner == steam)
                {
                    owner = 0;
                }

                KickConnectionBan(con, reason);
                if (owner > 0)
                {
                    Connection ownerCon = BasePlayer.FindByID(owner)?.net?.connection;
                    if (ownerCon != null)
                    {
                        KickConnectionBan(ownerCon, "Family Share Ban");
                    }
                }
            }

            bool isBannedAlready = _bansData.IsPlayerBanned(steam);
            if (!isBannedAlready)
            {
                _bansData.AddBan(steam, reason, banTime, bannedBy, ip, owner, single);
            }

            if (_exConfig.showGlobalBanMessage && (con != null || _exConfig.showGlobalBanMessageOffline && !isBannedAlready))
            {
                foreach (BasePlayer brodPlayer in BasePlayer.activePlayerList)
                {
                    brodPlayer.ChatMessage(
                        banTime == 0
                        ? GetLangMessage(string.IsNullOrEmpty(reason) ? "BAN.BROADCAST.FOREVER" : "BAN.BROADCAST.FOREVER.REASON", brodPlayer)
                        .Replace("{reason}", reason)
                        .Replace("{name}", name)
                        .Replace("{steamID}", steamStr)
                        : GetLangMessage(string.IsNullOrEmpty(reason) ? "BAN.BROADCAST" : "BAN.BROADCAST.REASON", brodPlayer)
                        .Replace("{reason}", reason)
                        .Replace("{name}", name)
                        .Replace("{steamID}", steamStr)
                        .Replace("{time}", GetNiceTime(banTime, GetLangMessage("DAY_SHORT", brodPlayer), GetLangMessage("HOUR_SHORT", brodPlayer),
                                    GetLangMessage("MIN_SHORT", brodPlayer), GetLangMessage("SEC_SHORT", brodPlayer))));
                }
            }

            _webApi.AddBanData["steamID"] = steam.ToString();
            _webApi.AddBanData["reason"] = reason;
            _webApi.AddBanData["banTime"] = banTime.ToString();
            _webApi.AddBanData["bannedBy"] = bannedBy.ToString();
            _webApi.AddBanData["ip"] = ip;
            _webApi.AddBanData["familyShare"] = owner.ToString();
            _webApi.AddBanData["singleBan"] = single.ToString();
            _webApi.PostRequest(_webApi.AddBanData, (success, res) =>
            {
                if (!success)
                {
                    if (isBannedAlready)
                    {
                        onMessage($"Игрок {name}{steam} уже забанен");
                    }
                    else
                    {
                        Interface.Oxide.CallHook("OnBanSystemBan", steam, owner, reason, banTime, bannedBy, single);
                        LogAdmin($"был забанен {banTimeString}", bannedBy, name, steamStr);
                        onMessage($"Игрок {name}{steam} успешно забанен {banTimeString} локально, но при синхронизации произошла ошибка, бан будет синхронизирован с сайтом позже");
                    }
                    return;
                }

                _bansData.SyncBan(steam, owner);

                if (res.message == "userAlreadyBanned")
                {
                    onMessage($"Игрок {name}{steam} уже забанен");
                    return;
                }

                Interface.Oxide.CallHook("OnBanSystemBan", steam, owner, reason, banTime, bannedBy, single);
                LogAdmin($"был забанен {banTimeString}" , bannedBy, name, steamStr);
                onMessage($"Игрок {name}{steam} успешно забанен {banTimeString}");
            });
        }

        private void StrikePlayer(string player, string reason, ulong initiator, Action<string> onMessage)
        {
            if (reason == "-")
            {
                reason = string.Empty;
            }

            ulong steamId;
            string name;
            if (!FindPlayer(player, out steamId, out name))
            {
                onMessage(name);
                return;
            }
            if (!string.IsNullOrEmpty(name))
            {
                name += " ";
            }

            List<string> strikes = _bansData.GiveStrike(steamId, reason).strikes;

            BasePlayer ply = BasePlayer.FindByID(steamId);
            if (ply != null && ply.IsConnected)
            {
                ply.ChatMessage(
                    GetLangMessage(string.IsNullOrEmpty(reason) ? "STRIKE.MESSAGE" : "STRIKE.MESSAGE.REASON", ply)
                    .Replace("{reason}", reason)
                    .Replace("{strikeCount}", strikes.Count.ToString())
                    .Replace("{banStrikeCount}", _exConfig.strikeBanCount.ToString()));
            }

            string steamStr = steamId.ToString();

            Interface.Oxide.CallHook("OnBanSystemStrike", steamId, reason, initiator);
            LogAdmin("был страйкнут", initiator, name, steamStr);

            if (_exConfig.showGlobalStrikeMessage)
            {
                string cntStr = strikes.Count.ToString();
                string maxCntStr = _exConfig.strikeBanCount.ToString();
                foreach (BasePlayer brodPlayer in BasePlayer.activePlayerList)
                {
                    brodPlayer.ChatMessage(
                        GetLangMessage(string.IsNullOrEmpty(reason) ? "STRIKE.BROADCAST" : "STRIKE.BROADCAST.REASON", brodPlayer)
                        .Replace("{reason}", reason)
                        .Replace("{name}", name)
                        .Replace("{steamID}", steamStr)
                        .Replace("{strikeCount}", cntStr)
                        .Replace("{banStrikeCount}", maxCntStr));
                }
            }

            if (strikes.Count < _exConfig.strikeBanCount)
            {
                onMessage($"Игрок {name}{steamId} получил страйк.");
                return;
            }
            uint banTime;
            if (!ConvertToSeconds(_exConfig.strikeBanTime, out banTime))
            {
                onMessage("Ошибка в формате времени");
                return;
            }

            string[] nonEmptyStrikes = strikes.Where(s => !string.IsNullOrEmpty(s)).ToArray();

            BanPlayer(name, steamId, GetLangMessage(nonEmptyStrikes.Length == 0 ? "BAN.STRIKE.FORMAT" : "BAN.STRIKE.FORMAT.REASON", steamId.ToString())
                .Replace("{STRIKES}", string.Join(", ", nonEmptyStrikes)), banTime, initiator, _exConfig.syncStrikeBans ? 0 : 1, onMessage);
        }

        private void UnbanPlayer(string player, ulong initiator, Action<string> onMessage)
        {
            ulong steamId;
            string name;
            if (!FindPlayer(player, out steamId, out name))
            {
                onMessage(name);
                return;
            }
            if (!string.IsNullOrEmpty(name))
            {
                name += " ";
            }

            _webApi.RemoveBanData["steamID"] = steamId.ToString();
            _webApi.RemoveBanData["bannedBy"] = initiator.ToString();
            _webApi.PostRequest(_webApi.RemoveBanData, (success, res) =>
            {
                if (!success)
                {
                    onMessage("Что-то пошло не так в процессе разбана, попробуйте позже");
                    return;
                }

                _bansData.RemoveBan(steamId);

                ServerUsers.User serverUser = ServerUsers.Get(steamId);
                if (serverUser != null && serverUser.group == ServerUsers.UserGroup.Banned)
                {
                    ServerUsers.Remove(steamId);
                    ConsoleSystem.Run(ConsoleSystem.Option.Unrestricted, "server.writecfg");
                } else if (res.message == "userNotBanned")
                {
                    onMessage($"Игрок {name}{steamId} не был забанен");
                    return;
                }

                string steamStr = steamId.ToString();

                LogAdmin("был разбанен", initiator, name, steamStr);
                Interface.Oxide.CallHook("OnBanSystemUnban", steamId, initiator);

                onMessage($"Игрок {name}{steamId} успешно разбанен");

                if (!_exConfig.showGlobalUnbanMessage)
                {
                    return;
                }

                foreach (BasePlayer brodPlayer in BasePlayer.activePlayerList)
                {
                    brodPlayer.ChatMessage(
                                           GetLangMessage("UNBAN.BROADCAST", brodPlayer)
                                              .Replace("{name}", name)
                                              .Replace("{steamID}", steamStr));
                }
            });
        }

        private void KickChatCmd(BasePlayer player, string command, string[] args)
        {
            if (player.net.connection.authLevel < 2 &&
                !_permission.UserHasPermission(player.UserIDString, KickPermission))
            {
                player.ChatMessage("Недостаточно прав");
                return;
            }

            if (args.Length == 0)
            {
                player.ChatMessage("Синтаксис: /kick {имя/стим} {причина}");
                return;
            }

            player.ChatMessage(KickPlayer(player.userID, args[0], args.Length > 1 ? args[1] : _exConfig.defaultKickReason));
        }

        private void StrikeChatCmd(BasePlayer player, string command, string[] args)
        {
            if (args.Length > 0 && args[0] == "info")
            {
                List<string> strikes = _bansData.GetStrikeData(player.userID)?.strikes;
                if (strikes == null)
                {
                    player.ChatMessage(GetLangMessage("STRIKE.INFO.EMPTY", player));
                    return;
                }

                string[] nonEmptyStrikes = strikes.Where(s => !string.IsNullOrEmpty(s)).ToArray();

                player.ChatMessage(
                    GetLangMessage(nonEmptyStrikes.Length == 0 ? "STRIKE.INFO" : "STRIKE.INFO.REASON", player)
                    .Replace("{strikeCount}", strikes.Count.ToString())
                    .Replace("{banStrikeCount}", _exConfig.strikeBanCount.ToString())
                    .Replace("{STRIKES}", string.Join(", ", nonEmptyStrikes)));
                return;
            }

            if (player.net.connection.authLevel < 2 &&
                !_permission.UserHasPermission(player.UserIDString, StrikePermission))
            {
                player.ChatMessage("Недостаточно прав");
                return;
            }

            if (args.Length == 0)
            {
                player.ChatMessage("Синтаксис: /strike {имя/стим} {причина}");
                return;
            }

            StrikePlayer(args[0], args.Length > 1 ? args[1] : _exConfig.defaultStrikeReason, player.userID, player.ChatMessage);
        }

        private void BanChatCmd(BasePlayer player, string command, string[] args)
        {
            if (player.net.connection.authLevel < 2 &&
                !_permission.UserHasPermission(player.UserIDString, BanPermission))
            {
                player.ChatMessage("Недостаточно прав");
                return;
            }

            if (args.Length == 0)
            {
                player.ChatMessage("Синтаксис: /ban {имя/стим} {причина} {время}, /ban single {имя/стим} {причина} {время}");
                return;
            }

            int single = args[0] == "single" ? 1 : 0;
            if (single == 1 && args.Length == 1)
            {
                player.ChatMessage("Синтаксис: /ban {имя/стим} {причина} {время}, /ban single {имя/стим} {причина} {время}");
                return;
            }

            string banTime = _exConfig.defaultTime;
            string reason = _exConfig.defaultBanReason;

            if (args.Length >= 2 + single)
            {
                reason = args[1 + single];
                if (args.Length >= 3 + single)
                {
                    banTime = args[2 + single];
                }
            }

            BanPlayer(args[0 + single], reason, banTime, player.userID, single, player.ChatMessage);
        }

        private void UnbanChatCmd(BasePlayer player, string command, string[] args)
        {
            if (player.net.connection.authLevel < 2 &&
                !_permission.UserHasPermission(player.UserIDString, UnbanPermission))
            {
                player.ChatMessage("Недостаточно прав");
                return;
            }

            if (args.Length == 0)
            {
                player.ChatMessage("Синтаксис: /unban {имя/стим}");
                return;
            }

            UnbanPlayer(args[0], player.userID, player.ChatMessage);
        }

        private bool BanConsoleCmd(ConsoleSystem.Arg arg)
        {
            var bannedBy = 0UL;
            if (arg.Connection != null)
            {
                if (arg.Connection.authLevel < 2 &&
                !_permission.UserHasPermission(arg.Connection.userid.ToString(), BanPermission))
                {
                    return false;
                }

                bannedBy = arg.Connection.userid;
            }

            string name = arg.GetString(0);
            if (string.IsNullOrEmpty(name))
            {
                arg.ReplyWith("Синтаксис: bs.ban {имя/стим} {причина} {время}, bs.ban single {имя/стим} {причина} {время}");
                return false;
            }

            int single = name == "single" ? 1 : 0;

            if (string.IsNullOrEmpty(arg.GetString(0 + single)))
            {
                arg.ReplyWith("Синтаксис: bs.ban {имя/стим} {причина} {время}, bs.ban single {имя/стим} {причина} {время}");
                return false;
            }

            BanPlayer(arg.GetString(0 + single), arg.GetString(1 + single, _exConfig.defaultBanReason), arg.GetString(2 + single, _exConfig.defaultTime), bannedBy, single, Log.Debug);
            return true;
        }

        private bool KickConsoleCmd(ConsoleSystem.Arg arg)
        {
            if (arg.Connection != null && arg.Connection.authLevel < 2 && !_permission.UserHasPermission(arg.Connection.userid.ToString(), KickPermission))
            {
                return false;
            }

            string name = arg.GetString(0);
            if (string.IsNullOrEmpty(name))
            {
                arg.ReplyWith("Синтаксис: bs.kick {имя/стим} {причина}");
                return false;
            }

            ulong initiator = 0;
            if (arg.Connection != null)
            {
                initiator = arg.Connection.userid;
            }

            arg.ReplyWith(KickPlayer(initiator, name, arg.GetString(1, _exConfig.defaultKickReason)));
            return true;
        }

        private bool StrikeConsoleCmd(ConsoleSystem.Arg arg)
        {
            var strikedBy = 0UL;
            if (arg.Connection != null)
            {
                if (arg.Connection.authLevel < 2 &&
                !_permission.UserHasPermission(arg.Connection.userid.ToString(), StrikePermission))
                {
                    return false;
                }

                strikedBy = arg.Connection.userid;
            }

            string name = arg.GetString(0);
            if (string.IsNullOrEmpty(name))
            {
                arg.ReplyWith("Синтаксис: bs.strike {имя/стим} {причина}");
                return false;
            }

            StrikePlayer(name, arg.GetString(1, _exConfig.defaultKickReason), strikedBy, Log.Debug);
            return true;
        }

        private bool SyncConsoleCmd(ConsoleSystem.Arg arg)
        {
            if (arg.Connection != null && arg.Connection.authLevel < 2 && !_permission.UserHasPermission(arg.Connection.userid.ToString(), SyncPermission))
            {
                return false;
            }

            ///SyncStandartBans("bans.cfg.backup", false);
            return true;
        }

        private bool UnbanConsoleCmd(ConsoleSystem.Arg arg)
        {
            var unbannedBy = 0UL;
            if (arg.Connection != null)
            {
                if (arg.Connection.authLevel < 2 &&
                !_permission.UserHasPermission(arg.Connection.userid.ToString(), UnbanPermission))
                {
                    return false;
                }

                unbannedBy = arg.Connection.userid;
            }

            string name = arg.GetString(0);
            if (string.IsNullOrEmpty(name))
            {
                arg.ReplyWith("Синтаксис: bs.unban {имя/стим}");
                return false;
            }

            UnbanPlayer(name, unbannedBy, Log.Debug);
            return true;
        }

        [HookMethod("OnServerCommand")]
        private object OnServerCommand(ConsoleSystem.Arg arg)
        {
            string command = arg.cmd?.Name;
            if (command == null)
            {
                return null;
            }

            switch (command)
            {
                case "ban":
                    BanConsoleCmd(arg);
                    return true;
                case "banid":
                    if (arg.Args.Length == 2)
                    {
                        arg.Args = new[] { arg.Args[0] };
                    }
                    else if (arg.Args.Length >= 3)
                    {
                        arg.Args = new[] { arg.Args[0], arg.Args[2] };
                    }
                    BanConsoleCmd(arg);
                    return true;
                case "unban":
                    UnbanConsoleCmd(arg);
                    return true;
                default:
                    return null;
            }
        }

        public class WWWRequests : MonoBehaviour
        {
            private readonly HashSet<WWW> activeRequests = new HashSet<WWW>();

            public void Request(string url, Dictionary<string, string> data = null, Action<string, string> onRequestComplete = null)
            {
                StartCoroutine(WaitForRequest(url, data, onRequestComplete));
            }

            private IEnumerator WaitForRequest(string url, Dictionary<string, string> data = null, Action<string, string> onRequestComplete = null)
            {
                WWW www;

                if (data != null)
                {
                    var form = new WWWForm();
                    foreach (KeyValuePair<string, string> pair in data)
                    {
                        form.AddField(pair.Key, pair.Value);
                    }

                    www = new WWW(url, form.data);
                }
                else
                {
                    www = new WWW(url);
                }

                www.threadPriority = ThreadPriority.High;

                activeRequests.Add(www);

                yield return www;

                onRequestComplete?.Invoke(www.text, www.error);

                activeRequests.Remove(www);
            }

            private void OnDestroy()
            {
                foreach (WWW www in activeRequests)
                {
                    try { www.Dispose(); } catch { }
                }
            }
        }

        public class WebApi
        {
            public class JsonResponse
            {
                public string message = "empty";
                public string status = "empty";
                public JToken data;

                public JsonResponse() { }
                public bool IsSuccess() => status == "success";
            }

            private readonly WWWRequests _requests;

            private const string BaseUrl = "https://store-api.moscow.ovh/index.php";
            public readonly Dictionary<string, string> InitData = new Dictionary<string, string>()
            {
                ["modules"] = "servers",
                ["action"] = "checkAuth"
            };

            public readonly Dictionary<string, string> CheckBanData = new Dictionary<string, string>()
            {
                ["modules"] = "banlist",
                ["action"] = "checkUserBan",
                ["steamID"] = "0",
                ["familyShare"] = "0",
                ["ip"] = "0.0.0.0"
            };

            public readonly Dictionary<string, string> AddBanData = new Dictionary<string, string>()
            {
                ["modules"] = "banlist",
                ["action"] = "addBan",
                ["steamID"] = "0",
                ["reason"] = "",
                ["banTime"] = "0",
                ["bannedBy"] = "0",
                ["ip"] = "0.0.0.0",
                ["familyShare"] = "0",
                ["singleBan"] = "0"
            };

            public readonly Dictionary<string, string> AddBanListData = new Dictionary<string, string>()
            {
                ["modules"] = "banlist",
                ["action"] = "importBans",
                ["data"] = ""
            };

            public readonly Dictionary<string, string> RemoveBanData = new Dictionary<string, string>()
            {
                ["modules"] = "banlist",
                ["action"] = "removeBan",
                ["steamID"] = "0",
                ["bannedBy"] = "0"
                //["ip"] = "0"
            };

            public WebApi(WWWRequests www, string storeId, string serverId, string serverKey)
            {
                _requests = www;
                InitData["storeID"] = storeId;
                InitData["serverID"] = serverId;
                InitData["serverKey"] = serverKey;

                CheckBanData["storeID"] = storeId;
                CheckBanData["serverID"] = serverId;
                CheckBanData["serverKey"] = serverKey;

                AddBanData["storeID"] = storeId;
                AddBanData["serverID"] = serverId;
                AddBanData["serverKey"] = serverKey;

                AddBanListData["storeID"] = storeId;
                AddBanListData["serverID"] = serverId;
                AddBanListData["serverKey"] = serverKey;

                RemoveBanData["storeID"] = storeId;
                RemoveBanData["serverID"] = serverId;
                RemoveBanData["serverKey"] = serverKey;
            }

            public void PostRequest(Dictionary<string, string> data, Action<bool, JsonResponse> response = null)
            {
                _requests.Request(BaseUrl, data, (res, error) =>
                {
                    if (!string.IsNullOrEmpty(error) || string.IsNullOrEmpty(res))
                    {
                        Log.File($"Request error: {error}", "errors");
                        response?.Invoke(false, new JsonResponse() { status = "RequestError", message = error });
                        return;
                    }

                    JsonResponse converted = null;
                    try
                    {
                        var settings = new JsonSerializerSettings
                        {
                            StringEscapeHandling = StringEscapeHandling.EscapeHtml
                        };

                        converted = JsonConvert.DeserializeObject<JsonResponse>(res, settings);
                    }
                    catch
                    {
                        // ignored
                    }

                    if (converted == null)
                    {
                        Log.File($"JsonError: {res}", "errors");
                        response?.Invoke(false, new JsonResponse() { status = "JsonError", message = res });
                        return;
                    }

                    if (!converted.IsSuccess())
                    {
                        switch (converted.message)
                        {
                            case "unload":
                                Log.Debug("Плагин отключен в настройках магазина.");
                                Interface.Oxide.UnloadPlugin("BanSystem");
                                return;
                            case "invalidStoreAuth":
                            case "invalidServerAuth":
                                Log.Error("Ошибка авторизации.");
                                Interface.Oxide.UnloadPlugin("BanSystem");
                                return;
                        }

                        response?.Invoke(false, converted);
                        return;
                    }

                    response?.Invoke(true, converted);
                });
            }
        }

        public static class Log
        {
            public static void File(string text, string title = "info")
            {
                text = $"[{DateTime.Now:HH:mm:ss}] {text}";
                _plugin.LogToFile($"{title}-{DateTime.Now:yyyy-MM-dd}.txt", text, _plugin);
            }

            public static void Debug(string format)
            {
                UnityEngine.Debug.Log($"[BanSystem] {format}");
            }

            public static void Error(string format, params object[] args)
            {
                UnityEngine.Debug.LogError($"[BanSystem] {string.Format(format, args)}");
            }

            public static void Warning(string format, params object[] args)
            {
                UnityEngine.Debug.LogWarning($"[BanSystem] {string.Format(format, args)}");
            }
        }

        public class ExConfig
        {
            private readonly Dictionary<string, string> _messages = new Dictionary<string, string>()
            {
                ["BAN.MESSAGE"] = "Вы забанены на этом сервере",
                ["BAN.MESSAGE.REASON"] = "Вы забанены на этом сервере, причина: {reason}",
                ["BAN.STRIKE.FORMAT"] = "Страйк бан",
                ["BAN.STRIKE.FORMAT.REASON"] = "Страйк бан: {STRIKES}",
                ["BAN.BROADCAST"] = "Игрок {name} ({steamID}) был забанен на {time}.",
                ["BAN.BROADCAST.REASON"] = "Игрок {name} ({steamID}) был забанен на {time}.\nПричина: {reason}",
                ["BAN.BROADCAST.FOREVER"] = "Игрок {name} ({steamID}) был забанен навсегда.",
                ["BAN.BROADCAST.FOREVER.REASON"] = "Игрок {name} ({steamID}) был забанен навсегда.\nПричина: {reason}",

                ["STRIKE.INFO"] = "Вы получили {strikeCount}/{banStrikeCount} страйков.",
                ["STRIKE.INFO.EMPTY"] = "Вы не получали страйков.",
                ["STRIKE.INFO.REASON"] = "Вы получили {strikeCount}/{banStrikeCount} страйков, причины: {STRIKES}",
                ["STRIKE.MESSAGE"] = "Вы получили страйк, всего страйков {strikeCount}/{banStrikeCount}",
                ["STRIKE.MESSAGE.REASON"] = "Вы получили страйк, причина: {reason}, всего страйков {strikeCount}/{banStrikeCount}",
                ["STRIKE.BROADCAST"] = "Игрок {name} ({steamID}) получил страйк.\nВсего страйков игрока {strikeCount}/{banStrikeCount}",
                ["STRIKE.BROADCAST.REASON"] = "Игрок {name} ({steamID}) получил страйк.\nПричина: {reason}.\nВсего страйков игрока {strikeCount}/{banStrikeCount}",

                ["KICK.BROADCAST"] = "Игрок {name} ({steamID}) был кикнут.",
                ["KICK.BROADCAST.REASON"] = "Игрок {name} ({steamID}) был кикнут.\nПричина: {reason}",

                ["UNBAN.BROADCAST"] = "Игрок {name} ({steamID}) был разбанен.",

                ["DAY_SHORT"] = "д ",
                ["HOUR_SHORT"] = "ч ",
                ["MIN_SHORT"] = "мин ",
                ["SEC_SHORT"] = "сек "
            };

            [JsonProperty("Время бана по умолчанию")]
            public string defaultTime = "0";
            [JsonProperty("Причина бана по умолчанию")]
            public string defaultBanReason = "";
            [JsonProperty("Причина кика по умолчанию")]
            public string defaultKickReason = "";
            [JsonProperty("Причина страйка по умолчанию")]
            public string defaultStrikeReason = "";

            [JsonProperty("Время бана за страйки")]
            public string strikeBanTime = "1d";
            [JsonProperty("Количество страйков для бана")]
            public ushort strikeBanCount = 3;
            [JsonProperty("Синхронизировать баны за страйки с другими серверами")]
            public bool syncStrikeBans = false;

            [JsonProperty("Оповещение о бане")]
            public bool showGlobalBanMessage = true;
            [JsonProperty("Оповещение о бане игрока, который не находится на сервере")]
            public bool showGlobalBanMessageOffline = false;
            [JsonProperty("Оповещение о разбане")]
            public bool showGlobalUnbanMessage = false;
            [JsonProperty("Оповещение о кике")]
            public bool showGlobalKickMessage = true;
            [JsonProperty("Оповещение о страйке")]
            public bool showGlobalStrikeMessage = true;

            private CSPlugin _pluginOwner;

            private void Initialize()
            {
                CSPlugin.GetLibrary<Lang>().RegisterMessages(_messages, _pluginOwner);
            }

            public static ExConfig Parse(RustPlugin plugin)
            {
                DynamicConfigFile pluginConfig = plugin.Config;
                ExConfig output = null;

                // Parsing config
                try { output = pluginConfig.ReadObject<ExConfig>(); }
                catch
                {
                    // ignored
                }

                if (output == null)
                {
                    pluginConfig.Save($"{pluginConfig.Filename}.jsonError");

                    Log.Debug("Lol");
                    output = new ExConfig();
                    Log.Error("Файл конфигурации \"{0}\" содержит ошибку и был заменен на стандартный.\n" +
                        "Ошибочный файл конфигурации сохранен под названием \"{0}.jsonError\"",
                        GetFileName(pluginConfig.Filename));
                }

                pluginConfig.WriteObject(output);

                output._pluginOwner = plugin;
                output.Initialize();

                return output;
            }
        }

        public class BansData
        {
            public HashSet<StrikeData> strikes = new HashSet<StrikeData>();
            public HashSet<SyncBanData> noSyncBans = new HashSet<SyncBanData>();
            public HashSet<ulong> bans = new HashSet<ulong>();

            public class SyncBanData
            {
                public ulong steamID;
                public ulong familyShare;
                public ulong bannedBy;
                public string reason;
                public string ip;
                public uint banTime;
                public int singleBan;
                public SyncBanData() { }
            }

            public class StrikeData
            {
                public ulong steamID;
                public List<string> strikes;
                public StrikeData() { }
            }

            public void Save()
            {
                Interface.Oxide.DataFileSystem.WriteObject("BanSystem", this);
            }

            public StrikeData GetStrikeData(ulong steam)
            {
                StrikeData strikeData = strikes.FirstOrDefault(sd => sd.steamID == steam);
                if (strikeData == default(StrikeData) || strikeData.strikes == null)
                {
                    return null;
                }

                return strikeData;
            }

            public StrikeData GiveStrike(ulong steam, string reason)
            {
                StrikeData strikeData = strikes.FirstOrDefault(sd => sd.steamID == steam);
                if (strikeData == default(StrikeData) || strikeData.strikes == null)
                {
                    strikeData = new StrikeData() { steamID = steam, strikes = new List<string> { reason } };
                    strikes.Add(strikeData);
                }
                else
                {
                    strikeData.strikes.Add(reason);
                }


                Save();

                return strikeData;
            }

            public void AddBan(ulong steam, string res, uint time, ulong admin, string _ip, ulong ownerID, int _single)
            {
                strikes.RemoveWhere(b => b.steamID == steam);
                noSyncBans.Add(new SyncBanData()
                {
                    steamID = steam,
                    reason = res,
                    banTime = time,
                    bannedBy = admin,
                    ip = _ip,
                    familyShare = ownerID,
                    singleBan = _single
                });
                Save();
            }

            public void RemoveBan(ulong steam)
            {
                strikes.RemoveWhere(b => b.steamID == steam);
                noSyncBans.RemoveWhere(b => b.steamID == steam);
                bans.Remove(steam);
                Save();
            }

            public void SyncBan(ulong steam, ulong owner, bool save = true)
            {
                noSyncBans.RemoveWhere(b => b.steamID == steam);
                bans.Add(steam);
                if (owner > 0)
                {
                    bans.Add(owner);
                }

                if (save)
                {
                    Save();
                }
            }

            public bool IsPlayerBanned(ulong steamID)
            {
                return noSyncBans.Any(b => b.steamID == steamID) || bans.Contains(steamID);
            }
        }

        public static string GetFileName(string path)
        {
            if (path != null)
            {
                int length = path.Length;
                int index = length;
                while (--index >= 0)
                {
                    char ch = path[index];
                    if ((int)ch == (int)Path.DirectorySeparatorChar || (int)ch == (int)Path.AltDirectorySeparatorChar || (int)ch == (int)Path.VolumeSeparatorChar)
                        return path.Substring(index + 1, length - index - 1);
                }
            }
            return path;
        }
    }
}