using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Oxide.Core.Libraries.Covalence; namespace Oxide.Plugins { [Info("UWStatusBot", "Torque", "1.0.2")] [Description("Posts and edits a live Discord status embed for your Rust server")] class UWStatusBot : CovalencePlugin { private PluginConfig _config; private string _messageId = null; private Timer _updateTimer; private DateTime _startTime; #region Config class PluginConfig { [JsonProperty("Bot Token")] public string BotToken { get; set; } = "YOUR_BOT_TOKEN_HERE"; [JsonProperty("Channel ID")] public string ChannelId { get; set; } = "YOUR_CHANNEL_ID_HERE"; [JsonProperty("Server Name")] public string ServerName { get; set; } = "My Rust Server"; [JsonProperty("Server Description")] public string ServerDescription { get; set; } = "☢️ **Solo/Duo/Trio | 2X | Vanilla+**"; [JsonProperty("Server IP and Port (for connect command)")] public string ServerAddress { get; set; } = "0.0.0.0:28015"; [JsonProperty("Max Players")] public int MaxPlayers { get; set; } = 100; [JsonProperty("Server Icon URL (thumbnail, leave blank to disable)")] public string ThumbnailUrl { get; set; } = ""; [JsonProperty("Server Banner URL (large image, leave blank to disable)")] public string BannerUrl { get; set; } = ""; [JsonProperty("Update Interval (seconds)")] public int UpdateInterval { get; set; } = 60; [JsonProperty("Wipe Schedule (days between wipes, e.g. 7 or 14)")] public int WipeIntervalDays { get; set; } = 14; [JsonProperty("Last Wipe Date (YYYY-MM-DD) — used for auto wipe calculation")] public string LastWipeDate { get; set; } = "2026-01-01"; [JsonProperty("Manual Next Wipe Date (YYYY-MM-DD) — overrides auto if set, leave blank to use auto")] public string ManualNextWipeDate { get; set; } = ""; [JsonProperty("Saved Message ID (auto-filled, do not edit)")] public string SavedMessageId { get; set; } = ""; } protected override void LoadConfig() { base.LoadConfig(); _config = Config.ReadObject(); SaveConfig(); } protected override void LoadDefaultConfig() { _config = new PluginConfig(); SaveConfig(); } protected override void SaveConfig() => Config.WriteObject(_config); #endregion #region Oxide Hooks void OnServerInitialized() { _startTime = DateTime.UtcNow; if (!string.IsNullOrEmpty(_config.SavedMessageId)) _messageId = _config.SavedMessageId; SendOrEditEmbed(); _updateTimer = timer.Every(_config.UpdateInterval, SendOrEditEmbed); } void Unload() { _updateTimer?.Destroy(); SendOfflineEmbed(); } #endregion #region Core Logic private void SendOrEditEmbed() { var embed = BuildOnlineEmbed(); var payload = BuildPayload(embed); if (string.IsNullOrEmpty(_messageId)) PostMessage(payload); else EditMessage(payload); } private void SendOfflineEmbed() { if (string.IsNullOrEmpty(_messageId)) return; var embed = BuildOfflineEmbed(); var payload = BuildPayload(embed); EditMessage(payload); } private string BuildPayload(object embed) { return JsonConvert.SerializeObject(new { embeds = new[] { embed } }); } private object BuildOnlineEmbed() { int online = players.Connected.Count(); string uptime = GetUptime(); string nextWipe = GetNextWipe(); // Build optional thumbnail/image fields dynamically var embedDict = new Dictionary { ["title"] = _config.ServerName, ["description"] = _config.ServerDescription, ["color"] = 0x2ECC71, ["fields"] = new[] { new { name = "STATUS", value = "🟢 Online", inline = true }, new { name = "PLAYERS", value = $"{online}/{_config.MaxPlayers}", inline = true }, new { name = "CONNECT COMMAND", value = $"`connect {_config.ServerAddress}`", inline = false }, new { name = "NEXT WIPE", value = nextWipe, inline = true }, new { name = "UPTIME", value = uptime, inline = true }, }, ["footer"] = new { text = $"{_config.ServerName} • Updated every {_config.UpdateInterval}s" }, ["timestamp"] = DateTime.UtcNow.ToString("o") }; if (!string.IsNullOrWhiteSpace(_config.ThumbnailUrl)) embedDict["thumbnail"] = new { url = _config.ThumbnailUrl }; if (!string.IsNullOrWhiteSpace(_config.BannerUrl)) embedDict["image"] = new { url = _config.BannerUrl }; return embedDict; } private object BuildOfflineEmbed() { var embedDict = new Dictionary { ["title"] = _config.ServerName, ["description"] = _config.ServerDescription, ["color"] = 0xE74C3C, ["fields"] = new[] { new { name = "STATUS", value = "🔴 Offline", inline = true }, new { name = "PLAYERS", value = $"0/{_config.MaxPlayers}", inline = true }, new { name = "CONNECT COMMAND", value = $"`connect {_config.ServerAddress}`", inline = false }, }, ["footer"] = new { text = $"{_config.ServerName} • Server is offline" }, ["timestamp"] = DateTime.UtcNow.ToString("o") }; return embedDict; } #endregion #region Discord API private void PostMessage(string payload) { string url = $"https://discord.com/api/v10/channels/{_config.ChannelId}/messages"; webrequest.Enqueue(url, payload, (code, response) => { if (code == 200 || code == 201) { var msg = JsonConvert.DeserializeObject>(response); if (msg.ContainsKey("id")) { _messageId = msg["id"].ToString(); _config.SavedMessageId = _messageId; SaveConfig(); Puts($"[UWStatusBot] Posted status message. ID: {_messageId}"); } } else { PrintError($"[UWStatusBot] Failed to post message. Code: {code} | Response: {response}"); } }, this, Core.Libraries.RequestMethod.POST, GetHeaders()); } private void EditMessage(string payload) { string url = $"https://discord.com/api/v10/channels/{_config.ChannelId}/messages/{_messageId}"; webrequest.Enqueue(url, payload, (code, response) => { if (code == 200) { Puts($"[UWStatusBot] Status updated successfully."); } else if (code == 404) { PrintWarning("[UWStatusBot] Saved message not found. Posting new message."); _messageId = null; _config.SavedMessageId = ""; SaveConfig(); PostMessage(payload); } else { PrintError($"[UWStatusBot] Failed to edit message. Code: {code} | Response: {response}"); } }, this, Core.Libraries.RequestMethod.PATCH, GetHeaders()); } private Dictionary GetHeaders() { return new Dictionary { ["Authorization"] = $"Bot {_config.BotToken}", ["Content-Type"] = "application/json" }; } #endregion #region Helpers private string GetUptime() { var uptime = DateTime.UtcNow - _startTime; if (uptime.TotalDays >= 1) return $"{(int)uptime.TotalDays}d {uptime.Hours}h {uptime.Minutes}m"; if (uptime.TotalHours >= 1) return $"{uptime.Hours}h {uptime.Minutes}m"; return $"{uptime.Minutes}m"; } private string GetNextWipe() { // Manual override — if set and valid, use it if (!string.IsNullOrWhiteSpace(_config.ManualNextWipeDate)) { if (DateTime.TryParse(_config.ManualNextWipeDate, out DateTime manual)) return manual.ToString("MMM dd, yyyy"); PrintWarning($"[UWStatusBot] ManualNextWipeDate '{_config.ManualNextWipeDate}' is not a valid date. Falling back to auto."); } // Auto calculation from LastWipeDate + WipeIntervalDays if (!DateTime.TryParse(_config.LastWipeDate, out DateTime lastWipe)) { PrintWarning("[UWStatusBot] LastWipeDate is invalid. Defaulting to today."); lastWipe = DateTime.UtcNow.Date; } DateTime next = lastWipe.AddDays(_config.WipeIntervalDays); while (next < DateTime.UtcNow) next = next.AddDays(_config.WipeIntervalDays); return next.ToString("MMM dd, yyyy"); } #endregion } }