Иконка ресурса

FREE ZonelyCoreRust : 1.0.0 1.0.0

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;

namespace Oxide.Plugins
{
    [Info("ZonelyCoreRust", "Zonely", "1.0.0")]
    [Description("Poll-based command bridge for Rust: fetch signed jobs from your website, validate, queue, and execute.")]
    public class ZonelyCoreRust : CovalencePlugin
    {
        private ConfigData _cfg;

        public class ConfigData
        {
            [JsonProperty("Website URL (e.g. https://example.com)")]
            public string WebsiteUrl = "";

            [JsonProperty("API Key (shared with website)")]
            public string ApiKey = "";

            [JsonProperty("Server Token (unique per Rust server)")]
            public string ServerToken = "";

            [JsonProperty("Poll Interval Seconds")]
            public int PollIntervalSec = 8;

            [JsonProperty("Max Items Per Poll")]
            public int MaxPerPoll = 50;

            [JsonProperty("Delay Between Commands (seconds)")]
            public float DelayBetweenSec = 0.75f;

            [JsonProperty("Check Player Online Before Execute")]
            public bool CheckPlayerOnline = true;

            [JsonProperty("Debug Mode")]
            public bool Debug = false;
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _cfg = Config.ReadObject<ConfigData>();
                if (_cfg == null) throw new Exception("null cfg");
            }
            catch
            {
                PrintWarning("Config invalid, generating defaults...");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            _cfg = new ConfigData();
        }

        protected override void SaveConfig() => Config.WriteObject(_cfg, true);
        private class PullResponse
        {
            public List<Item> items;
            public class Item
            {
                public string id;
                public string username;
                public string command;
            }
        }

        private readonly Dictionary<string, List<string>> _queue = new Dictionary<string, List<string>>();
        private readonly object _queueLock = new object();
        private const string DATA_FILE = "queue.json";

        private void LoadQueue()
        {
            try
            {
                var path = Path.Combine(Interface.Oxide.DataDirectory, Name, DATA_FILE);
                if (File.Exists(path))
                {
                    var json = File.ReadAllText(path);
                    var d = JsonConvert.DeserializeObject<Dictionary<string, List<string>>>(json);
                    if (d != null) foreach (var kv in d) _queue[kv.Key] = kv.Value;
                    LogInfo($"Loaded {_queue.Count} queued player(s).");
                }
            }
            catch (Exception e)
            {
                LogError($"LoadQueue error: {e.Message}");
            }
        }

        private void SaveQueue()
        {
            try
            {
                var dir = Path.Combine(Interface.Oxide.DataDirectory, Name);
                if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                var path = Path.Combine(dir, DATA_FILE);
                var json = JsonConvert.SerializeObject(_queue, Formatting.Indented);
                File.WriteAllText(path, json);
            }
            catch (Exception e)
            {
                LogError($"SaveQueue error: {e.Message}");
            }
        }

        private void Enqueue(string steamId, IEnumerable<string> commands)
        {
            lock (_queueLock)
            {
                if (!_queue.ContainsKey(steamId)) _queue[steamId] = new List<string>();
                _queue[steamId].AddRange(commands.Where(c => !string.IsNullOrWhiteSpace(c)));
                SaveQueue();
            }
        }

        private void ProcessQueueFor(string steamId)
        {
            List<string> cmds = null;
            lock (_queueLock)
            {
                if (_queue.TryGetValue(steamId, out cmds))
                {
                    _queue.Remove(steamId);
                    SaveQueue();
                }
            }
            if (cmds == null || cmds.Count == 0) return;

            var p = players.FindPlayerById(steamId);
            var name = p?.Name ?? "Unknown";
            LogInfo($"Processing {cmds.Count} queued command(s) for {name} ({steamId})");
            ExecuteCommands(cmds, name);
        }
        #endregion

        #region Lifecycle
        private Timer _pollTimer;

        void Init()
        {
            if (!ValidateCfg())
            {
                PrintError("Configuration invalid. Please edit the config and reload the plugin.");
                return;
            }
            LoadQueue();
        }

        void OnServerInitialized()
        {
            StartPolling();
        }

        void Unload()
        {
            _pollTimer?.Destroy();
            SaveQueue();
        }

        void OnUserConnected(IPlayer p)
        {
            try
            {
                if (p == null || string.IsNullOrEmpty(p.Id)) return;
                if (_cfg.CheckPlayerOnline && _queue.ContainsKey(p.Id))
                {
                    timer.Once(2f, () => ProcessQueueFor(p.Id));
                }
            }
            catch (Exception e)
            {
                LogError($"OnUserConnected error: {e.Message}");
            }
        }
        #endregion

        #region Polling
        private void StartPolling()
        {
            _pollTimer?.Destroy();
            _pollTimer = timer.Every(Math.Max(3, _cfg.PollIntervalSec), DoPoll);
            LogInfo($"Polling {_cfg.WebsiteUrl} every {_cfg.PollIntervalSec}s");
        }

        private void DoPoll()
        {
            if (string.IsNullOrWhiteSpace(_cfg.WebsiteUrl)) return;

            var url = $"{_cfg.WebsiteUrl.TrimEnd('/')}/api/zcr/pull";
            var body = $"token={Uri.EscapeDataString(_cfg.ServerToken)}&limit={_cfg.MaxPerPoll}";
            var headers = new Dictionary<string, string>
            {
                ["X-API-Key"] = _cfg.ApiKey,
                ["Content-Type"] = "application/x-www-form-urlencoded",
                ["Accept"] = "application/json"
            };

            if (_cfg.Debug) LogInfo($"Polling {url}");

            webrequest.Enqueue(url, body, (code, resp) =>
            {
                try
                {
                    if (code != 200 || string.IsNullOrWhiteSpace(resp))
                    {
                        if (_cfg.Debug) LogWarning($"Pull failed: HTTP {code}");
                        return;
                    }

                    string ts = webrequest.LastResponseHeaders?.ContainsKey("X-Timestamp") == true ? webrequest.LastResponseHeaders["X-Timestamp"] : null;
                    string sig = webrequest.LastResponseHeaders?.ContainsKey("X-Signature") == true ? webrequest.LastResponseHeaders["X-Signature"] : null;
                    if (!VerifyOptionalSignature(ts, resp, sig))
                    {
                        LogWarning("Signature verification failed. Skipping payload.");
                        return;
                    }

                    var data = JsonConvert.DeserializeObject<PullResponse>(resp);
                    if (data?.items == null || data.items.Count == 0) return;

                    var byUser = new Dictionary<string, List<PullResponse.Item>>();
                    foreach (var it in data.items)
                    {
                        if (it == null || string.IsNullOrWhiteSpace(it.command)) continue;
                        var uid = string.IsNullOrWhiteSpace(it.username) ? "" : it.username;
                        if (!byUser.ContainsKey(uid)) byUser[uid] = new List<PullResponse.Item>();
                        byUser[uid].Add(it);
                    }

                    var ackIds = new List<string>();

                    foreach (var kv in byUser)
                    {
                        var uid = kv.Key;
                        var list = kv.Value;
                        var commands = list.Select(x => x.command).ToList();

                        if (_cfg.CheckPlayerOnline && !string.IsNullOrEmpty(uid))
                        {
                            var pl = players.FindPlayerById(uid);
                            if (pl != null && pl.IsConnected)
                            {
                                var name = pl.Name ?? "Unknown";
                                LogInfo($"User online: {name} ({uid}) => executing {commands.Count}");
                                ExecuteCommands(commands, name);
                            }
                            else
                            {
                                LogInfo($"User offline: {uid} => queueing {commands.Count}");
                                Enqueue(uid, commands);
                            }
                        }
                        else
                        {
                            ExecuteCommands(commands, uid);
                        }

                        ackIds.AddRange(list.Select(x => x.id).Where(x => !string.IsNullOrWhiteSpace(x)));
                    }

                    if (ackIds.Count > 0) Ack(ackIds);
                }
                catch (Exception e)
                {
                    LogError($"Poll parse error: {e.Message}");
                }
            }, this, Oxide.Core.Libraries.RequestMethod.POST, headers, 10f);
        }

        private bool VerifyOptionalSignature(string ts, string body, string sig)
        {
            try
            {
                if (string.IsNullOrEmpty(sig) || string.IsNullOrEmpty(ts)) return true;
                var message = $"{ts}.{body}";
                var expected = ToHex(HmacSha256(_cfg.ApiKey, message));
                var a = HexToBytes(expected);
                var b = HexToBytes(sig);
                if (a.Length != b.Length) return false;
                int diff = 0; for (int i = 0; i < a.Length; i++) diff |= a[i] ^ b[i];
                return diff == 0;
            }
            catch { return false; }
        }

        private static byte[] HmacSha256(string key, string data)
        {
            using (var h = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(key ?? "")))
                return h.ComputeHash(Encoding.UTF8.GetBytes(data ?? ""));
        }
        private static string ToHex(byte[] b)
        {
            var sb = new StringBuilder(b.Length * 2);
            foreach (var x in b) sb.Append(x.ToString("x2"));
            return sb.ToString();
        }
        private static byte[] HexToBytes(string hex)
        {
            if (string.IsNullOrEmpty(hex) || hex.Length % 2 != 0) return new byte[0];
            var data = new byte[hex.Length / 2];
            for (int i = 0; i < data.Length; i++)
                data[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
            return data;
        }
        private void ExecuteCommands(List<string> commands, string usernameOrId)
        {
            if (commands == null || commands.Count == 0) return;
            if (_cfg.Debug) LogInfo($"Executing {commands.Count} command(s) for {usernameOrId}");

            float d = 0f;
            foreach (var cmd in commands)
            {
                var c = (cmd ?? "").Trim();
                if (string.IsNullOrEmpty(c)) continue;
                var delay = d;
                timer.Once(delay, () =>
                {
                    try
                    {
                        server.Command(c);
                        if (_cfg.Debug) LogInfo($"OK: {c}");
                    }
                    catch (Exception e)
                    {
                        LogError($"Exec fail: {c} => {e.Message}");
                    }
                });
                d += Math.Max(0.05f, _cfg.DelayBetweenSec);
            }
        }
        private void Ack(List<string> ids)
        {
            try
            {
                var url = $"{_cfg.WebsiteUrl.TrimEnd('/')}/api/zcr/ack";
                var obj = new { token = _cfg.ServerToken, ids = ids };
                var json = JsonConvert.SerializeObject(obj);
                var headers = new Dictionary<string, string>
                {
                    ["X-API-Key"] = _cfg.ApiKey,
                    ["Content-Type"] = "application/json",
                    ["Accept"] = "application/json"
                };
                if (_cfg.Debug) LogInfo($"Ack {ids.Count} ids");
                webrequest.Enqueue(url, json, (code, resp) =>
                {
                    if (_cfg.Debug) LogInfo($"Ack result: HTTP {code}");
                }, this, Oxide.Core.Libraries.RequestMethod.POST, headers, 10f);
            }
            catch (Exception e)
            {
                LogError($"Ack error: {e.Message}");
            }
        }
        [Command("zcr.status")]
        private void CmdStatus(IPlayer player, string cmd, string[] args)
        {
            if (!player.IsAdmin) return;
            player.Reply($"ZonelyCoreRust v1.0.0\n" +
                         $"- Website: {_cfg.WebsiteUrl}\n" +
                         $"- Poll: every {_cfg.PollIntervalSec}s, batch {_cfg.MaxPerPoll}\n" +
                         $"- CheckOnline: {_cfg.CheckPlayerOnline}\n" +
                         $"- Queue size: {_queue.Sum(kv => kv.Value.Count)}");
        }

        [Command("zcr.poll")]
        private void CmdPoll(IPlayer player, string cmd, string[] args)
        {
            if (!player.IsAdmin) return;
            DoPoll();
            player.Reply("Polling now...");
        }

        [Command("zcr.debug")]
        private void CmdDebug(IPlayer player, string cmd, string[] args)
        {
            if (!player.IsAdmin) return;
            _cfg.Debug = !_cfg.Debug;
            SaveConfig();
            player.Reply("Debug: " + (_cfg.Debug ? "ON" : "OFF"));
        }
        private void LogInfo(string m) => Puts($"[ZonelyCoreRust] {m}");
        private void LogWarning(string m) => PrintWarning($"[ZonelyCoreRust] {m}");
        private void LogError(string m) => PrintError($"[ZonelyCoreRust] {m}");
        private bool ValidateCfg()
        {
            var ok = true;
            if (string.IsNullOrWhiteSpace(_cfg.WebsiteUrl))
            {
                PrintError("Website URL must be set and use HTTPS in production.");
                ok = false;
            }
            if (string.IsNullOrWhiteSpace(_cfg.ApiKey))
            {
                PrintError("API Key must be set.");
                ok = false;
            }
            if (string.IsNullOrWhiteSpace(_cfg.ServerToken))
            {
                PrintError("Server Token must be set.");
                ok = false;
            }
            return ok;
        }
    }
}

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху