using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Linq; using Facepunch.Extend; using Newtonsoft.Json; using Oxide.Core; using Oxide.Core.Configuration; using Oxide.Core.Libraries; using Oxide.Core.Plugins; using UnityEngine; using UnityEngine.Networking; using Newtonsoft.Json.Linq; namespace Oxide.Plugins { [Info(_PluginName, _PluginAuthor, _PluginVersion)] [Description(_PluginDescription)] public class EasyVotePro : RustPlugin { // Plugin References [PluginReference] Plugin CustomStatusFramework; [PluginReference] Plugin ImageLibrary; [PluginReference] Plugin UINotify; [PluginReference] Plugin DiscordMessages; // Plugin Metadata private const string _PluginName = "EasyVotePro"; private const string _PluginAuthor = "BippyMiester"; private const string _PluginVersion = "3.0.3"; private const string _PluginDescription = "Voting System"; #region ChangeLog /* * 3.0.0 * Added Custom Status Frame Integration * * 3.0.1 * [+] Added permissions for notifications * easyvotepro.canseecsfvote * easyvotepro.canseecsfclaim * easyvotepro.canseenotifyvote * easyvotepro.canseenotifyclaim * * [/] Fixed notifyui enabled wasn't being checked * [-] Removed random debug message on claim status checked * * 3.0.2 * [/] Fixed NullReferenceException when CustomStatusFrames was unloaded * [+] Added check to see if the first key is contained in the rewards section * [+] Added check in OnPlayerSleepEnded to ensure player is a Steam User * [+] Added easyvotepro console command to display a help section * [+] Added check to see if the ID:KEY has been set prior to executing status or claim checks * * 3.0.3 * [/] Fixed NullReferenceException in Claim command when CustomStatusFrames was unloaded */ #endregion // Misc Variables private IEnumerator coroutine; public bool notifyUIVote = false; public bool notifyUIClaim = false; public bool CSFVote = false; public bool CSFClaim = false; // Permision Constants public const string permCanSeeCSFVote = "easyvotepro.canseecsfvote"; public const string permCanSeeCSFClaim = "easyvotepro.canseecsfclaim"; public const string permCanSeeNotifyVote = "easyvotepro.canseenotifyvote"; public const string permCanSeeNotifyClaim = "easyvotepro.canseenotifyclaim"; private void Init() { ConsoleLog($"{_PluginName} has been initialized..."); _config = Config.ReadObject(); LoadMessages(); RegisterAllPermissions(); } private void RegisterAllPermissions() { permission.RegisterPermission(permCanSeeCSFVote, this); permission.RegisterPermission(permCanSeeCSFClaim, this); permission.RegisterPermission(permCanSeeNotifyVote, this); permission.RegisterPermission(permCanSeeNotifyClaim, this); } private void OnServerInitialized() { // Check if CSF Plugin Exists if (_config.CSFSettings[ConfigDefaultKeys.CSFEnabled].ToBool()) { if (CustomStatusFramework == null) { ConsoleError("CustomStatusFramework is not loaded, but its notifications are enabled. Download from here: https://codefling.com/plugins/custom-status-framework"); } else { AddImageToLibrary(_config.CSFSettings[ConfigDefaultKeys.CSFClaimIcon].ToString(), _config.CSFSettings[ConfigDefaultKeys.CSFClaimStatusID]); AddImageToLibrary(_config.CSFSettings[ConfigDefaultKeys.CSFVoteIcon].ToString(), _config.CSFSettings[ConfigDefaultKeys.CSFVoteStatusID]); } } // Check if UINotify Plugin Exists if (_config.NotifyUISettings[ConfigDefaultKeys.NotifyUIEnabled].ToBool()) { if (UINotify == null) { ConsoleError("UINotify is not loaded, but its notifications are enabled. Download from here: https://umod.org/plugins/ui-notify"); } } // Check if the DiscordMessages Plugin Exists if (_config.Discord[ConfigDefaultKeys.DiscordEnabled].ToBool()) { if(DiscordMessages == null) { ConsoleError("Discord Webhook is enabled, but DiscordMessages is not present in your plugins folder. Download from here: https://umod.org/plugins/discord-messages"); } } } private void Loaded() { } private void Unload() { // Stop all coroutines if (coroutine != null) ServerMgr.Instance.StopCoroutine(coroutine); // Clear Players CSF Frames if (_config.CSFSettings[ConfigDefaultKeys.CSFEnabled].ToBool()) { foreach (BasePlayer player in BasePlayer.activePlayerList) { ClearCSFStatus(player); } } } #region HelperFunctions private void AddImageToLibrary(string url, string id) { _Debug("------------------------------"); _Debug("Method: AddImageToLibrary"); _Debug($"URL: {url}"); _Debug($"ID: {id}"); if (ImageLibrary == null) { ConsoleWarn("ImageLibrary is not loaded, download from here: https://umod.org/plugins/image-library"); return; } bool results = ImageLibrary.Call( "AddImage", url, id, 0ul, (Action) delegate() { _Debug("------------------------------"); _Debug("Method: AddImageToLibrary -> (Action) delegate"); ConsoleLog("Added Image Successfully!"); ConsoleLog($"Image ID: {ImageLibrary.Call("GetImage", id).ToString()}"); ConsoleLog($"ImageLibrary Name: {id.ToString()}"); } ); if (!results) { ConsoleError("Added Image Failed!!!"); } } private void HandleClaimWebRequestCallback(int code, string response, BasePlayer player, string url, string serverName, string site) { if (code != 200) { ConsoleError($"An error occurred while trying to check the claim status of the player {player.displayName}:{player.UserIDString}"); ConsoleWarn($"URL: {url}"); ConsoleWarn($"HTTP Code: {code} | Response: {response} | Server Name: {serverName}"); ConsoleWarn("This error could be due to a malformed or incorrect server token, id, or player id / username issue. Most likely its due to your server key being incorrect. Check that you server key is correct."); return; } _Debug("------------------------------"); _Debug("Method: HandleClaimWebRequestCallback"); _Debug($"Site: {site}"); _Debug($"Code: {code}"); _Debug($"Response: {response}"); _Debug($"URL: {url}"); _Debug($"ServerName: {serverName}"); _Debug($"Player Name: {player.displayName}"); _Debug($"Player SteamID: {player.UserIDString}"); _Debug("Web Request Type: Claim"); // Handle Verbose Debugging if (_config.DebugSettings[ConfigDefaultKeys.VerboseDebugEnabled].ToBool()) { _Debug($"Verbose Debug Enabled, Setting Response Code to: {_config.DebugSettings[ConfigDefaultKeys.ClaimAPIRepsonseCode]}"); response = _config.DebugSettings[ConfigDefaultKeys.ClaimAPIRepsonseCode]; } // Handle Every Reward if (response == "1") { HandleVoteCount(player); player.ChatMessage(_lang("ThankYou", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], DataFile[player.UserIDString].ToString(), site)); // Handle Discord Announcements if (_config.Discord[ConfigDefaultKeys.DiscordEnabled].ToBool()) { DiscordSendMessage(player, serverName, site); } // Handle Global Annonucements if (_config.NotificationSettings[ConfigDefaultKeys.GlobalChatAnnouncements] == "true") { foreach (var p in BasePlayer.activePlayerList) { p.ChatMessage(_lang("GlobalChatAnnouncements", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix],player.displayName, DataFile[player.UserIDString].ToString())); } } } else { player.ChatMessage(_lang("ClaimStatus", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], serverName, site, "Not Voted")); } } private void HandleStatusWebRequestCallback(int code, string response, BasePlayer player, string url, string serverName, string site) { // If the error code isn't a successful code if (code != 200 || response == null || code == 404) { ConsoleError($"An error occurred while trying to check the claim status of the player {player.displayName}:{player.UserIDString}"); ConsoleWarn($"URL: {url}"); ConsoleWarn($"HTTP Code: {code} | Response: {response} | Server Name: {serverName}"); ConsoleWarn("This error could be due to a malformed or incorrect server token, id, or player id / username issue. Most likely its due to your server key being incorrect. Check that you server key is correct."); return; } _Debug("------------------------------"); _Debug("Method: HandleClaimWebRequestCallback"); _Debug($"Site: {site}"); _Debug($"Code: {code}"); _Debug($"Response: {response}"); _Debug($"URL: {url}"); _Debug($"ServerName: {serverName}"); _Debug($"Player Name: {player.displayName}"); _Debug($"Player SteamID: {player.UserIDString}"); _Debug($"Web Request Type: Status/Check"); // Handle Verbose Debugging if (_config.DebugSettings[ConfigDefaultKeys.VerboseDebugEnabled].ToBool()) { _Debug($"Verbose Debug Enabled, Setting Response Code to: {_config.DebugSettings[ConfigDefaultKeys.CheckAPIResponseCode]}"); response = _config.DebugSettings[ConfigDefaultKeys.CheckAPIResponseCode]; } // Handle all other sites if (response == "0") { // Set CSF Vote Boolen CSFVote = true; // Set NotifyUI Vote Boolean notifyUIVote = true; player.ChatMessage(_lang("NoRewards", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], serverName, site)); } // Handle a player needs to claim a reward else if (response == "1") { // Set CSF Claim Boolean CSFClaim = true; // Set NotifyUI Claim Boolean notifyUIClaim = true; player.ChatMessage(_lang("RememberClaim", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], site)); } // Handle a player has already voted else if (response == "2") { player.ChatMessage(_lang("AlreadyVoted", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], site)); } } private void HandleVoteCount(BasePlayer player) { _Debug("------------------------------"); _Debug("Method: HandleVoteCount"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); // Grab the current vote count of the player int playerVoteCount = (int) DataFile[player.UserIDString]; _Debug($"Current VoteCount: {playerVoteCount}"); // Increase the players vote count by 1 playerVoteCount += 1; DataFile[player.UserIDString] = playerVoteCount; SaveDataFile(DataFile); _Debug($"Updated Vote Count: {playerVoteCount}"); // Handle giving rewards to the player based on cumulative boolean value if (_config.PluginSettings[ConfigDefaultKeys.RewardIsCumulative] == "true") { GiveCumulativeRewards(player, (int) DataFile[player.UserIDString]); } else { GiveNormalRewards(player, (int) DataFile[player.UserIDString]); } } private void GiveCumulativeRewards(BasePlayer player, int playerVoteCount) { _Debug("------------------------------"); _Debug("Method: GiveCumulativeRewards"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); // Handle the every reward GiveEveryReward(player); // Handle the first reward GiveFirstReward(player); // Handle all subsequent rewards foreach (KeyValuePair> rewards in _config.Rewards) { if (rewards.Key.ToInt() <= playerVoteCount) { GiveSubsequentReward(player, rewards.Value); } } } private void GiveNormalRewards(BasePlayer player, int playerVoteCount) { _Debug("------------------------------"); _Debug("Method: GiveNormalRewards"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); // Handle the every reward GiveEveryReward(player); // Handle the first reward if (playerVoteCount == 1) { GiveFirstReward(player); } // Handle all subsequent rewards foreach (KeyValuePair> rewards in _config.Rewards) { if (rewards.Key.ToInt() == playerVoteCount) { GiveSubsequentReward(player, rewards.Value); } } } private void GiveEveryReward(BasePlayer player) { _Debug("------------------------------"); _Debug("Method: GiveEveryReward"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); if(_config.Rewards.ContainsKey("@")) { foreach (string rewardCommand in _config.Rewards["@"]) { string command = ParseRewardCommand(player, rewardCommand); _Debug($"Reward Command: {command}"); rust.RunServerCommand(command); } } } private void GiveFirstReward(BasePlayer player) { _Debug("------------------------------"); _Debug("Method: GiveFirstReward"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); if(_config.Rewards.ContainsKey("first")) { foreach (string rewardCommand in _config.Rewards["first"]) { string command = ParseRewardCommand(player, rewardCommand); _Debug($"Reward Command: {command}"); rust.RunServerCommand(command); } } } private void GiveSubsequentReward(BasePlayer player, List rewardsList) { _Debug("------------------------------"); _Debug("Method: GiveSubsequentReward"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); _Debug($"Vote Count: {DataFile[player.UserIDString]}"); foreach (string rewardCommand in rewardsList) { string command = ParseRewardCommand(player, rewardCommand); _Debug($"Reward Command: {command}"); rust.RunServerCommand(command); } } private string ParseRewardCommand(BasePlayer player, string command) { return command .Replace("{playerid}", player.UserIDString) .Replace("{playername}", player.displayName); } private void CheckIfPlayerDataExists(BasePlayer player) { _Debug("------------------------------"); _Debug("Method: CheckIfPlayerDataExists"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); // If the player data entry is null then we need to create a new entry if (DataFile[player.UserIDString] == null) { _Debug($"{player.displayName} data does not exist. Creating new entry now."); DataFile[player.UserIDString] = 0; SaveDataFile(DataFile); _Debug($"{player.displayName} Data has been created."); } } private void ResetAllVoteData() { _Debug("------------------------------"); _Debug("Method: ResetAllVoteData"); foreach (KeyValuePair player in DataFile.ToList()) { DataFile[player.Key] = 0; _Debug($"Player {player.Key} vote count reset..."); } SaveDataFile(DataFile); } private void CheckVotingStatus(BasePlayer player) { _Debug("------------------------------"); _Debug("Method: CheckVotingStatus"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); // Clear Notify UI Booleans notifyUIClaim = false; notifyUIVote = false; // Clear CSF Booleans CSFVote = false; CSFClaim = false; // Clear any CSF Frames beforehand if (_config.CSFSettings[ConfigDefaultKeys.CSFEnabled].ToBool()) { ClearCSFStatus(player); } // Set Timeout for Web Request var timeout = 5000; // Check if the please wait message is enabled in the config if (_config.NotificationSettings[ConfigDefaultKeys.PleaseWaitMessage].ToBool()) { player.ChatMessage(_lang("PleaseWait", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix])); } // Loop through all the Servers foreach (KeyValuePair> ServersKVP in _config.Servers) { _Debug($"ServersKVP.Key: {ServersKVP.Key.ToString()}"); // Loop through all the ID's and Keys for each Voting Site foreach (KeyValuePair IDKeys in ServersKVP.Value) { _Debug($"IDKeys.Key: {IDKeys.Key.ToString().ToLower()}"); _Debug($"IDKeys.Value: {IDKeys.Value.ToString()}"); // Check if the ID and Key is set for the voting site if (IDKeys.Value.ToString() == "ID:KEY") { ConsoleWarn($"You need to set your ID and Key for the {IDKeys.Key.ToString()} voting site!"); continue; } // Check if the API key is present if (!_config.VoteSitesAPI.ContainsKey(IDKeys.Key)) { ConsoleWarn($"The voting website {IDKeys.Key} does not exist in the API section of the config!"); continue; } var APILink = _config.VoteSitesAPI[IDKeys.Key.ToString()][ConfigDefaultKeys.apiStatus]; _Debug($"Check Status API Link: {APILink.ToString()}"); var usernameAPIEnabled = _config.VoteSitesAPI[IDKeys.Key.ToString()][ConfigDefaultKeys.apiUsername]; _Debug($"API Username Enabled: {usernameAPIEnabled}"); string[] IDKey = IDKeys.Value.Split(':'); _Debug($"ID: {IDKey[0]}"); _Debug($"Key/Token: {IDKey[1]}"); string formattedURL = ""; if (usernameAPIEnabled.ToBool()) { formattedURL = string.Format(APILink, IDKey[1], player.displayName); } else if (IDKeys.Key.ToString().ToLower() == "rustservers.gg") { formattedURL = string.Format(APILink, IDKey[1], player.UserIDString, IDKey[0]); } else { formattedURL = string.Format(APILink, IDKey[1], player.UserIDString); } _Debug($"Formatted URL: {formattedURL}"); webrequest.Enqueue(formattedURL, null, (code, response) => HandleStatusWebRequestCallback(code, response, player, formattedURL, ServersKVP.Key.ToString(), IDKeys.Key.ToString()), this, RequestMethod.GET, null, timeout); _Debug("------------------------------"); } } // Handle NotifyUI Messages if (_config.NotifyUISettings[ConfigDefaultKeys.NotifyUIEnabled].ToBool()) { timer.Once(3f, () => { NotifyUIShowNotifications(player); }); } // Handle CSF Frames if (_config.CSFSettings[ConfigDefaultKeys.CSFEnabled].ToBool()) { timer.Once(3f, () => { CSFShowStatus(player); }); } } private void ClearCSFStatus(BasePlayer player) { // Clear Claim Status if (CustomStatusFramework.Call("HasStatus", player, _config.CSFSettings[ConfigDefaultKeys.CSFClaimStatusID])) { CustomStatusFramework.Call("ClearStatus", player, _config.CSFSettings[ConfigDefaultKeys.CSFClaimStatusID]); } // Clear Needs To Vote Status if (CustomStatusFramework.Call("HasStatus", player, _config.CSFSettings[ConfigDefaultKeys.CSFVoteStatusID])) { CustomStatusFramework.Call("ClearStatus", player, _config.CSFSettings[ConfigDefaultKeys.CSFVoteStatusID]); } } private void CSFShowStatus(BasePlayer player) { _Debug("------------------------------"); _Debug("Method: CSFShowStatus"); _Debug($"CSFVote: {CSFVote.ToString()}"); _Debug($"CSFClaim: {CSFClaim.ToString()}"); if (_config.CSFSettings[ConfigDefaultKeys.CSFEnabled].ToBool()) { CustomStatusFramework.Call("RefreshStatus", player, _config.CSFSettings[ConfigDefaultKeys.CSFClaimStatusID]); // Handle Vote Notification if (permission.UserHasPermission(player.UserIDString, permCanSeeCSFVote) && _config.CSFSettings[ConfigDefaultKeys.CSFVoteEnabled].ToBool()) { if (CSFVote) { NextFrame(() => { CustomStatusFramework.Call( "SetStatus", player, _config.CSFSettings[ConfigDefaultKeys.CSFVoteStatusID], _config.CSFSettings[ConfigDefaultKeys.CSFVoteBannerColor], _config.CSFSettings[ConfigDefaultKeys.CSFVoteMainText], _config.CSFSettings[ConfigDefaultKeys.CSFVoteTextColor], _config.CSFSettings[ConfigDefaultKeys.CSFVoteSubText], _config.CSFSettings[ConfigDefaultKeys.CSFVoteSubTextColor], _config.CSFSettings[ConfigDefaultKeys.CSFVoteStatusID], _config.CSFSettings[ConfigDefaultKeys.CSFVoteIconColor] ); }); } } // Handle Claim Rewards Notification if (permission.UserHasPermission(player.UserIDString, permCanSeeCSFClaim) && _config.CSFSettings[ConfigDefaultKeys.CSFClaimEnabled].ToBool()) { if (CSFClaim) { NextFrame(() => { CustomStatusFramework.Call( "SetStatus", player, _config.CSFSettings[ConfigDefaultKeys.CSFClaimStatusID], _config.CSFSettings[ConfigDefaultKeys.CSFClaimBannerColor], _config.CSFSettings[ConfigDefaultKeys.CSFClaimMainText], _config.CSFSettings[ConfigDefaultKeys.CSFClaimTextColor], _config.CSFSettings[ConfigDefaultKeys.CSFClaimSubText], _config.CSFSettings[ConfigDefaultKeys.CSFClaimSubTextColor], _config.CSFSettings[ConfigDefaultKeys.CSFClaimStatusID], _config.CSFSettings[ConfigDefaultKeys.CSFClaimIconColor] ); }); } } } } private void NotifyUIShowNotifications(BasePlayer player) { _Debug("------------------------------"); _Debug("Method: NotifyUIShowNotifications"); _Debug($"NotifyUIVote: {notifyUIVote.ToString()}"); _Debug($"notifyUIClaim: {notifyUIClaim.ToString()}"); if (_config.NotifyUISettings[ConfigDefaultKeys.NotifyUIEnabled].ToBool()) { // Handle Vote Notification if (permission.UserHasPermission(player.UserIDString, permCanSeeNotifyVote)) { if (notifyUIVote) { NextFrame(() => { _Debug(_config.NotifyUISettings[ConfigDefaultKeys.NotifyUIVoteType]); _Debug(_config.NotifyUISettings[ConfigDefaultKeys.NotifyUIVoteText]); UINotify.Call( "SendNotify", player, _config.NotifyUISettings[ConfigDefaultKeys.NotifyUIVoteType].ToInt(), _config.NotifyUISettings[ConfigDefaultKeys.NotifyUIVoteText] ); }); } } // Handle Claim Rewards Notification if (permission.UserHasPermission(player.UserIDString, permCanSeeNotifyClaim)) { if (notifyUIClaim) { NextFrame(() => { _Debug(_config.NotifyUISettings[ConfigDefaultKeys.NotifyUIClaimType]); _Debug(_config.NotifyUISettings[ConfigDefaultKeys.NotifyUIClaimText]); UINotify.Call( "SendNotify", player, _config.NotifyUISettings[ConfigDefaultKeys.NotifyUIClaimType].ToInt(), _config.NotifyUISettings[ConfigDefaultKeys.NotifyUIClaimText] ); }); } } } } #endregion #region Hooks private void OnPlayerConnected(BasePlayer player) { _Debug("------------------------------"); _Debug("Method: OnPlayerConnected"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); // Check if the player data is present in the data file CheckIfPlayerDataExists(player); // Check voting status if (!_config.NotificationSettings[ConfigDefaultKeys.OnPlayerSleepEnded].ToBool() && _config.NotificationSettings[ConfigDefaultKeys.OnPlayerConnected].ToBool()) { // Check Vote Site API's CheckVotingStatus(player); } } private void OnNewSave(string filename) { _Debug("------------------------------"); _Debug("Method: OnNewSave"); ConsoleLog("New map data detected!"); if (_config.PluginSettings[ConfigDefaultKeys.ClearRewardsOnWipe] == "true") { _Debug("Wiping all votes from data file"); ResetAllVoteData(); } } private void OnPlayerSleepEnded(BasePlayer player) { _Debug("------------------------------"); _Debug("Method: OnPlayerSleepEnded"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); // Check if the player is a steam id player if(!player.userID.IsSteamId()) { return; } // Check the voting status when the player wakes up if (_config.NotificationSettings[ConfigDefaultKeys.OnPlayerSleepEnded].ToBool()) { // Check Vote Site API's CheckVotingStatus(player); } } #endregion #region ChatCommands [ChatCommand("rewardlist")] private void RewardListChatCommand(BasePlayer player, string command, string[] args) { player.ChatMessage(_config.PluginSettings[ConfigDefaultKeys.Prefix] + "The following rewards are given for voting!"); foreach (KeyValuePair kvp in _config.RewardDescriptions) { player.ChatMessage(kvp.Value); } } [ChatCommand("vote")] private void VoteChatCommand(BasePlayer player, string command, string[] args) { player.ChatMessage(_lang("VoteList", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix])); foreach (KeyValuePair> kvp in _config.VoteSitesAPI) { foreach (KeyValuePair> serverskvp in _config.Servers) { foreach (KeyValuePair serveridkeys in serverskvp.Value) { if (Equals(kvp.Key, serveridkeys.Key)) { string[] parts = serveridkeys.Value.Split(':'); player.ChatMessage(serverskvp.Key + ": " + string.Format(kvp.Value[ConfigDefaultKeys.apiLink], parts[0])); } } } } player.ChatMessage(_lang("EarnReward")); } [ChatCommand("claim")] private void ClaimChatCommand(BasePlayer player, string command, string[] args) { _Debug("------------------------------"); _Debug("Method: ClaimChatCommand"); _Debug($"Player: {player.displayName}/{player.UserIDString}"); // Check if the player data is present in the data file CheckIfPlayerDataExists(player); // Clear CSF Frame if(CustomStatusFramework != null && _config.CSFSettings[ConfigDefaultKeys.CSFEnabled].ToBool()) { ClearCSFStatus(player); } // Set Timeout for Web Request var timeout = 5000; // Check if the please wait message is enabled in the config if (_config.NotificationSettings[ConfigDefaultKeys.PleaseWaitMessage].ToBool()) { player.ChatMessage(_lang("PleaseWait", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix])); } // Loop through all the Servers foreach (KeyValuePair> ServersKVP in _config.Servers) { _Debug($"ServersKVP.Key: {ServersKVP.Key.ToString()}"); // Loop through all the ID's and Keys for each Voting Site foreach (KeyValuePair IDKeys in ServersKVP.Value) { _Debug($"IDKeys.Key: {IDKeys.Key.ToString().ToLower()}"); _Debug($"IDKeys.Value: {IDKeys.Value.ToString()}"); // Check if the ID and Key is set for the voting site if (IDKeys.Value.ToString() == "ID:KEY") { ConsoleWarn($"You need to set your ID and Key for the {IDKeys.Key.ToString()} voting site!"); continue; } // Check if the API key is present if (!_config.VoteSitesAPI.ContainsKey(IDKeys.Key)) { ConsoleWarn($"The voting website {IDKeys.Key} does not exist in the API section of the config!"); continue; } var APILink = _config.VoteSitesAPI[IDKeys.Key.ToString()][ConfigDefaultKeys.apiClaim]; _Debug($"Check Status API Link: {APILink.ToString()}"); var usernameAPIEnabled = _config.VoteSitesAPI[IDKeys.Key.ToString()][ConfigDefaultKeys.apiUsername]; _Debug($"API Username Enabled: {usernameAPIEnabled}"); string[] IDKey = IDKeys.Value.Split(':'); _Debug($"ID: {IDKey[0]}"); _Debug($"Key/Token: {IDKey[1]}"); string formattedURL = ""; if (usernameAPIEnabled.ToBool()) { formattedURL = string.Format(APILink, IDKey[1], player.displayName); } else if (IDKeys.Key.ToString().ToLower() == "rustservers.gg") { formattedURL = string.Format(APILink, IDKey[1], player.UserIDString, IDKey[0]); } else { formattedURL = string.Format(APILink, IDKey[1], player.UserIDString); } _Debug($"Formatted URL: {formattedURL}"); webrequest.Enqueue(formattedURL, null, (code, response) => HandleClaimWebRequestCallback(code, response, player, formattedURL, ServersKVP.Key.ToString(), IDKeys.Key.ToString()), this, RequestMethod.GET, null, timeout); _Debug("------------------------------"); } } // Wait until all web requests are done and then send a message timer.Once(5f, () => { player.ChatMessage(_lang("ClaimReward", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix])); }); } #endregion #region ConsoleCommands [ConsoleCommand("clearvote")] private void ClearPlayerVoteCountConsoleCommand(ConsoleSystem.Arg arg) { // Check if an argument is even passed if (!arg.HasArgs(1)) { ConsoleError("Command clearvote usage: clearvote steamid|username"); return; } // Get the player based off the argument passed BasePlayer player = arg.GetPlayer(0); if (player == null) { ConsoleError($"Failed to find player with ID/Username/IP of: {arg.GetString(0)}"); return; } // Update the player vote count in the data file DataFile[player.UserIDString] = 0; SaveDataFile(DataFile); ConsoleLog($"{player.displayName}/{player.UserIDString} vote count has been reset to 0"); } [ConsoleCommand("checkvote")] private void CheckPlayerVoteCountConsoleCommand(ConsoleSystem.Arg arg) { // Check if an argument is even passed if (!arg.HasArgs(1)) { ConsoleError("Command checkvote usage: checkvote steamid|username"); return; } // Get the player based off the argument passed BasePlayer player = arg.GetPlayer(0); if (player == null) { ConsoleError($"Failed to find player with ID/Username/IP of: {arg.GetString(0)}"); return; } ConsoleLog($"Player {player.displayName}/{player.UserIDString} has {getPlayerVotes(player.UserIDString)} votes total"); } [ConsoleCommand("setvote")] private void SetPlayerVoteCountConsoleCommand(ConsoleSystem.Arg arg) { // Check if an argument is even passed if (!arg.HasArgs(2)) { ConsoleError("Command setvote usage: setvote steamid|username numberOfVotes"); return; } // Get the player based off the argument passed BasePlayer player = arg.GetPlayer(0); if (player == null) { ConsoleError($"Failed to find player with ID/Username/IP of: {arg.GetString(0)}"); return; } DataFile[player.UserIDString] = arg.GetInt(1); SaveDataFile(DataFile); ConsoleLog($"Player {player.displayName}/{player.UserIDString} vote count has been updated to {arg.GetString(1)}"); } [ConsoleCommand("resetvotedata")] private void ResetAllVoteDataConsoleCommand(ConsoleSystem.Arg arg) { if (arg.HasArgs(1)) { ConsoleError("Command resetvotedata usage: This command has no arguments. Run the command again with no arguments"); return; } ResetAllVoteData(); } [ConsoleCommand("clearcsf")] private void ClearCSFStatusConsoleCommand(ConsoleSystem.Arg arg) { if (!arg.HasArgs(1)) { ConsoleError("Command clearcsf usage: clearcsf steamid|username"); return; } // Get the player based off the argument passed BasePlayer player = arg.GetPlayer(0); if (player == null) { ConsoleError($"Failed to find player with ID/Username/IP of: {arg.GetString(0)}"); return; } ClearCSFStatus(player); } [ConsoleCommand("easyvotepro")] private void EasyVoteProHelpConsoleCommand(ConsoleSystem.Arg arg) { ConsoleLog("------------------------"); ConsoleLog("EasyVotePro Help Section"); ConsoleLog("------------------------"); ConsoleLog("clearvote - Clears a players vote count to 0"); ConsoleLog("checkvote - Check the players current vote count"); ConsoleLog("setvote - Set the players vote count to a specific number"); ConsoleLog("resetvotedata - Resets all voting data for every player"); ConsoleLog("------------------------"); } #endregion #region ConsoleHelpers protected void ConsoleLog(object message) { Puts(message?.ToString()); } protected void ConsoleError(string message) { if (Convert.ToBoolean(_config.PluginSettings[ConfigDefaultKeys.LogEnabled])) LogToFile("EasyVote2", $"ERROR: {message}", this); Debug.LogError($"ERROR: " + message); } protected void ConsoleWarn(string message) { if (Convert.ToBoolean(_config.PluginSettings[ConfigDefaultKeys.LogEnabled])) LogToFile("EasyVote2", $"WARNING: {message}", this); Debug.LogWarning($"WARNING: " + message); } protected void _Debug(string message, string arg = null) { if (_config.DebugSettings[ConfigDefaultKeys.DebugEnabled] == "true") { if (Convert.ToBoolean(_config.PluginSettings[ConfigDefaultKeys.LogEnabled])) LogToFile("EasyVote2", $"DEBUG: {message}", this); Puts($"DEBUG: {message}"); if (arg != null) { Puts($"DEBUG ARG: {arg}"); } } } #endregion #region APIHooks [HookMethod("getPlayerVotes")] public int getPlayerVotes(string steamID) { if (DataFile[steamID] == null) { _Debug("getPlayerVotes(): Player data doesn't exist"); return 0; } return (int) DataFile[steamID]; } #endregion #region Localization string _lang(string key, string id = null, params object[] args) => string.Format(lang.GetMessage(key, this, id), args); private void LoadMessages() { lang.RegisterMessages(new Dictionary { ["NoPermission"] = "You do not have permission to use this command!", ["ClaimStatus"] = "{0} {1}\nChecked {2}, Status: {3}", ["ClaimReward"] = "{0} If you voted, and the votes went through, then you just received your vote reward(s). Enjoy!", ["PleaseWait"] = "{0} Checking all the VoteSites API's... Please be patient as this can take some time...", ["VoteList"] = "{0} You can vote for our server at the following links:", ["EarnReward"] = "When you have voted, type /claim to claim your reward(s)!", ["ThankYou"] = "{0} Thank you for voting! You have voted {1} time(s) Here is your reward for: {2}", ["NoRewards"] = "{0} You haven't voted for {1} on {2} yet! Type /vote to get started!", ["RememberClaim"] = "{0} {1} is reporting that you have an unclaimed reward! Use /claim to claim your reward!\n You have to claim your reward within 24h! Otherwise it will be gone!", ["GlobalChatAnnouncements"] = "{0} {1} has voted {2} time(s) and just received their rewards. Find out where you can vote by typing /vote\nTo see a list of available rewards type /rewardlist", ["AlreadyVoted"] = "{0} {1} reports you have already voted! Vote again later.", ["DiscordWebhookMessage"] = "{0} has voted for {1} on {2} and got some rewards! Type /rewardlist in game to find out what you can get when you vote for us!" }, this); } #endregion #region Config private PluginConfig _config; protected override void LoadDefaultConfig() { _config = new PluginConfig(); _config.DebugSettings = new Dictionary { {ConfigDefaultKeys.DebugEnabled, "false"}, {ConfigDefaultKeys.VerboseDebugEnabled, "false"}, {ConfigDefaultKeys.CheckAPIResponseCode, "0"}, {ConfigDefaultKeys.ClaimAPIRepsonseCode, "0"} }; _config.PluginSettings = new Dictionary { {ConfigDefaultKeys.LogEnabled, "true"}, {ConfigDefaultKeys.ClearRewardsOnWipe, "true"}, {ConfigDefaultKeys.RewardIsCumulative, "false"}, {ConfigDefaultKeys.Prefix, "[EasyVote] "}, }; _config.NotificationSettings = new Dictionary { {ConfigDefaultKeys.GlobalChatAnnouncements, "true"}, {ConfigDefaultKeys.PleaseWaitMessage, "true"}, {ConfigDefaultKeys.OnPlayerSleepEnded, "false"}, {ConfigDefaultKeys.OnPlayerConnected, "true"}, }; _config.CSFSettings = new Dictionary { {ConfigDefaultKeys.CSFEnabled, "false"}, // Claim/Rewards Status Settings {ConfigDefaultKeys.CSFClaimEnabled, "true"}, {ConfigDefaultKeys.CSFClaimBannerColor, "0.18 0.8 0.44 1"}, {ConfigDefaultKeys.CSFClaimIconColor, "0.93 0.94 0.95 1"}, {ConfigDefaultKeys.CSFClaimIcon, "https://i.imgur.com/bkoPUv4.png"}, {ConfigDefaultKeys.CSFClaimTextColor, "0.93 0.94 0.95 1"}, {ConfigDefaultKeys.CSFClaimMainText, "Claim Your Rewards!"}, {ConfigDefaultKeys.CSFClaimSubTextColor, "0.93 0.94 0.95 1"}, {ConfigDefaultKeys.CSFClaimSubText, "/claim"}, {ConfigDefaultKeys.CSFClaimStatusID, "hasrewards"}, // Vote Status Settings {ConfigDefaultKeys.CSFVoteEnabled, "true"}, {ConfigDefaultKeys.CSFVoteBannerColor, "0.91 0.3 0.24 1"}, {ConfigDefaultKeys.CSFVoteIconColor, "0.93 0.94 0.95 1"}, {ConfigDefaultKeys.CSFVoteIcon, "https://i.imgur.com/XVdKgGf.png"}, {ConfigDefaultKeys.CSFVoteTextColor, "0.93 0.94 0.95 1"}, {ConfigDefaultKeys.CSFVoteMainText, "Vote For Us!"}, {ConfigDefaultKeys.CSFVoteSubTextColor, "0.93 0.94 0.95 1"}, {ConfigDefaultKeys.CSFVoteSubText, "/vote"}, {ConfigDefaultKeys.CSFVoteStatusID, "norewards"}, }; _config.NotifyUISettings = new Dictionary { { ConfigDefaultKeys.NotifyUIEnabled, "false" }, { ConfigDefaultKeys.NotifyUIClaimText, "You have unclaimed rewards! Type /claim now!" }, { ConfigDefaultKeys.NotifyUIClaimType, "0" }, { ConfigDefaultKeys.NotifyUIVoteText, "Support our server by voting! Type /vote now!" }, { ConfigDefaultKeys.NotifyUIVoteType, "0" } }; _config.Discord = new Dictionary { {ConfigDefaultKeys.discordWebhookURL, "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks"}, {ConfigDefaultKeys.DiscordEnabled, "false"}, {ConfigDefaultKeys.discordTitle, "A player has just voted for us!"}, {ConfigDefaultKeys.discordEmbedColor, "3329330"}, {ConfigDefaultKeys.discordNotifyHere, "false"}, {ConfigDefaultKeys.discordNotifyEveryone, "false"}, {ConfigDefaultKeys.discordAdditionalMessage, "Don't forget to vote! Type /vote in chat to get started!"} }; _config.Rewards = new Dictionary> { { "@", new List() { "giveto {playerid} supply.signal 1" } }, { "first", new List() { "giveto {playerid} stones 10000", "sr add {playerid} 10000" } }, { "3", new List() { "addgroup {playerid} vip 7d" } }, { "6", new List() { "grantperm {playerid} plugin.test 1d" } }, { "10", new List() { "zl.lvl {playerid} * 2" } } }; _config.RewardDescriptions = new Dictionary { { "@", "Every Vote: 1 Supply Signal" }, { "first", "First Vote: 10000 Stones, 10000 RP" }, { "3", "3rd Vote: 7 days of VIP rank" }, { "6", "6th Vote: 1 day of plugin.test permission" }, { "10", "10th Vote: 2 zLevels in Every Category" } }; _config.Servers = new Dictionary> { { "ServerName1", new Dictionary() { { "Rust-Servers.net", "ID:KEY" },{ "Rustservers.gg", "ID:KEY" }, { "BestServers.com", "ID:KEY" }, { "Top-Games.net", "ID:TOKEN" }, { "TrackyServer.com", "ID:TOKEN" }, { "ServerTilt.com", "ID:TOKEN" } } }, { "ServerName2", new Dictionary() { { "Rust-Servers.net", "ID:KEY" },{ "Rustservers.gg", "ID:KEY" }, { "BestServers.com", "ID:KEY" }, { "Top-Games.net", "ID:TOKEN" }, { "TrackyServer.com", "ID:TOKEN" }, { "ServerTilt.com", "ID:TOKEN" } } } }; _config.VoteSitesAPI = new Dictionary> { { "Rust-Servers.net", new Dictionary() { { ConfigDefaultKeys.apiClaim, "http://rust-servers.net/api/?action=custom&object=plugin&element=reward&key={0}&steamid={1}" }, { ConfigDefaultKeys.apiStatus, "http://rust-servers.net/api/?object=votes&element=claim&key={0}&steamid={1}" }, { ConfigDefaultKeys.apiLink, "http://rust-servers.net/server/{0}" }, { ConfigDefaultKeys.apiUsername, "false"} } }, { "Rustservers.gg", new Dictionary() { { ConfigDefaultKeys.apiClaim, "https://rustservers.gg/vote-api.php?action=claim&key={0}&server={2}&steamid={1}" }, { ConfigDefaultKeys.apiStatus, "https://rustservers.gg/vote-api.php?action=status&key={0}&server={2}&steamid={1}" }, { ConfigDefaultKeys.apiLink, "https://rustservers.gg/server/{0}" }, { ConfigDefaultKeys.apiUsername, "false"} } }, { "BestServers.com", new Dictionary() { { ConfigDefaultKeys.apiClaim, "https://bestservers.com/api/vote.php?action=claim&key={0}&steamid={1}" }, { ConfigDefaultKeys.apiStatus, "https://bestservers.com/api/vote.php?action=status&key={0}&steamid={1}" }, { ConfigDefaultKeys.apiLink, "https://bestservers.com/server/{0}" }, { ConfigDefaultKeys.apiUsername, "false"} } }, { "Top-Games.net", new Dictionary() { { ConfigDefaultKeys.apiClaim, "https://api.top-games.net/v1/votes/claim-username?server_token={0}&playername={1}" }, { ConfigDefaultKeys.apiStatus, "https://api.top-games.net/v1/votes/check?server_token={0}&playername={1}" }, { ConfigDefaultKeys.apiLink, "https://top-games.net/rust/{0}" }, { ConfigDefaultKeys.apiUsername, "true"} } }, { "TrackyServer.com", new Dictionary() { { ConfigDefaultKeys.apiClaim, "https://api.trackyserver.com/vote/?action=claim&key={0}&steamid={1}" }, { ConfigDefaultKeys.apiStatus, "https://api.trackyserver.com/vote/?action=status&key={0}&steamid={1}" }, { ConfigDefaultKeys.apiLink, "https://trackyserver.com/server/{0}" }, { ConfigDefaultKeys.apiUsername, "false"} } }, { "ServerTilt.com", new Dictionary() { { ConfigDefaultKeys.apiClaim, "https://www.servertilt.com/api-vote.php?action=claim&key={0}&username={1}" }, { ConfigDefaultKeys.apiStatus, "https://www.servertilt.com/api-vote.php?action=status&key={0}&username={1}" }, { ConfigDefaultKeys.apiLink, "https://www.servertilt.com/{0}" }, { ConfigDefaultKeys.apiUsername, "true"} } }, }; SaveConfig(); ConsoleWarn("A new configuration file has been generated!"); } protected override void LoadConfig() { base.LoadConfig(); try { _config = Config.ReadObject(); if (_config == null) { LoadDefaultConfig(); //SaveConfig(); } } catch { ConsoleError("The configuration file is corrupted. Please delete the config file and reload the plugin."); LoadDefaultConfig(); SaveConfig(); } } protected override void SaveConfig() => Config.WriteObject(_config); class ConfigDefaultKeys { // API Stuff public const string apiClaim = "API Claim Reward (GET URL)"; public const string apiStatus = "API Vote status (GET URL)"; public const string apiLink = "Vote link (URL)"; public const string apiUsername = "Site Uses Username Instead of Player Steam ID?"; // Discord Webhook public const string discordTitle = "Discord Title"; public const string discordWebhookURL = "Discord webhook (URL)"; public const string DiscordEnabled = "DiscordMessage Enabled (true / false)"; public const string discordEmbedColor = "Discord Embed Color (Integer String)"; public const string discordNotifyHere = "Notify @here when webhook is executed?"; public const string discordNotifyEveryone = "Notify @everyone when webhook is executed?"; public const string discordAdditionalMessage = "Some additional message to put inside of Discord embed"; // Plugin Settings public const string Prefix = "Chat Prefix"; public const string LogEnabled = "Enable logging => logs/EasyVote (true / false)"; public const string RewardIsCumulative = "Vote rewards cumulative (true / false)"; public const string ClearRewardsOnWipe = "Wipe Rewards Count on Map Wipe?"; // Notification Settings public const string GlobalChatAnnouncements = "Globally announcment in chat when player voted (true / false)"; public const string PleaseWaitMessage = "Enable the 'Please Wait' message when checking voting status?"; public const string OnPlayerSleepEnded = "Notify player of rewards when they stop sleeping?"; public const string OnPlayerConnected = "Notify player of rewards when they connect to the server?"; // Debug Settings public const string DebugEnabled = "Debug Enabled?"; public const string VerboseDebugEnabled = "Enable Verbose Debugging? (READ DOCUMENTATION FIRST!)"; public const string CheckAPIResponseCode = "Set Check API Response Code (0 = Not found, 1 = Has voted and not claimed, 2 = Has voted and claimed)"; public const string ClaimAPIRepsonseCode = "Set Claim API Response Code (0 = Not found, 1 = Has voted and not claimed. The vote will now be set as claimed., 2 = Has voted and claimed"; // CSF Settings public const string CSFEnabled = "Custom Status Frame Enabled?"; // CSF Claim Banner Settings public const string CSFClaimEnabled = "Enable Rewards Need To Be Claimed Status?"; public const string CSFClaimBannerColor = "Claim Rewards Banner Color"; public const string CSFClaimIconColor = "Claim Rewards Icon Color"; public const string CSFClaimIcon = "Claim Rewards Icon"; public const string CSFClaimTextColor = "Claim Rewards Main Text Color"; public const string CSFClaimMainText = "Main Text of Claim Rewards Banner"; public const string CSFClaimSubTextColor = "Claim Rewards Sub-Text Color"; public const string CSFClaimSubText = "Sub-Text of Claim Rewards Banner"; public const string CSFClaimStatusID = "Claim Rewards CSF ID (DO NOT CHANGE!)"; // CSF Vote Banner Settings public const string CSFVoteEnabled = "Enable Needs To Vote Status?"; public const string CSFVoteBannerColor = "Needs To Vote Banner Color"; public const string CSFVoteIconColor = "Needs to Vote Icon Color"; public const string CSFVoteIcon = "Needs to Vote Icon"; public const string CSFVoteTextColor = "Needs to Vote Main Text Color"; public const string CSFVoteMainText = "Main Text of Needs To Vote Banner"; public const string CSFVoteSubTextColor = "Needs to Vote Sub-Text Color"; public const string CSFVoteSubText = "Sub-Text of Needs To Vote Banner"; public const string CSFVoteStatusID = "Needs to Vote CSF ID (DO NOT CHANGE!)"; // NotifyUI Settings public const string NotifyUIEnabled = "Notify UI Enabled?"; public const string NotifyUIClaimText = "Message to show when a player has unclaimed rewards"; public const string NotifyUIClaimType = "Type of message to show when a player has unclaimed rewards"; public const string NotifyUIVoteText = "Message to show when a player needs to vote"; public const string NotifyUIVoteType = "Type of message to show when a player needs to vote"; } private class PluginConfig { [JsonProperty(PropertyName = "Debug Settings")] public Dictionary DebugSettings; [JsonProperty(PropertyName = "Plugin Settings")] public Dictionary PluginSettings; [JsonProperty(PropertyName = "Notification Settings")] public Dictionary NotificationSettings; [JsonProperty(PropertyName = "Custom Status Frame Settings")] public Dictionary CSFSettings; [JsonProperty(PropertyName = "Notify UI Settings")] public Dictionary NotifyUISettings; [JsonProperty(PropertyName = "Discord")] public Dictionary Discord; [JsonProperty(PropertyName = "Rewards")] public Dictionary> Rewards; [JsonProperty(PropertyName = "Reward Descriptions")] public Dictionary RewardDescriptions; [JsonProperty(PropertyName = "Server Voting IDs and Keys")] public Dictionary> Servers; [JsonProperty(PropertyName = "Voting Sites API Information")] public Dictionary> VoteSitesAPI; } #endregion #region Data protected internal static DynamicConfigFile DataFile = Interface.Oxide.DataFileSystem.GetDatafile(_PluginName); private void SaveDataFile(DynamicConfigFile data) { data.Save(); _Debug("Data file has been updated."); } #endregion #region DiscordMessages private void DiscordSendMessage(BasePlayer player, string serverName, string site) { _Debug("------------------------------"); _Debug("Method: DiscordSendMessage"); _Debug($"Notify Here: {_config.Discord[ConfigDefaultKeys.discordNotifyHere]}"); _Debug($"Notify Everyone: {_config.Discord[ConfigDefaultKeys.discordNotifyEveryone]}"); _Debug($"Player: {player.displayName}"); // Check if the discord webhook is default or null/empty if (_config.Discord[ConfigDefaultKeys.discordWebhookURL] != "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks" || !string.IsNullOrEmpty(_config.Discord[ConfigDefaultKeys.discordWebhookURL])) { var fields = Facepunch.Pool.GetList(); string json; fields.Add(new Fields("", _config.Discord[ConfigDefaultKeys.discordAdditionalMessage], false)); fields.Add(new Fields("Voter", $"[{player.displayName}](https://steamcommunity.com/profiles/{player.userID})", true)); fields.Add(new Fields("Vote Count", DataFile[player.UserIDString].ToString(), true)); fields.Add(new Fields("Server", (serverName != null ? serverName : "[ UNKNOWN ]"), false)); fields.Add(new Fields("Site", site, true)); json = JsonConvert.SerializeObject(fields); Facepunch.Pool.FreeList(ref fields); // Handle Discord Tagging var discordNotification = "@here"; if(!_config.Discord[ConfigDefaultKeys.discordNotifyHere].ToBool()) { discordNotification = null; } if(_config.Discord[ConfigDefaultKeys.discordNotifyEveryone].ToBool()) { discordNotification = "@everyone"; } DiscordMessages?.Call("API_SendFancyMessage", _config.Discord[ConfigDefaultKeys.discordWebhookURL], _config.Discord[ConfigDefaultKeys.discordTitle], _config.Discord[ConfigDefaultKeys.discordEmbedColor].ToInt(), json, discordNotification); } } public class Fields { public string name { get; set; } public string value { get; set; } public bool inline { get; set; } public Fields(string name, string value, bool inline) { this.name = name; this.value = value; this.inline = inline; } } #endregion } }