//#define DEBUG using System; using System.Collections; using System.Collections.Generic; using Newtonsoft.Json; using System.Linq; using System.Text.RegularExpressions; using Oxide.Core; using Oxide.Game.Rust.Cui; using UnityEngine; using UnityEngine.Networking; namespace Oxide.Plugins { [Info("UpdateChecker", "tofurahie#4144", "3.2.3")] internal class UpdateChecker : RustPlugin { #region Static private const string Layer = "UI_UpdateChecker"; private const string perm = "updatechecker.setup"; private Configuration _config; private bool isChecking; private string infoList = string.Empty; private IEnumerator coroutine; private Dictionary RegexPatterns = new Dictionary { ["Umod"] = "latest_release_version\":\"(.*?)\"", ["Codefling"] = "softwareVersion\": \"(.*?)\"", ["LoneDesign"] = "Version (.*?) | Updated (.*?)", ["ChaosCode"] = "u-muted\">(.*?)", }; #region Classes private class Configuration { [JsonProperty("Command to open UI")] public string Command = "ucsetup"; [JsonProperty("Discord WebHook")] public string DiscordWebHook = ""; [JsonProperty("Discord message ID")] public string MessageID = ""; [JsonProperty("Check Interval(In minutes)")] public int CheckMinutes = 60; [JsonProperty("Ignore not found plugins")] public bool IgnoreNotFound = false; [JsonProperty("Add a link to the plugin to be updated")] public bool UseURL = true; [JsonProperty("Enable auto search [chaoscode doesn't support auto search]", ObjectCreationHandling = ObjectCreationHandling.Replace)] public Dictionary AutoSearch = new Dictionary { ["Umod"] = true, ["Codefling"] = true, ["LoneDesign"] = true, }; [JsonProperty("List of plugins", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List ListOfPlugins = new List(); } private class PluginInfo { [JsonProperty("Plugin name (it's just a file name without .cs")] public string Name; [JsonProperty("Plugin version")] public string Version; [JsonProperty("Link to plugin [Umod | Codefling | LoneDesign | ChaosCode]")] public string Url; [JsonProperty("Ignore new version")] public bool NewVersionIgnore; public PluginInfo(string name, string version, string url = "", bool newVersionIgnore = false) { Name = name; Version = version; Url = url; NewVersionIgnore = newVersionIgnore; } } #region Config protected override void LoadConfig() { base.LoadConfig(); try { _config = Config.ReadObject(); 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 #endregion #endregion #region OxideHooks private void OnServerInitialized() { cmd.AddChatCommand(_config.Command, this, nameof(cmdChatblacklist)); permission.RegisterPermission(perm, this); CheckPlugins(); timer.Every(60 * _config.CheckMinutes, CheckPlugins); } private void Unload() { if (coroutine != null) ServerMgr.Instance.StopCoroutine(coroutine); foreach (var check in BasePlayer.activePlayerList) CuiHelper.DestroyUi(check, Layer + ".bg"); } #endregion #region Commands private void cmdChatblacklist(BasePlayer player, string command, string[] args) { if (!permission.UserHasPermission(player.UserIDString, perm)) { SendReply(player, "You don't have permissions to use this command"); return; } ShowUIBG(player); } [ConsoleCommand("checkupdates")] private void cmdConsolecheckupdates(ConsoleSystem.Arg arg) { if (arg == null || arg.Player() != null) return; if (isChecking) { PrintWarning("The plugin already scans other plugins for updates"); return; } CheckPlugins(); } [ConsoleCommand("UI_UC")] private void cmdConsoleUI_UC(ConsoleSystem.Arg arg) { if (!arg.HasArgs()) return; var player = arg.Player(); switch (arg.GetString(0)) { case "SEARCH": ShowUIPluginsList(player, 0, string.Join("", arg.Args.Skip(1))); break; case "SELECTPLUGIN": ShowUISetupPluginConfig(player, arg.GetInt(1)); break; case "CHANGENAME": _config.ListOfPlugins[arg.GetInt(1)].Name = arg.Args.Length == 2 ? "" : string.Join(" ", arg.Args.Skip(2)); SaveConfig(); break; case "CHANGEURL": _config.ListOfPlugins[arg.GetInt(1)].Url = arg.Args.Length == 2 ? "" : string.Join(" ", arg.Args.Skip(2)); SaveConfig(); break; case "CHANGEIGNORESTATUS": _config.ListOfPlugins[arg.GetInt(1)].NewVersionIgnore = !_config.ListOfPlugins[arg.GetInt(1)].NewVersionIgnore; ShowUISetupPluginConfig(player,arg.GetInt(1)); SaveConfig(); break; case "PAGE": ShowUIPluginsList(player,arg.GetInt(1), string.Join("", arg.Args.Skip(2))); break; } } #endregion #region Functions private List GetList(List list, int skipAmount, int takeAmount) { var result = new List(); var takeCounter = 0; foreach (var check in list) { if (skipAmount > 0) { skipAmount--; continue; } if (takeCounter >= takeAmount) break; result.Add(check); takeCounter++; } return result; } private IEnumerator SendMessageDiscord() { if (!string.IsNullOrEmpty(_config.DiscordWebHook)) { var field = new WWWForm(); field.AddField("content", infoList); if (infoList.Length > 1999) { for (var i = 0; i < infoList.Length; i += 1999) { var fieldI = new WWWForm(); var partMessage = infoList.Substring(i, Math.Min(1999, infoList.Length - i)); var startNewLinePos = partMessage.LastIndexOf("[", StringComparison.Ordinal); fieldI.AddField("content", partMessage.Substring(0, startNewLinePos)); if (startNewLinePos != 0) i -= 1999 - startNewLinePos; using (var request = UnityWebRequest.Post(_config.DiscordWebHook, fieldI)) yield return request.SendWebRequest(); yield return new WaitForSeconds(1.5f); } } else if (string.IsNullOrEmpty(_config.MessageID)) { using (var request = UnityWebRequest.Post(_config.DiscordWebHook, field)) yield return request.SendWebRequest(); } else { using (var request = new UnityWebRequest($"{_config.DiscordWebHook}/messages/{_config.MessageID}", "PATCH")) { request.uploadHandler = new UploadHandlerRaw(field.data); request.uploadHandler.contentType = "application/x-www-form-urlencoded"; yield return request.SendWebRequest(); } } } infoList = string.Empty; ServerMgr.Instance.StopCoroutine(CheckNewVersion()); isChecking = false; } private void CheckPlugins() { UpdatePluginList(); isChecking = true; coroutine = CheckNewVersion(); ServerMgr.Instance.StartCoroutine(coroutine); } private void UpdatePluginList() { ClearPluginList(); foreach (var entry in plugins.GetAll()) { if (entry.IsCorePlugin) continue; var pluginInfo = _config.ListOfPlugins.FirstOrDefault(x => string.Equals(x.Name, entry.Name, StringComparison.CurrentCultureIgnoreCase)); if (pluginInfo?.Name != null) { pluginInfo.Name = entry.Name; pluginInfo.Version = entry.Version.ToString(); if (pluginInfo.Name == "LightsOn") pluginInfo.Url = "https://codefling.com/plugins/lights-on"; continue; } _config.ListOfPlugins.Add(new PluginInfo(entry.Name, entry.Version.ToString())); } SaveConfig(); } private void ClearPluginList() { foreach (var cfgPlugin in _config.ListOfPlugins.ToArray()) { var isExist = false; foreach (var serverPlugin in plugins.GetAll()) { if (cfgPlugin.Name != serverPlugin.Name) continue; isExist = true; break; } if (!isExist) _config.ListOfPlugins.Remove(cfgPlugin); } } private IEnumerator CheckNewVersion() { var notFoundList = string.Empty; var updateList = string.Empty; for (var index = 0; index < _config.ListOfPlugins.Count; index++) { var check = _config.ListOfPlugins[index]; if (check.NewVersionIgnore) continue; if (check.Url == "Not found on: Umod Codefling LoneDesign") { notFoundList += $"\n[{check.Name}] - not found"; continue; } var name = Regex.Replace(check.Name, @"(? {version} {(_config.UseURL ? $"<{url}>" : "")}"; } infoList = updateList + (_config.IgnoreNotFound ? "" : ""); PrintWarning(infoList); ServerMgr.Instance.StopCoroutine(coroutine); coroutine = SendMessageDiscord(); ServerMgr.Instance.StartCoroutine(coroutine); SaveConfig(); } private VersionNumber ParseVersion(string v) { if (v == string.Empty) return new VersionNumber(1, 0, 0); var parts = v.Split('.'); int major; if (!int.TryParse(parts[0], out major)) { var majorPart = parts[0]; var majorString = string.Empty; for (int i = majorPart.Length - 1; i >= 0; i--) { if (!char.IsDigit(majorPart[i])) break; majorString = majorPart[i] + majorString; } if (string.IsNullOrEmpty(majorString)) return new VersionNumber(1, 0, 0); major = int.Parse(majorString); } if (parts.Length < 2) return new VersionNumber(major, 0, 0); int minor; if (!int.TryParse(parts[1], out minor)) minor = 0; if (parts.Length < 3) return new VersionNumber(major, minor, 0); int patch; if (!int.TryParse(parts[2], out patch)) { var patchPart = parts[2]; var patchString = string.Empty; for (int i = 0; i < patchPart.Length; i++) { if (!char.IsDigit(patchPart[i])) break; patchString += patchPart[i]; } if (string.IsNullOrEmpty(patchString)) return new VersionNumber(major, minor, 0); major = int.Parse(patchString); } return new VersionNumber(major, minor, patch); } private string GetRegex(string pluginUrl) { if (pluginUrl.Contains("umod.org")) return RegexPatterns["Umod"]; if (pluginUrl.Contains("codefling.com")) return RegexPatterns["Codefling"]; if (pluginUrl.Contains("lone.design")) return RegexPatterns["LoneDesign"]; return pluginUrl.Contains("chaoscode.io") ? RegexPatterns["ChaosCode"] : ""; } private string GetSiteName(string pluginUrl) { if (pluginUrl.Contains("umod.org")) return "Umod"; if (pluginUrl.Contains("codefling.com")) return "Codefling"; if (pluginUrl.Contains("lone.design")) return "LoneDesign"; return pluginUrl.Contains("chaoscode.io") ? "ChaosCode" : ""; } #endregion #region UI private void ShowUISetupPluginConfig(BasePlayer player, int index) { var container = new CuiElementContainer(); var plugin = _config.ListOfPlugins[index]; container.Add(new CuiElement { Parent = Layer + ".bg", Name = Layer + ".SetupPluginConfig", Components = { new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-305 130", OffsetMax = "305 250" }, new CuiImageComponent { Color = "0.1 0.1 0.1 0.95", Material = "assets/icons/iconmaterial.mat", }, }, }); container.Add(new CuiElement { Parent = Layer + ".SetupPluginConfig", Name = Layer + ".label", Components = { new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "10 -30", OffsetMax = "-10 0" }, new CuiTextComponent { Text = $"Name:", FontSize = 16, Color = "1 1 1 1", Align = TextAnchor.MiddleLeft, Font = "robotocondensed-regular.ttf", }, new CuiOutlineComponent { Distance = "-0.5 -0.5", Color = "0 0 0 1"}, }, }); container.Add(new CuiElement { Parent = Layer + ".SetupPluginConfig", Name = Layer + ".panel.input", Components = { new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "70 -27", OffsetMax = "-10 -3" }, new CuiImageComponent { Color = "0.76 0.76 0.76 0.79", Material = "assets/icons/iconmaterial.mat", }, }, }); container.Add(new CuiElement { Parent = Layer + ".panel.input", Name = Layer + ".input", Components = { new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 0", OffsetMax = "0 0" }, new CuiInputFieldComponent { Text = plugin.Name, Command = $"UI_UC CHANGENAME {index}", CharsLimit = 50, FontSize = 16, Color = "0 0 0 0.89", Align = TextAnchor.MiddleLeft, Font = "robotocondensed-regular.ttf", }, } }); container.Add(new CuiElement { Parent = Layer + ".SetupPluginConfig", Name = Layer + ".label", Components = { new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "10 -60", OffsetMax = "-10 -30" }, new CuiTextComponent { Text = $"Version:", FontSize = 16, Color = "1 1 1 1", Align = TextAnchor.MiddleLeft, Font = "robotocondensed-regular.ttf", }, new CuiOutlineComponent { Distance = "-0.5 -0.5", Color = "0 0 0 1"}, }, }); container.Add(new CuiElement { Parent = Layer + ".SetupPluginConfig", Name = Layer + ".panel.input", Components = { new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "70 -57", OffsetMax = "-10 -33" }, new CuiImageComponent { Color = "0.76 0.76 0.76 0.79", Material = "assets/icons/iconmaterial.mat", }, }, }); container.Add(new CuiElement { Parent = Layer + ".panel.input", Name = Layer + ".input", Components = { new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 0", OffsetMax = "0 0" }, new CuiInputFieldComponent { ReadOnly = true, Text = plugin.Version, Command = "", CharsLimit = 50, FontSize = 16, Color = "0 0 0 0.89", Align = TextAnchor.MiddleLeft, Font = "robotocondensed-regular.ttf", }, } }); container.Add(new CuiElement { Parent = Layer + ".SetupPluginConfig", Name = Layer + ".label", Components = { new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "10 -90", OffsetMax = "-10 -60" }, new CuiTextComponent { Text = $"URL:", FontSize = 16, Color = "1 1 1 1", Align = TextAnchor.MiddleLeft, Font = "robotocondensed-regular.ttf", }, new CuiOutlineComponent { Distance = "-0.5 -0.5", Color = "0 0 0 1"}, }, }); container.Add(new CuiElement { Parent = Layer + ".SetupPluginConfig", Name = Layer + ".panel.input", Components = { new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "70 -87", OffsetMax = "-10 -63" }, new CuiImageComponent { Color = "0.76 0.76 0.76 0.79", Material = "assets/icons/iconmaterial.mat", }, }, }); container.Add(new CuiElement { Parent = Layer + ".panel.input", Name = Layer + ".input", Components = { new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 0", OffsetMax = "0 0" }, new CuiInputFieldComponent { Text = plugin.Url, Command = $"UI_UC CHANGEURL {index}", CharsLimit = 250, FontSize = 16, Color = "0 0 0 0.89", Align = TextAnchor.MiddleLeft, Font = "robotocondensed-regular.ttf", }, } }); container.Add(new CuiButton { RectTransform = { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = "10 -120", OffsetMax = "-10 -90" }, Text = { Text = $"Ignore new versions: {plugin.NewVersionIgnore}", FontSize = 16, Color = "1 1 1 1", Align = TextAnchor.MiddleLeft, Font = "robotocondensed-regular.ttf", }, Button = { Command = $"UI_UC CHANGEIGNORESTATUS {index}", Color = "0 0 0 0", }, }, Layer + ".SetupPluginConfig", Layer + ".button"); CuiHelper.DestroyUi(player, Layer + ".SetupPluginConfig"); CuiHelper.AddUi(player, container); } private void ShowUISearch(BasePlayer player) { var container = new CuiElementContainer(); container.Add(new CuiElement { Parent = Layer + ".bg", Name = Layer + ".Search", Components = { new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-305 80", OffsetMax = "305 120" }, new CuiImageComponent { Color = "0.1 0.1 0.1 0.95", Material = "assets/icons/iconmaterial.mat", }, }, }); container.Add(new CuiElement { Parent = Layer + ".Search", Name = Layer + ".image", Components = { new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "0 1", OffsetMin = "5 0", OffsetMax = "45 0" }, new CuiImageComponent { Sprite = "assets/icons/examine.png"}, }, }); container.Add(new CuiElement { Parent = Layer + ".Search", Name = Layer + ".input", Components = { new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "55 0", OffsetMax = "-10 0" }, new CuiInputFieldComponent { Text = "", Command = $"UI_UC SEARCH", CharsLimit = 20, FontSize = 16, Color = "1 1 1 1", Align = TextAnchor.MiddleLeft, Font = "robotocondensed-regular.ttf", }, } }); CuiHelper.DestroyUi(player, Layer + ".Search"); CuiHelper.AddUi(player, container); } private void ShowUIPluginsList(BasePlayer player, int page = 0, string search = "") { var container = new CuiElementContainer(); var listSort = GetList(_config.ListOfPlugins, 40 * page, 40); var listCount = _config.ListOfPlugins.Count; container.Add(new CuiElement { Parent = Layer + ".bg", Name = Layer + ".panel.PluginsList", Components = { new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-305 -310", OffsetMax = "305 70" }, new CuiImageComponent { Color = "0.1 0.1 0.1 0.95", Material = "assets/icons/iconmaterial.mat", }, }, }); var posX = 10; var posY = 0; for (var index = 0; index < listSort.Count; index++) { var pluginInfo = listSort[index]; if (!string.IsNullOrEmpty(search) && !pluginInfo.Name.ToLower().Replace(" ", "").Contains(search.ToLower().Replace(" ", ""))) continue; container.Add(new CuiElement { Parent = Layer + ".panel.PluginsList", Name = Layer + ".label.PluginName" + posX + posY, Components = { new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "0 1", OffsetMin = $"{posX} {posY - 35}", OffsetMax = $"{posX + 110} {posY}" }, new CuiTextComponent { Text = pluginInfo.Name, FontSize = 14, Color =pluginInfo.NewVersionIgnore ? "0.73 0 0 0.75" : string.IsNullOrEmpty(GetRegex(pluginInfo.Url)) ? "0.95 0.75 0 0.75" : "0 0.73 0 0.75", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-bold.ttf", }, }, }); container.Add(new CuiButton { RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", }, Text = { Text = "", FontSize = 18, Color = "1 1 1 1", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-bold.ttf", }, Button = { Command = $"UI_UC SELECTPLUGIN {index + 40 * page}", Color = "0 0 0 0", }, }, Layer + ".label.PluginName" + posX + posY, Layer + ".button"); posX += 120; if (posX < 550) continue; posY -= 45; posX = 10; } #region Nav if (page != 0) { container.Add(new CuiElement { Parent = Layer + ".panel.PluginsList", Name = Layer + ".label", Components = { new CuiRectTransformComponent { AnchorMin = "0.5 0", AnchorMax = "0.5 0", OffsetMin = "-25 0", OffsetMax = "-10 44" }, new CuiTextComponent { Text = "«", FontSize = 28, Color = "0.85 0.84 0.82 1.00", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-regular.ttf", }, new CuiOutlineComponent {Distance = "-0.5 -0.5", Color = "0 0 0 1"}, }, }); container.Add(new CuiButton { RectTransform = { AnchorMin = "0.5 0", AnchorMax = "0.5 0", OffsetMin = "-25 0", OffsetMax = "-10 44" }, Text = { Text = "", FontSize = 28, Color = "0.85 0.84 0.82 1.00", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-regular.ttf", }, Button = { Command = $"UI_UC PAGE {page - 1} {search}", Color = "0 0 0 0", }, }, Layer + ".panel.PluginsList", Layer + ".button"); } if (listCount / 40 > 0) container.Add(new CuiElement { Parent = Layer + ".panel.PluginsList", Name = Layer + ".label", Components = { new CuiRectTransformComponent { AnchorMin = "0.5 0", AnchorMax = "0.5 0", OffsetMin = "-10 0", OffsetMax = "10 40" }, new CuiTextComponent { Text = (page + 1).ToString(), FontSize = 20, Color = "0.85 0.84 0.82 1.00", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-regular.ttf", }, new CuiOutlineComponent {Distance = "-0.5 -0.5", Color = "0 0 0 1"}, }, }); if (listCount / 40 > page) { container.Add(new CuiElement { Parent = Layer + ".panel.PluginsList", Name = Layer + ".label", Components = { new CuiRectTransformComponent { AnchorMin = "0.5 0", AnchorMax = "0.5 0", OffsetMin = "10 0", OffsetMax = "25 44" }, new CuiTextComponent { Text = "»", FontSize = 28, Color = "0.85 0.84 0.82 1.00", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-regular.ttf", }, new CuiOutlineComponent {Distance = "-0.5 -0.5", Color = "0 0 0 1"}, }, }); container.Add(new CuiButton { RectTransform = { AnchorMin = "0.5 0", AnchorMax = "0.5 0", OffsetMin = "10 0", OffsetMax = "25 44" }, Text = { Text = "", FontSize = 28, Color = "0.85 0.84 0.82 1.00", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-regular.ttf", }, Button = { Command = $"UI_UC PAGE {page + 1} {search}", Color = "0 0 0 0", }, }, Layer + ".panel.PluginsList", Layer + ".button"); } #endregion CuiHelper.DestroyUi(player, Layer + ".panel.PluginsList"); CuiHelper.AddUi(player, container); ShowUISetupPluginConfig(player, 0); } private void ShowUIBG(BasePlayer player) { var container = new CuiElementContainer(); container.Add(new CuiPanel { KeyboardEnabled = true, CursorEnabled = true, RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }, Image = { Color = "0.15 0.15 0.15 0.94", Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat" } }, "Overlay", Layer + ".bg"); container.Add(new CuiButton { RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" }, Text = { Text = "", FontSize = 16, Color = "1 1 1 1", Align = TextAnchor.MiddleCenter, Font = "robotocondensed-regular.ttf", }, Button = { Close = Layer + ".bg", Color = "0 0 0 0", }, }, Layer + ".bg", Layer + ".button"); CuiHelper.DestroyUi(player, Layer + ".bg"); CuiHelper.AddUi(player, container); ShowUIPluginsList(player); ShowUISearch(player); } #endregion } }