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

FREE TGOCF7Report : 1.0.0 1.0.0

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

namespace Oxide.Plugins
{
    [Info("TGOCF7Report", "TheGhostOfChaos", "1.0.0")]
    [Description("Forwards in-game F7 reports to a Discord webhook")]
    public class TGOCF7Report : RustPlugin
    {
        #region Fields

        private Configuration config;

        private readonly Queue<string> _queue = new Queue<string>();
        private bool _sending;

        private readonly Dictionary<ulong, float> _cooldowns = new Dictionary<ulong, float>();

        private const string PermBypass = "tgocf7report.bypass";

        #endregion

        #region Configuration

        private class Configuration
        {
            [JsonProperty("Discord webhook URL")]
            public string WebhookUrl = "Change me to the Discord webhook";

            [JsonProperty("Webhook bot name")]
            public string BotName = "TGOC Reports";

            [JsonProperty("Webhook avatar URL (empty = default)")]
            public string AvatarUrl = "";

            [JsonProperty("Embed thumbnail URL (empty = none)")]
            public string ThumbnailUrl = "";

            [JsonProperty("Default embed color (decimal)")]
            public int EmbedColor = 15158332;

            [JsonProperty("Role ID to ping (empty = no ping)")]
            public string MentionRoleId = "";

            [JsonProperty("Server name shown in embed")]
            public string ServerName = "TGOC | Report forwarding";

            [JsonProperty("Show Steam profile links")]
            public bool ShowSteamLinks = true;

            [JsonProperty("Show reporter position / grid")]
            public bool ShowPosition = true;

            [JsonProperty("Show reported player online status and position")]
            public bool ShowTargetStatus = true;

            [JsonProperty("Show copy-paste admin commands")]
            public bool ShowAdminCommands = true;

            [JsonProperty("Cooldown per player in seconds (0 = disabled)")]
            public float Cooldown = 30f;

            [JsonProperty("Log reports to server console")]
            public bool LogToConsole = true;

            [JsonProperty("Log reports to file")]
            public bool LogToFile = true;

            [JsonProperty("Report type labels")]
            public Dictionary<string, string> TypeLabels = new Dictionary<string, string>
            {
                ["cheat"] = "🔴 Cheating",
                ["abusive"] = "🗯️ Abusive behaviour",
                ["spam"] = "📢 Spam",
                ["name"] = "🏷️ Offensive name",
                ["break_server_rules"] = "📕 Breaking server rules",
                ["other"] = "❔ Other"
            };

            [JsonProperty("Report type colors (decimal)")]
            public Dictionary<string, int> TypeColors = new Dictionary<string, int>
            {
                ["cheat"] = 15158332,
                ["abusive"] = 15105570,
                ["spam"] = 10181046,
                ["name"] = 3447003,
                ["break_server_rules"] = 16776960,
                ["other"] = 9807270
            };
        }

        protected override void LoadDefaultConfig()
        {
            config = new Configuration();
            PrintWarning("New configuration file created. Please fill in your webhook URL!");
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null) throw new Exception("Config is null");
            }
            catch (Exception ex)
            {
                PrintError($"Failed to read config ({ex.Message}). Using default values.");
                config = new Configuration();
            }
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config, true);

        #endregion

        #region Hooks

        private void Init()
        {
            permission.RegisterPermission(PermBypass, this);

            try
            {
                if (config == null) config = new Configuration();
                if (Config != null) SaveConfig();
            }
            catch (Exception ex)
            {
                PrintError($"Could not write config: {ex.Message}");
            }
        }

        private void OnServerInitialized()
        {
            Puts($"Config file: {Config.Filename}");

            if (string.IsNullOrEmpty(config.WebhookUrl) || !config.WebhookUrl.StartsWith("http"))
                PrintWarning("No valid webhook URL set - reports will not be sent.");
        }

        private void OnPlayerReported(BasePlayer reporter, string targetName, string targetId,
                                      string subject, string message, string type)
        {
            if (reporter == null) return;

            if (config.Cooldown > 0f && !permission.UserHasPermission(reporter.UserIDString, PermBypass))
            {
                float last;
                if (_cooldowns.TryGetValue(reporter.userID, out last) &&
                    Time.realtimeSinceStartup - last < config.Cooldown)
                {
                    return;
                }
                _cooldowns[reporter.userID] = Time.realtimeSinceStartup;
            }

            if (config.LogToConsole)
                Puts($"[F7] {reporter.displayName} ({reporter.UserIDString}) reported {targetName} ({targetId}) | {type} | {subject}");

            if (config.LogToFile)
                LogToFile("reports", $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {reporter.displayName} ({reporter.UserIDString}) -> {targetName} ({targetId}) | Type: {type} | Subject: {subject} | Message: {message}", this);

            SendReport(reporter, targetName, targetId, subject, message, type);
        }

        private void Unload()
        {
            _queue.Clear();
            _cooldowns.Clear();
        }

        #endregion

        #region Discord

        private void SendReport(BasePlayer reporter, string targetName, string targetId,
                                string subject, string message, string type)
        {
            if (string.IsNullOrEmpty(config.WebhookUrl) || !config.WebhookUrl.StartsWith("http")) return;

            BasePlayer target = FindTarget(targetId);

            var fields = new List<Field>
            {
                new Field("👤 Reporter", PlayerBlock(reporter.displayName, reporter.UserIDString, null), true),
                new Field("🎯 Reported", PlayerBlock(targetName, targetId, target), true),
                new Field("📋 Reason", TypeLabel(type), true)
            };

            if (config.ShowPosition)
                fields.Add(new Field("📍 Reporter", PositionBlock(reporter), true));

            if (config.ShowTargetStatus && target != null)
                fields.Add(new Field("📍 Reported", PositionBlock(target), true));

            if (config.ShowAdminCommands)
                fields.Add(new Field("⚙️ Admin", AdminBlock(reporter, target, targetId), false));

            var embed = new Embed
            {
                Author = new Author { Name = "New F7 Report" },
                Title = CleanSubject(subject, type),
                Description = Quote(message),
                Color = TypeColor(type),
                Fields = fields,
                Footer = new Footer { Text = $"{config.ServerName} • {ConVar.Server.hostname}" },
                Timestamp = DateTime.UtcNow.ToString("o")
            };

            if (!string.IsNullOrEmpty(config.ThumbnailUrl))
                embed.Thumbnail = new Media { Url = config.ThumbnailUrl };

            var payload = new WebhookPayload
            {
                Username = string.IsNullOrEmpty(config.BotName) ? null : config.BotName,
                AvatarUrl = string.IsNullOrEmpty(config.AvatarUrl) ? null : config.AvatarUrl,
                Content = string.IsNullOrEmpty(config.MentionRoleId) ? null : $"<@&{config.MentionRoleId}>",
                Embeds = new List<Embed> { embed }
            };

            Enqueue(JsonConvert.SerializeObject(payload, Formatting.None,
                new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));
        }

        #endregion

        #region Formatting helpers

        private BasePlayer FindTarget(string targetId)
        {
            if (!config.ShowTargetStatus || string.IsNullOrEmpty(targetId)) return null;

            try
            {
                ulong id;
                if (!ulong.TryParse(targetId, out id)) return null;
                return BasePlayer.FindByID(id) ?? BasePlayer.FindSleeping(id);
            }
            catch
            {
                return null;
            }
        }

        private string PlayerBlock(string name, string id, BasePlayer found)
        {
            string clean = Escape(string.IsNullOrEmpty(name) ? "Unknown" : name);
            var sb = new StringBuilder();

            sb.Append(config.ShowSteamLinks
                ? $"**[{Truncate(clean, 60)}](https://steamcommunity.com/profiles/{id})**"
                : $"**{Truncate(clean, 60)}**");

            sb.Append($"\n`{id}`");

            if (config.ShowTargetStatus && found != null)
                sb.Append(found.IsConnected ? "\n🟢 Online" : "\n💤 Sleeping");

            return sb.ToString();
        }

        private string PositionBlock(BasePlayer player)
        {
            try
            {
                Vector3 pos = player.transform.position;
                return $"Grid **{MapHelper.PositionToString(pos)}**\n`{pos.x:F0} {pos.y:F0} {pos.z:F0}`";
            }
            catch
            {
                return "*unknown*";
            }
        }

        private string AdminBlock(BasePlayer reporter, BasePlayer target, string targetId)
        {
            var sb = new StringBuilder("```\n");

            if (target != null)
            {
                Vector3 t = target.transform.position;
                sb.Append($"teleportpos {t.x:F0},{t.y:F0},{t.z:F0}\n");
            }

            Vector3 r = reporter.transform.position;
            sb.Append($"teleportpos {r.x:F0},{r.y:F0},{r.z:F0}\n");
            sb.Append($"{targetId}\n```");

            return sb.ToString();
        }

        private string TypeLabel(string type)
        {
            if (string.IsNullOrEmpty(type)) return "*Unknown*";

            string label;
            if (config.TypeLabels != null && config.TypeLabels.TryGetValue(type, out label) && !string.IsNullOrEmpty(label))
                return label;

            string pretty = type.Replace('_', ' ').Trim();
            return pretty.Length == 0 ? "*Unknown*" : char.ToUpper(pretty[0]) + pretty.Substring(1);
        }

        private int TypeColor(string type)
        {
            int color;
            if (config.TypeColors != null && !string.IsNullOrEmpty(type) &&
                config.TypeColors.TryGetValue(type, out color))
                return color;

            return config.EmbedColor;
        }

        private string CleanSubject(string subject, string type)
        {
            if (string.IsNullOrEmpty(subject)) return "No subject";

            if (!string.IsNullOrEmpty(type))
            {
                string prefix = $"[{type}]";
                if (subject.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                    subject = subject.Substring(prefix.Length).Trim();
            }

            return Truncate(subject, 240);
        }

        private static string Quote(string text)
        {
            if (string.IsNullOrEmpty(text)) return "> *No message provided*";

            string clean = Truncate(text, 3000).Replace("\r", "");
            return "> " + clean.Replace("\n", "\n> ");
        }

        private static string Escape(string input)
        {
            if (string.IsNullOrEmpty(input)) return input;

            var sb = new StringBuilder(input.Length);
            foreach (char c in input)
            {
                if (c == '*' || c == '_' || c == '~' || c == '`' || c == '|' ||
                    c == '[' || c == ']' || c == '\\' || c == '>' || c == '#')
                    sb.Append('\\');
                sb.Append(c);
            }
            return sb.ToString();
        }

        private static string Truncate(string input, int max)
        {
            if (string.IsNullOrEmpty(input)) return "-";
            return input.Length <= max ? input : input.Substring(0, max - 3) + "...";
        }

        #endregion

        #region Webhook queue

        private void Enqueue(string payload)
        {
            _queue.Enqueue(payload);
            if (!_sending) ProcessQueue();
        }

        private void ProcessQueue()
        {
            if (_queue.Count == 0)
            {
                _sending = false;
                return;
            }

            _sending = true;
            string payload = _queue.Peek();

            var headers = new Dictionary<string, string> { ["Content-Type"] = "application/json" };

            webrequest.Enqueue(config.WebhookUrl, payload, (code, response) =>
            {
                if (code == 200 || code == 204)
                {
                    _queue.Dequeue();
                    timer.Once(0.5f, ProcessQueue);
                    return;
                }

                if (code == 429) // rate limited: retry
                {
                    float retry = 2f;
                    try
                    {
                        var obj = JsonConvert.DeserializeObject<Dictionary<string, object>>(response);
                        object val;
                        if (obj != null && obj.TryGetValue("retry_after", out val))
                        {
                            retry = Convert.ToSingle(val);
                            if (retry > 100f) retry /= 1000f;
                        }
                    }
                    catch { }
                    timer.Once(Mathf.Clamp(retry + 0.25f, 0.5f, 60f), ProcessQueue);
                    return;
                }

                PrintWarning($"Discord webhook error (HTTP {code}): {response}");
                _queue.Dequeue();
                timer.Once(1f, ProcessQueue);
            }, this, RequestMethod.POST, headers, 10f);
        }

        #endregion

        #region Test command

        [ConsoleCommand("tgocf7report.test")]
        private void CmdTest(ConsoleSystem.Arg arg)
        {
            if (arg.Connection != null && !arg.IsAdmin) return;

            var player = arg.Player();
            if (player == null)
            {
                arg.ReplyWith("Run this command in-game (F1) so the plugin has a reporter to work with.");
                return;
            }

            SendReport(player, "Test Player", "76561198000000000",
                "[break_server_rules] Test report",
                "This is a test from TGOCF7Report.\nSecond line to show multi-line formatting.",
                "break_server_rules");

            arg.ReplyWith("Test report sent to Discord.");
        }

        #endregion

        #region Payload classes

        private class WebhookPayload
        {
            [JsonProperty("username")] public string Username;
            [JsonProperty("avatar_url")] public string AvatarUrl;
            [JsonProperty("content")] public string Content;
            [JsonProperty("embeds")] public List<Embed> Embeds;
        }

        private class Embed
        {
            [JsonProperty("author")] public Author Author;
            [JsonProperty("title")] public string Title;
            [JsonProperty("description")] public string Description;
            [JsonProperty("color")] public int Color;
            [JsonProperty("fields")] public List<Field> Fields;
            [JsonProperty("thumbnail")] public Media Thumbnail;
            [JsonProperty("footer")] public Footer Footer;
            [JsonProperty("timestamp")] public string Timestamp;
        }

        private class Author
        {
            [JsonProperty("name")] public string Name;
            [JsonProperty("icon_url")] public string IconUrl;
        }

        private class Media
        {
            [JsonProperty("url")] public string Url;
        }

        private class Field
        {
            [JsonProperty("name")] public string Name;
            [JsonProperty("value")] public string Value;
            [JsonProperty("inline")] public bool Inline;

            public Field(string name, string value, bool inline)
            {
                Name = name;
                Value = value;
                Inline = inline;
            }
        }

        private class Footer
        {
            [JsonProperty("text")] public string Text;
        }

        #endregion
    }
}

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