Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Text.RegularExpressions;
- using Facepunch;
- using Newtonsoft.Json;
- using Newtonsoft.Json.Linq;
- using Oxide.Core;
- using Oxide.Core.Libraries;
- using Oxide.Core.Libraries.Covalence;
- using Oxide.Core.Plugins;
- namespace Oxide.Plugins
- {
- [Info("Discord Report", "misticos", "1.2.1")]
- [Description("Send reports from players ingame to a Discord channel")]
- class DiscordReport : CovalencePlugin
- {
- #region Variables
- [PluginReference("PlaceholderAPI")]
- private Plugin _placeholders = null;
- private Action<IPlayer, StringBuilder, bool> _placeholderProcessor = null;
- private static DiscordReport _ins;
- private const string SteamProfileXML = "https://steamcommunity.com/profiles/{0}?xml=1";
- private const string SteamProfile = "https://steamcommunity.com/profiles/";
- private readonly Regex _steamProfileIconRegex =
- new Regex(@"(?<=<avatarIcon>[\w\W]+)https://.+\.jpg(?=[\w\W]+<\/avatarIcon>)", RegexOptions.Compiled);
- private Dictionary<string, uint> _cooldownData = new Dictionary<string, uint>();
- private Time _time = GetLibrary<Time>();
- private const string PermissionIgnoreCooldown = "discordreport.ignorecooldown";
- private const string PermissionUse = "discordreport.use";
- private const string PermissionAdmin = "discordreport.admin";
- #endregion
- #region Configuration
- private Configuration _config;
- private class Configuration
- {
- [JsonProperty("Webhook URL")]
- public string Webhook = "YOUR WEBHOOK LINK HERE";
- [JsonProperty("Message Content")]
- public string MessageContent = "";
- [JsonProperty("Embed Title")]
- public string EmbedTitle = "My Server Report";
- [JsonProperty("Embed Description")]
- public string EmbedDescription = "Report sent by a player from your server";
- [JsonProperty("Embed Color")]
- public int EmbedColor = 1484265;
- [JsonProperty("Set Author Icon From Player Profile")]
- public bool AuthorIcon = true;
- [JsonProperty("Use Reporter (True) Or Suspect (False) As Author")]
- public bool IsReporterIcon = true;
- [JsonProperty("Allow Reporting Admins")]
- public bool ReportAdmins = false;
- [JsonProperty("Report Commands", ObjectCreationHandling = ObjectCreationHandling.Replace)]
- public List<string> ReportCommands = new List<string> { "report" };
- [JsonProperty("Allow Only Online Suspects Reports")]
- public bool OnlyOnlineSuspects = true;
- [JsonProperty("Threshold Before Sending Reports")]
- public int Threshold = 0;
- #if RUST
- [JsonProperty("Show Recent Suspect Combatlog")]
- public bool ShowCombatlog = false;
- [JsonProperty("Show In-Game Subject")]
- public bool ShowInGameSubject = true;
- [JsonProperty("Minimum In-Game Report Subject Length")]
- public int SubjectMinimumGame = 0;
- [JsonProperty("Minimum In-Game Report Message Length")]
- public int MessageMinimumGame = 0;
- [JsonProperty("Allowed In-Game Report Types", ObjectCreationHandling = ObjectCreationHandling.Replace)]
- public string[] AllowedTypes = { "cheat", "abusive", "name", "spam" };
- [JsonProperty("Recent Combatlog Entries")]
- public int CombatlogEntries = 2;
- #endif
- [JsonProperty("Cooldown In Seconds")]
- public uint Cooldown = 300;
- [JsonProperty("User Cache Validity In Seconds")]
- public uint UserCacheValidity = 86400;
- [JsonProperty("Minimum Message Length")]
- public int MessageMinimum = 0;
- }
- protected override void LoadConfig()
- {
- base.LoadConfig();
- try
- {
- _config = Config.ReadObject<Configuration>();
- if (_config == null) throw new Exception();
- SaveConfig();
- }
- catch
- {
- PrintError("Your configuration file contains an error. Using default configuration values.");
- LoadDefaultConfig();
- }
- }
- protected override void SaveConfig() => Config.WriteObject(_config);
- protected override void LoadDefaultConfig() => _config = new Configuration();
- #endregion
- #region Work with Data
- private Dictionary<string, PlayerData> _loadedData = new Dictionary<string, PlayerData>();
- private void SaveData(string id)
- {
- PlayerData data;
- if (!_loadedData.TryGetValue(id, out data))
- return;
- Interface.Oxide.DataFileSystem.WriteObject(nameof(DiscordReport) + '/' + id, data);
- }
- private PlayerData GetOrLoadData(string id)
- {
- PlayerData data;
- if (_loadedData.TryGetValue(id, out data))
- return data;
- try
- {
- data = Interface.Oxide.DataFileSystem.ReadObject<PlayerData>(nameof(DiscordReport) + '/' + id);
- }
- catch (Exception e)
- {
- PrintError(e.ToString());
- }
- return _loadedData[id] = data ?? new PlayerData();
- }
- private class PlayerData
- {
- public string ImageURL = string.Empty;
- public string LastKnownAddress = string.Empty;
- public HashSet<string> Reporters = new HashSet<string>();
- public uint LastImageUpdate = 0;
- }
- #endregion
- #region Discord Classes
- // ReSharper disable NotAccessedField.Local
- private class WebhookBody
- {
- [JsonProperty("embeds")]
- public List<EmbedBody> Embeds;
- [JsonProperty("content")]
- public string Content = null;
- }
- private class EmbedBody
- {
- [JsonProperty("title")]
- public string Title;
- [JsonProperty("type")]
- public string Type;
- [JsonProperty("description")]
- public string Description;
- [JsonProperty("color")]
- public int Color;
- [JsonProperty("author")]
- public AuthorBody Author;
- [JsonProperty("fields")]
- public List<FieldBody> Fields;
- public class AuthorBody
- {
- [JsonProperty("name")]
- public string Name;
- [JsonProperty("url")]
- public string AuthorURL;
- [JsonProperty("icon_url")]
- public string AuthorIconURL;
- }
- public class FieldBody
- {
- [JsonProperty("name")]
- public string Name;
- [JsonProperty("value")]
- public string Value;
- [JsonProperty("inline")]
- public bool Inline;
- }
- }
- // ReSharper restore NotAccessedField.Local
- #endregion
- #region Hooks
- protected override void LoadDefaultMessages()
- {
- lang.RegisterMessages(new Dictionary<string, string>
- {
- { "Webhook: Reporter Data Title", "Reporter" },
- { "Webhook: Suspect Data Title", "Suspect" },
- #if RUST
- { "Webhook: Combatlog Title", "Suspect's Combatlog #{n}" },
- { "Webhook: Combatlog Attacker Title", "Attacker" },
- { "Webhook: Combatlog Target Title", "Target" },
- { "Webhook: Combatlog Time Title", "Time" },
- { "Webhook: Combatlog Weapon Title", "Weapon" },
- { "Webhook: Combatlog Ammo Title", "Ammo" },
- { "Webhook: Combatlog Distance Title", "Distance" },
- { "Webhook: Combatlog Old HP Title", "Old HP" },
- { "Webhook: Combatlog New HP Title", "New HP" },
- { "Webhook: Combatlog Info Title", "Info" },
- #endif
- {
- "Webhook: Reporter Data", "#{discordreport.total} {name} ({id}). IP: {ip}.\n" +
- "Ping: {ping}ms. Connected: {connected}"
- },
- {
- "Webhook: Suspect Data", "#{discordreport.total} {name} ({id}). IP: {ip}.\n" +
- "Ping: {ping}ms. Connected: {connected}"
- },
- { "Webhook: Report Subject", "Report Subject" },
- { "Webhook: Report Message", "Report Message" },
- {
- "Command: Syntax", "Syntax:\n" +
- "report (ID / \"Name\") (Message)\n" +
- "WARNING! Use quotes for names."
- },
- { "Command: User Not Found", "We were unable to find this user or multiple were found." },
- { "Command: Report Sent", "Thank you for your report, it was sent to our administration." },
- { "Command: Exceeded Cooldown", "You have exceeded your cooldown on reports." },
- { "Command: Cannot Report Admins", "You cannot report admins." },
- { "Command: Cannot Use", "You cannot use this command since you do not have enough permissions." },
- { "Command: Message Length", "Please add more information to the message." }
- }, this);
- }
- private void Init()
- {
- _ins = this;
- permission.RegisterPermission(PermissionIgnoreCooldown, this);
- permission.RegisterPermission(PermissionUse, this);
- permission.RegisterPermission(PermissionAdmin, this);
- foreach (var command in _config.ReportCommands)
- AddCovalenceCommand(command, nameof(CommandReport));
- }
- private void Loaded()
- {
- foreach (var player in players.Connected)
- OnUserConnected(player);
- }
- private void Unload()
- {
- _ins = null;
- }
- private void OnUserConnected(IPlayer player)
- {
- var user = GetOrLoadData(player.Id);
- if (_config.AuthorIcon)
- UpdateCachedImage(player, user);
- user.LastKnownAddress = player.Address;
- SaveData(player.Id);
- }
- private void OnPlaceholderAPIReady()
- {
- _placeholderProcessor =
- _placeholders.Call<Action<IPlayer, StringBuilder, bool>>("GetProcessPlaceholders", 1);
- _placeholders.Call("AddPlaceholder", this, "discordreport.total", new Func<IPlayer, string, object>(
- (player, s) => GetOrLoadData(player.Id).Reporters.Count));
- }
- private void OnPluginUnloaded(Plugin plugin)
- {
- if (plugin?.Name != "PlaceholderAPI")
- return;
- _placeholderProcessor = null;
- }
- #if RUST
- private void OnPlayerReported(BasePlayer player, string targetName, string targetId, string subject,
- string message, string type)
- {
- if (player == null)
- return;
- // Empty message will not be shown
- if (_config.MessageMinimumGame != 0 && !string.IsNullOrEmpty(message) &&
- message.Length < _config.MessageMinimumGame)
- return;
- subject = subject.Substring(3 + type.Length);
- if (_config.SubjectMinimumGame != 0 && _config.ShowInGameSubject &&
- subject.Length < _config.SubjectMinimumGame)
- return;
- if (_config.AllowedTypes.Length != 0 && !_config.AllowedTypes.Contains(type))
- return;
- var suspect = players.FindPlayer(targetId);
- if (suspect == null || player.IPlayer == null)
- return;
- if (!CanUse(player.IPlayer))
- return;
- if (!_config.ReportAdmins && suspect.HasPermission(PermissionAdmin))
- return;
- if (_config.OnlyOnlineSuspects && !suspect.IsConnected)
- return;
- if (ExceedsCooldown(player.IPlayer))
- return;
- SendReport(player.IPlayer, suspect, _config.ShowInGameSubject ? subject : string.Empty, message);
- }
- #endif
- #endregion
- #region Commands
- private void CommandReport(IPlayer player, string command, string[] args)
- {
- if (!CanUse(player))
- {
- player.Reply(GetMsg("Command: Cannot Use", player.Id));
- return;
- }
- if (args.Length < 2)
- goto syntax;
- var message = string.Join(" ", args.Skip(1));
- if (_config.MessageMinimum != 0 && message.Length < _config.MessageMinimum)
- {
- player.Reply(GetMsg("Command: Message Length", player.Id));
- return;
- }
- var suspect = players.FindPlayer(args[0]);
- if (suspect == null || !suspect.IsConnected && _config.OnlyOnlineSuspects)
- {
- player.Reply(GetMsg("Command: User Not Found", player.Id));
- return;
- }
- if (!_config.ReportAdmins && suspect.HasPermission(PermissionAdmin))
- {
- player.Reply(GetMsg("Command: Cannot Report Admins", player.Id));
- return;
- }
- if (ExceedsCooldown(player))
- {
- player.Reply(GetMsg("Command: Exceeded Cooldown", player.Id));
- return;
- }
- SendReport(player, suspect, string.Empty, message);
- player.Reply(GetMsg("Command: Report Sent", player.Id));
- return;
- syntax:
- player.Reply(GetMsg("Command: Syntax", player.Id));
- }
- #endregion
- #region Helpers
- private void UpdateCachedImage(IPlayer player, PlayerData data)
- {
- var now = _time.GetUnixTimestamp();
- // If cached and still valid, return
- if (!string.IsNullOrEmpty(data.ImageURL) &&
- data.LastImageUpdate + _config.UserCacheValidity > now)
- return;
- webrequest.Enqueue(string.Format(SteamProfileXML, player.Id), string.Empty,
- (code, result) =>
- {
- data.ImageURL = _steamProfileIconRegex.Match(result).Value;
- data.LastImageUpdate = now;
- SaveData(player.Id);
- },
- this);
- }
- #region Webhook
- private void SendReport(IPlayer reporter, IPlayer suspect, string subject, string message)
- {
- // Threshold
- var cached = GetOrLoadData(suspect.Id);
- cached.Reporters.Add(reporter.Id);
- if (cached.Reporters.Count < _config.Threshold)
- return;
- // Author Icon
- var authorIconURL = string.Empty;
- var author = _config.IsReporterIcon ? reporter : suspect;
- if (_config.AuthorIcon)
- {
- var user = GetOrLoadData(author.Id);
- if (!string.IsNullOrEmpty(user.ImageURL))
- authorIconURL = user.ImageURL;
- UpdateCachedImage(author, user); // Won't get update now but will be for any other reports for this user
- }
- // Embed data
- const string type = "rich";
- var body = new WebhookBody
- {
- Embeds = new List<EmbedBody>(),
- Content = string.IsNullOrEmpty(_config.MessageContent) ? null : _config.MessageContent
- };
- var fields = new List<EmbedBody.FieldBody>();
- if (!string.IsNullOrEmpty(subject))
- {
- fields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Report Subject"),
- Value = subject,
- Inline = false
- });
- }
- if (!string.IsNullOrEmpty(message))
- {
- fields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Report Message"),
- Value = message,
- Inline = false
- });
- }
- fields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Reporter Data Title"),
- Value =
- FormatUserDetails(new StringBuilder(GetMsg("Webhook: Reporter Data")), reporter),
- Inline = false
- });
- fields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Suspect Data Title"),
- Value = FormatUserDetails(new StringBuilder(GetMsg("Webhook: Suspect Data")), suspect),
- Inline = false
- });
- body.Embeds.Add(new EmbedBody
- {
- Title = _config.EmbedTitle,
- Description = _config.EmbedDescription,
- Type = type,
- Color = _config.EmbedColor,
- Author = new EmbedBody.AuthorBody
- {
- AuthorIconURL = authorIconURL,
- AuthorURL = SteamProfile + author.Id,
- Name = author.Name
- },
- Fields = fields
- });
- // Rust-specific embed data
- #if RUST
- if (_config.ShowCombatlog && suspect.Object is BasePlayer)
- {
- var events = CombatLog.Get(((BasePlayer)suspect.Object).userID).ToArray();
- for (var i = 1; i <= _config.CombatlogEntries && i <= events.Length; i++)
- {
- var combat = events[events.Length - i];
- var combatFields = new List<EmbedBody.FieldBody>();
- combatFields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Combatlog Attacker Title"),
- Value = combat.attacker,
- Inline = true
- });
- combatFields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Combatlog Target Title"),
- Value = combat.target,
- Inline = true
- });
- combatFields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Combatlog Time Title"),
- Value = (UnityEngine.Time.realtimeSinceStartup - combat.time).ToString("0.0s"),
- Inline = true
- });
- combatFields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Combatlog Weapon Title"),
- Value = combat.weapon,
- Inline = true
- });
- combatFields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Combatlog Ammo Title"),
- Value = combat.ammo,
- Inline = true
- });
- combatFields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Combatlog Distance Title"),
- Value = combat.distance.ToString("0.0m"),
- Inline = true
- });
- combatFields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Combatlog Old HP Title"),
- Value = combat.health_old.ToString("0.0"),
- Inline = true
- });
- combatFields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Combatlog New HP Title"),
- Value = combat.health_new.ToString("0.0"),
- Inline = true
- });
- combatFields.Add(new EmbedBody.FieldBody
- {
- Name = GetMsg("Webhook: Combatlog Info Title"),
- Value = string.IsNullOrEmpty(combat.info) ? "none" : combat.info,
- Inline = true
- });
- body.Embeds.Add(new EmbedBody
- {
- Title = GetMsg("Webhook: Combatlog Title").Replace("{n}", $"{i}"),
- Type = type,
- Color = _config.EmbedColor,
- Fields = combatFields
- });
- }
- }
- #endif
- // Send a web request
- webrequest.Enqueue(_config.Webhook, JObject.FromObject(body).ToString(),
- (code, result) =>
- {
- if (code == 204)
- {
- SetCooldown(reporter);
- }
- else
- {
- PrintWarning($"Discord Webhook returned {code}:\n{result}");
- }
- }, this, RequestMethod.POST,
- new Dictionary<string, string> { { "Content-Type", "application/json" } });
- }
- private string FormatUserDetails(StringBuilder builder, IPlayer player)
- {
- // Apply placeholders if possible
- _placeholderProcessor?.Invoke(player, builder, false);
- return builder
- .Replace("{name}", player.Name).Replace("{id}", player.Id)
- .Replace("{ip}", GetOrLoadData(player.Id)?.LastKnownAddress ?? "Unknown")
- .Replace("{ping}", player.IsConnected ? player.Ping.ToString() : "0")
- .Replace("{connected}", player.IsConnected.ToString()).ToString();
- }
- #endregion
- #region Cooldown
- private bool ExceedsCooldown(IPlayer player)
- {
- if (player.HasPermission(PermissionIgnoreCooldown))
- return false;
- var currentTime = _time.GetUnixTimestamp();
- if (_cooldownData.ContainsKey(player.Id))
- return _cooldownData[player.Id] - currentTime < _config.Cooldown;
- return false;
- }
- private void SetCooldown(IPlayer player) => _cooldownData[player.Id] = _time.GetUnixTimestamp();
- #endregion
- private bool CanUse(IPlayer player) => player.HasPermission(PermissionUse);
- private string GetMsg(string key, string userId = null) => lang.GetMessage(key, this, userId);
- #endregion
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment