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

FREE EasyVoteExtended : 3.2.9 3.2.9

Нет прав для скачивания
C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Facepunch.Extend;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries;
using UnityEngine;
using UnityEngine.Networking;
namespace Oxide.Plugins {
    [Info("Easy Vote Extended", "dFxPhoeniX&TimRS", "3.2.9")]
    [Description("The best Rust server voting system")]
    public class EasyVoteExtended : RustPlugin {
        private readonly HashSet<string> pendingClaims = new HashSet<string>();
        private readonly HashSet<string> pendingStatusChecks = new HashSet<string>();
        private readonly Dictionary<string, string> statusRequestTokens = new Dictionary<string, string>();
        private readonly HashSet<ulong> scheduledVoteChecks = new HashSet<ulong>();
        private readonly HashSet<string> reportedConfigurationWarnings = new HashSet<string>();
        private readonly Queue<Action> claimRequestQueue = new Queue<Action>();
        private readonly Queue<Action> voteRequestQueue = new Queue<Action>();
        private readonly Dictionary<string, float> commandCooldowns = new Dictionary<string, float>();
        private readonly HashSet<string> scheduledClaimRetries = new HashSet<string>();
        private Dictionary<string, List<string>> pendingRewardCommands = new Dictionary<string, List<string>>();
        private Dictionary<string, PendingClaimTransaction> pendingClaimTransactions = new Dictionary<string, PendingClaimTransaction>();
        private const int CurrentConfigurationVersion = 1;
        private const int DefaultAutomaticVoteCheckInterval = 300;
        private const int DefaultVoteFollowUpCheckDelay = 60;
        private const float VoteRequestSpacing = 0.2f;
        private const float ManualCommandCooldown = 10f;
        private const int VoteApiRequestTimeout = 15000;
        private const int MaximumClaimAttempts = 6;
        private const string PendingRewardsDataFileName = "EasyVoteExtended_PendingRewards";
        private const string PendingClaimsDataFileName = "EasyVoteExtended_PendingClaims";
        private const string PendingRewardTransactionPrefix = "__EVE_TX__:";
        private const string ClaimStateCreated = "Created";
        private const string ClaimStateSent = "Sent";
        private const string ClaimStateUncertain = "Uncertain";
        private const string ClaimStateConfirmed = "Confirmed";
        private const string ClaimStateManualReview = "ManualReview";
        private class PendingClaimTransaction {
            public string TransactionId;
            public ulong PlayerId;
            public string PlayerName;
            public string ServerName;
            public string Site;
            public bool NotifyPlayer;
            public int Attempts;
            public string State;
            public bool HadAmbiguousFailure;
            public int TargetVoteCount;
        }
        ////////////////////////////////////////////////////////////
        // Oxide Hooks
        ////////////////////////////////////////////////////////////
        private void Init() {
            // LoadConfig() normally validates the configuration before Init().
            // Keep this fallback only for unexpected load-order scenarios.
            if (_config == null) {
                _config = Config.ReadObject<PluginConfig>();
                EnsureConfigDefaults();
            }
            LoadMessages();
            LoadPendingRewardsData();
            LoadPendingClaimsData();
        }
        private void OnServerInitialized() {
            ConsoleLog("Easy Vote Extended has been initialized...");
            StartRequestQueueProcessor();
            StartAutomaticVoteChecks();
            RecoverPendingClaimTransactions();
            timer.Once(2f, () => {
                foreach (BasePlayer player in BasePlayer.activePlayerList.ToArray()) {
                    if (player == null || !player.IsConnected) {
                        continue;
                    }
                    CheckIfPlayerDataExists(player);
                    DeliverPendingRewards(player);
                    CheckVotingStatus(player, false);
                }
            }
            );
        }
        private void Unload() {
            pendingClaims.Clear();
            pendingStatusChecks.Clear();
            statusRequestTokens.Clear();
            scheduledVoteChecks.Clear();
            reportedConfigurationWarnings.Clear();
            claimRequestQueue.Clear();
            voteRequestQueue.Clear();
            commandCooldowns.Clear();
            scheduledClaimRetries.Clear();
            SavePendingRewardsData();
            SavePendingClaimsData();
        }
        private void OnNewSave(string filename) {
            _Debug("------------------------------");
            _Debug("Method: OnNewSave");
            ConsoleLog("New map data detected!");
            if (_config.PluginSettings[ConfigDefaultKeys.ClearRewardsOnWipe].ToBool()) {
                _Debug("Wiping all votes from data file");
                ResetAllVoteData();
            }
        }
        private void OnPlayerConnected(BasePlayer player) {
            _Debug("------------------------------");
            _Debug("Method: OnPlayerConnected");
            _Debug($"Player: {player.displayName}/{player.UserIDString}");
            CheckIfPlayerDataExists(player);
            ulong connectedPlayerId = player.userID;
            timer.Once(2f, () => {
                BasePlayer connectedPlayer = BasePlayer.FindByID(connectedPlayerId);
                if (connectedPlayer != null && connectedPlayer.IsConnected) {
                    DeliverPendingRewards(connectedPlayer);
                }
            }
            );
            bool checkOnSleepEnded = _config.NotificationSettings[ConfigDefaultKeys.OnPlayerSleepEnded].ToBool();
            bool notifyOnConnected = _config.NotificationSettings[ConfigDefaultKeys.OnPlayerConnected].ToBool();
            if (!checkOnSleepEnded) {
                CheckVotingStatus(player, notifyOnConnected);
            }
        }
        private void OnPlayerDisconnected(BasePlayer player, string reason) {
            if (player == null) {
                return;
            }
            commandCooldowns.Remove($"{player.UserIDString}:vote");
            commandCooldowns.Remove($"{player.UserIDString}:claim");
            scheduledVoteChecks.Remove(player.userID);
        }
        private void OnPlayerSleepEnded(BasePlayer player) {
            _Debug("------------------------------");
            _Debug("Method: OnPlayerSleepEnded");
            _Debug($"Player: {player.displayName}/{player.UserIDString}");
            DeliverPendingRewards(player);
            if (_config.NotificationSettings[ConfigDefaultKeys.OnPlayerSleepEnded].ToBool()) {
                CheckVotingStatus(player);
            }
        }
        ////////////////////////////////////////////////////////////
        // General Methods
        ////////////////////////////////////////////////////////////
        private void HandleClaimWebRequestCallback(int code, string response, string pendingClaimKey, string transactionId, string requestToken) {
            pendingClaims.Remove(requestToken);
            PendingClaimTransaction transaction;
            if (!TryGetActiveClaimTransaction(pendingClaimKey, transactionId, out transaction)) {
                _Debug($"Ignoring a stale claim callback for transaction {transactionId}.");
                return;
            }
            string playerSteamId = transaction.PlayerId.ToString();
            BasePlayer player = FindPlayer(transaction.PlayerId);
            string resolvedPlayerName = GetResolvedPlayerName(player, transaction.PlayerName, playerSteamId);
            if (code != 200) {
                transaction.State = ClaimStateUncertain;
                transaction.HadAmbiguousFailure = true;
                SavePendingClaimsData();
                ConsoleError($"An error occurred while trying to claim the vote reward of the player {resolvedPlayerName}:{playerSteamId}");
                ConsoleWarn($"HTTP Code: {code} | Response: {response} | Server Name: {transaction.ServerName}");
                ConsoleWarn("The claim transaction was preserved and will be retried automatically.");
                ScheduleClaimRetry(pendingClaimKey);
                return;
            }
            response = response?.Trim();
            _Debug("------------------------------");
            _Debug("Method: HandleClaimWebRequestCallback");
            _Debug($"Site: {transaction.Site}");
            _Debug($"Code: {code}");
            _Debug($"Response: {response}");
            _Debug($"ServerName: {transaction.ServerName}");
            _Debug($"Player Name: {resolvedPlayerName}");
            _Debug($"Player SteamID: {playerSteamId}");
            _Debug("Web Request Type: Claim");
            if (_config.DebugSettings[ConfigDefaultKeys.VerboseDebugEnabled].ToBool()) {
                _Debug($"Verbose Debug Enabled, Setting Response Code to: {_config.DebugSettings[ConfigDefaultKeys.ClaimAPIRepsonseCode]}");
                response = _config.DebugSettings[ConfigDefaultKeys.ClaimAPIRepsonseCode];
            }
            if (response == "1") {
                ConfirmClaimTransaction(pendingClaimKey, transaction);
                return;
            }
            if (response == "2") {
                if (transaction.HadAmbiguousFailure) {
                    // A previous request was sent but its response was lost or invalid.
                    // In the normal one-plugin workflow, "already claimed" now confirms
                    // that the preserved request reached the tracker.
                    ConfirmClaimTransaction(pendingClaimKey, transaction);
                    return;
                }
                RemovePendingClaimTransaction(pendingClaimKey);
                if (transaction.NotifyPlayer && player != null && player.IsConnected) {
                    player.ChatMessage(_lang("AlreadyVoted", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], transaction.Site));
                }
                return;
            }
            if (response == "0") {
                RemovePendingClaimTransaction(pendingClaimKey);
                if (transaction.NotifyPlayer && player != null && player.IsConnected) {
                    player.ChatMessage(_lang("ClaimStatus", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], transaction.Site, transaction.ServerName));
                }
                return;
            }
            transaction.State = ClaimStateUncertain;
            transaction.HadAmbiguousFailure = true;
            SavePendingClaimsData();
            ConsoleWarn($"Unexpected claim API response from {transaction.Site} for {resolvedPlayerName}/{playerSteamId} on {transaction.ServerName}: '{response ?? "null"}'. Expected 0, 1, or 2. The claim transaction will be retried.");
            ScheduleClaimRetry(pendingClaimKey);
        }
        private bool TryGetActiveClaimTransaction(string pendingClaimKey, string transactionId, out PendingClaimTransaction transaction) {
            transaction = null;
            return !string.IsNullOrEmpty(transactionId) && pendingClaimTransactions.TryGetValue(pendingClaimKey, out transaction) && transaction != null && string.Equals(transaction.TransactionId, transactionId, StringComparison.Ordinal);
        }
        private void ConfirmClaimTransaction(string pendingClaimKey, PendingClaimTransaction transaction) {
            if (transaction == null || !pendingClaimTransactions.ContainsKey(pendingClaimKey) || !ReferenceEquals(pendingClaimTransactions[pendingClaimKey], transaction)) {
                return;
            }
            if (transaction.TargetVoteCount <= 0) {
                transaction.TargetVoteCount = GetStoredVoteCount(transaction.PlayerId.ToString()) + 1;
            }
            transaction.State = ClaimStateConfirmed;
            SavePendingClaimsData();
            FinalizeConfirmedClaimTransaction(pendingClaimKey, transaction.TransactionId);
        }
        private void FinalizeConfirmedClaimTransaction(string pendingClaimKey, string transactionId) {
            PendingClaimTransaction transaction;
            if (!TryGetActiveClaimTransaction(pendingClaimKey, transactionId, out transaction) || !string.Equals(transaction.State, ClaimStateConfirmed, StringComparison.Ordinal)) {
                return;
            }
            string playerSteamId = transaction.PlayerId.ToString();
            BasePlayer player = FindPlayer(transaction.PlayerId);
            string resolvedPlayerName = GetResolvedPlayerName(player, transaction.PlayerName, playerSteamId);
            int targetVoteCount = Math.Max(1, transaction.TargetVoteCount);
            int currentVoteCount = GetStoredVoteCount(playerSteamId);
            if (currentVoteCount < targetVoteCount) {
                DataFile[playerSteamId] = targetVoteCount;
                SaveDataFile(DataFile);
            }
            bool cumulativeRewards = _config.PluginSettings[ConfigDefaultKeys.RewardIsCumulative].ToBool();
            List<string> rewardCommands = BuildRewardCommands(transaction.PlayerId, resolvedPlayerName, targetVoteCount, cumulativeRewards);
            bool rewardConfigured = rewardCommands.Count > 0;
            if (rewardConfigured && !QueuePendingRewardTransaction(playerSteamId, transaction.TransactionId, rewardCommands)) {
                ConsoleError($"Failed to persist reward commands for claim transaction {transaction.TransactionId}. Local completion will be retried.");
                ScheduleClaimRetry(pendingClaimKey);
                return;
            }
            // Removing the claim transaction only after the vote count and reward
            // queue are persisted makes local completion idempotent after reloads.
            RemovePendingClaimTransaction(pendingClaimKey);
            bool rewardDeliveredImmediately = rewardConfigured && player != null && player.IsConnected && !player.IsSleeping();
            if (rewardDeliveredImmediately) {
                DeliverPendingRewards(player, false);
            }
            string displayedVoteCount = GetStoredVoteCount(playerSteamId).ToString();
            if (player != null && player.IsConnected) {
                string messageKey = rewardConfigured ? (rewardDeliveredImmediately ? "ThankYou" : "ThankYouPending") : "ThankYouNoReward";
                player.ChatMessage(_lang(messageKey, player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], displayedVoteCount, transaction.Site));
            }
            if (_config.Discord[ConfigDefaultKeys.DiscordEnabled].ToBool()) {
                string discordMessageKey = rewardConfigured ? (rewardDeliveredImmediately ? "DiscordWebhookMessage" : "DiscordWebhookMessagePending") : "DiscordWebhookMessageNoReward";
                ServerMgr.Instance.StartCoroutine(DiscordSendMessage(_lang(discordMessageKey, null, resolvedPlayerName, transaction.ServerName, transaction.Site)));
            }
            if (_config.NotificationSettings[ConfigDefaultKeys.GlobalChatAnnouncements].ToBool()) {
                string globalMessageKey = rewardConfigured ? (rewardDeliveredImmediately ? "GlobalChatAnnouncements" : "GlobalChatAnnouncementsPending") : "GlobalChatAnnouncementsNoReward";
                foreach (BasePlayer recipient in BasePlayer.activePlayerList) {
                    if (recipient == null || !recipient.IsConnected) {
                        continue;
                    }
                    recipient.ChatMessage(_lang(globalMessageKey, recipient.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], resolvedPlayerName, displayedVoteCount));
                }
            }
        }
        private void HandleStatusWebRequestCallback(int code, string response, ulong playerId, string playerName, string statusUrl, string serverName, string site, bool notifyPlayer, string pendingStatusKey, string statusRequestToken) {
            if (!TryConsumeStatusRequest(pendingStatusKey, statusRequestToken)) {
                _Debug($"Ignoring a stale status callback for {playerName}/{playerId} on {site} ({serverName}).");
                return;
            }
            string playerSteamId = playerId.ToString();
            BasePlayer player = FindPlayer(playerId);
            string resolvedPlayerName = GetResolvedPlayerName(player, playerName, playerSteamId);
            if (code != 200) {
                ConsoleError($"An error occurred while trying to check the vote status of the player {resolvedPlayerName}:{playerSteamId}");
                ConsoleWarn($"URL: {MaskApiUrl(statusUrl)}");
                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 your server key is correct.");
                return;
            }
            response = response?.Trim();
            _Debug("------------------------------");
            _Debug("Method: HandleStatusWebRequestCallback");
            _Debug($"Site: {site}");
            _Debug($"Code: {code}");
            _Debug($"Response: {response}");
            _Debug($"URL: {MaskApiUrl(statusUrl)}");
            _Debug($"ServerName: {serverName}");
            _Debug($"Player Name: {resolvedPlayerName}");
            _Debug($"Player SteamID: {playerSteamId}");
            _Debug("Web Request Type: Status/Check");
            if (_config.DebugSettings[ConfigDefaultKeys.VerboseDebugEnabled].ToBool()) {
                _Debug($"Verbose Debug Enabled, Setting Response Code to: {_config.DebugSettings[ConfigDefaultKeys.CheckAPIResponseCode]}");
                response = _config.DebugSettings[ConfigDefaultKeys.CheckAPIResponseCode];
            }
            string pendingClaimKey = GetPendingClaimKey(playerId, serverName, site);
            PendingClaimTransaction existingTransaction;
            pendingClaimTransactions.TryGetValue(pendingClaimKey, out existingTransaction);
            if (response == "1") {
                EnqueueClaimRequest(playerId, resolvedPlayerName, serverName, site, notifyPlayer);
                return;
            }
            if (response == "0") {
                if (existingTransaction != null) {
                    RemovePendingClaimTransaction(pendingClaimKey);
                }
                if (notifyPlayer && player != null && player.IsConnected) {
                    player.ChatMessage(_lang("NoRewards", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], serverName, site));
                }
                return;
            }
            if (response == "2") {
                if (existingTransaction != null && existingTransaction.HadAmbiguousFailure) {
                    ConfirmClaimTransaction(pendingClaimKey, existingTransaction);
                    return;
                }
                if (existingTransaction != null) {
                    RemovePendingClaimTransaction(pendingClaimKey);
                }
                if (notifyPlayer && player != null && player.IsConnected) {
                    player.ChatMessage(_lang("AlreadyVoted", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix], site));
                }
                return;
            }
            ConsoleWarn($"Unexpected status API response from {site} for {resolvedPlayerName}/{playerSteamId} on {serverName}: '{response ?? "null"}'. Expected 0, 1, or 2.");
        }
        private bool IsActiveStatusRequest(string pendingStatusKey, string statusRequestToken) {
            string activeToken;
            return !string.IsNullOrEmpty(statusRequestToken) && statusRequestTokens.TryGetValue(pendingStatusKey, out activeToken) && string.Equals(activeToken, statusRequestToken, StringComparison.Ordinal);
        }
        private bool TryConsumeStatusRequest(string pendingStatusKey, string statusRequestToken) {
            if (!IsActiveStatusRequest(pendingStatusKey, statusRequestToken)) {
                return false;
            }
            statusRequestTokens.Remove(pendingStatusKey);
            pendingStatusChecks.Remove(pendingStatusKey);
            return true;
        }
        private void CancelStatusRequest(string pendingStatusKey, string statusRequestToken) {
            if (!IsActiveStatusRequest(pendingStatusKey, statusRequestToken)) {
                return;
            }
            statusRequestTokens.Remove(pendingStatusKey);
            pendingStatusChecks.Remove(pendingStatusKey);
        }
        private void CancelStatusRequestsForPlayer(string steamId) {
            if (string.IsNullOrEmpty(steamId)) {
                return;
            }
            foreach (string pendingStatusKey in statusRequestTokens.Keys .Where(key => key.StartsWith(steamId + ":", StringComparison.Ordinal)) .ToList()) {
                statusRequestTokens.Remove(pendingStatusKey);
                pendingStatusChecks.Remove(pendingStatusKey);
            }
        }
        private void EnqueueClaimRequest(ulong playerId, string playerName, string serverName, string site, bool notifyPlayer) {
            string pendingClaimKey = GetPendingClaimKey(playerId, serverName, site);
            PendingClaimTransaction transaction;
            if (!pendingClaimTransactions.TryGetValue(pendingClaimKey, out transaction) || transaction == null) {
                transaction = new PendingClaimTransaction {
                    TransactionId = Guid.NewGuid().ToString("N"), PlayerId = playerId, PlayerName = playerName, ServerName = serverName, Site = site, NotifyPlayer = notifyPlayer, Attempts = 0, State = ClaimStateCreated, HadAmbiguousFailure = false, TargetVoteCount = 0
                }
                ;
                pendingClaimTransactions[pendingClaimKey] = transaction;
            }
            else {
                transaction.PlayerName = playerName;
                transaction.NotifyPlayer |= notifyPlayer;
                if (string.Equals(transaction.State, ClaimStateManualReview, StringComparison.Ordinal)) {
                    transaction.Attempts = 0;
                    transaction.State = ClaimStateCreated;
                    transaction.HadAmbiguousFailure = false;
                }
            }
            SavePendingClaimsData();
            QueuePendingClaimTransaction(pendingClaimKey);
        }
        private void QueuePendingClaimTransaction(string pendingClaimKey) {
            PendingClaimTransaction transaction;
            if (!pendingClaimTransactions.TryGetValue(pendingClaimKey, out transaction) || transaction == null) {
                pendingClaimTransactions.Remove(pendingClaimKey);
                SavePendingClaimsData();
                return;
            }
            if (string.IsNullOrEmpty(transaction.TransactionId)) {
                transaction.TransactionId = Guid.NewGuid().ToString("N");
                SavePendingClaimsData();
            }
            if (string.Equals(transaction.State, ClaimStateConfirmed, StringComparison.Ordinal)) {
                FinalizeConfirmedClaimTransaction(pendingClaimKey, transaction.TransactionId);
                return;
            }
            if (string.Equals(transaction.State, ClaimStateManualReview, StringComparison.Ordinal)) {
                return;
            }
            if (transaction.Attempts >= MaximumClaimAttempts) {
                transaction.State = ClaimStateManualReview;
                SavePendingClaimsData();
                ConsoleError($"Claim transaction {transaction.TransactionId} for {transaction.PlayerName}/{transaction.PlayerId} on {transaction.Site} reached the retry limit and requires manual review.");
                return;
            }
            string claimUrl;
            if (!TryBuildClaimUrl(transaction, out claimUrl)) {
                transaction.State = ClaimStateManualReview;
                SavePendingClaimsData();
                ConsoleError($"Unable to rebuild the claim URL for {transaction.PlayerName}/{transaction.PlayerId} on {transaction.Site} ({transaction.ServerName}). The transaction requires manual review.");
                return;
            }
            string requestToken = $"{pendingClaimKey}:{transaction.TransactionId}";
            if (!pendingClaims.Add(requestToken)) {
                _Debug($"A claim request is already pending for {transaction.PlayerName}/{transaction.PlayerId} on {transaction.Site} ({transaction.ServerName}).");
                return;
            }
            scheduledClaimRetries.Remove(pendingClaimKey);
            _Debug($"Automatic claim URL: {MaskApiUrl(claimUrl)}");
            QueueVoteRequest(() => {
                PendingClaimTransaction currentTransaction;
                if (!TryGetActiveClaimTransaction(pendingClaimKey, transaction.TransactionId, out currentTransaction)) {
                    pendingClaims.Remove(requestToken);
                    return;
                }
                currentTransaction.Attempts++;
                currentTransaction.State = ClaimStateSent;
                SavePendingClaimsData();
                try {
                    webrequest.Enqueue(claimUrl, null, (code, response) => HandleClaimWebRequestCallback(code, response, pendingClaimKey, currentTransaction.TransactionId, requestToken), this, RequestMethod.GET, null, VoteApiRequestTimeout);
                }
                catch (Exception exception) {
                    pendingClaims.Remove(requestToken);
                    if (TryGetActiveClaimTransaction(pendingClaimKey, currentTransaction.TransactionId, out currentTransaction)) {
                        currentTransaction.State = ClaimStateUncertain;
                        currentTransaction.HadAmbiguousFailure = true;
                        SavePendingClaimsData();
                    }
                    ConsoleError($"Failed to enqueue the claim request for {transaction.PlayerName}/{transaction.PlayerId} on {transaction.Site}: {exception.Message}");
                    ScheduleClaimRetry(pendingClaimKey);
                }
            }
            , true);
        }
        private bool TryBuildClaimUrl(PendingClaimTransaction transaction, out string claimUrl) {
            claimUrl = null;
            if (transaction == null || _config.Servers == null) {
                return false;
            }
            KeyValuePair<string, Dictionary<string, string>> server = _config.Servers.FirstOrDefault(entry => entry.Key.Equals(transaction.ServerName, StringComparison.OrdinalIgnoreCase));
            if (string.IsNullOrEmpty(server.Key) || server.Value == null) {
                return false;
            }
            KeyValuePair<string, string> configuredVoteSite = server.Value.FirstOrDefault(entry => entry.Key.Equals(transaction.Site, StringComparison.OrdinalIgnoreCase));
            if (string.IsNullOrEmpty(configuredVoteSite.Key)) {
                return false;
            }
            string configuredSiteName;
            Dictionary<string, string> apiConfiguration;
            string serverId;
            string serverKey;
            if (!TryGetVoteSiteConfiguration(configuredVoteSite.Key, out configuredSiteName, out apiConfiguration) || !TryParseServerCredentials(configuredVoteSite.Value, out serverId, out serverKey)) {
                return false;
            }
            try {
                claimUrl = FormatApiUrl(apiConfiguration, ConfigDefaultKeys.apiClaim, transaction.PlayerId, transaction.PlayerName, serverId, serverKey);
                return true;
            }
            catch (Exception exception) {
                ConsoleError($"Failed to format the recovered claim URL for {configuredSiteName} on {server.Key}: {exception.Message}");
                return false;
            }
        }
        private void ScheduleClaimRetry(string pendingClaimKey) {
            PendingClaimTransaction transaction;
            if (!pendingClaimTransactions.TryGetValue(pendingClaimKey, out transaction) || transaction == null || string.Equals(transaction.State, ClaimStateManualReview, StringComparison.Ordinal) || !scheduledClaimRetries.Add(pendingClaimKey)) {
                return;
            }
            if (!string.Equals(transaction.State, ClaimStateConfirmed, StringComparison.Ordinal) && transaction.Attempts >= MaximumClaimAttempts) {
                scheduledClaimRetries.Remove(pendingClaimKey);
                transaction.State = ClaimStateManualReview;
                SavePendingClaimsData();
                ConsoleError($"Claim transaction {transaction.TransactionId} for {transaction.PlayerName}/{transaction.PlayerId} on {transaction.Site} reached the retry limit and requires manual review.");
                return;
            }
            float retryDelay = string.Equals(transaction.State, ClaimStateConfirmed, StringComparison.Ordinal) ? 10f : (transaction.Attempts <= 3 ? Math.Max(10f, transaction.Attempts * 10f) : 300f);
            _Debug($"Claim retry for {transaction.PlayerName}/{transaction.PlayerId} on {transaction.Site} scheduled in {retryDelay:0} seconds.");
            timer.Once(retryDelay, () => {
                scheduledClaimRetries.Remove(pendingClaimKey);
                if (pendingClaimTransactions.ContainsKey(pendingClaimKey)) {
                    QueuePendingClaimTransaction(pendingClaimKey);
                }
            }
            );
        }
        private void RecoverPendingClaimTransactions() {
            bool changed = false;
            foreach (KeyValuePair<string, PendingClaimTransaction> entry in pendingClaimTransactions.ToList()) {
                PendingClaimTransaction transaction = entry.Value;
                if (transaction == null) {
                    pendingClaimTransactions.Remove(entry.Key);
                    changed = true;
                    continue;
                }
                if (string.IsNullOrEmpty(transaction.TransactionId)) {
                    transaction.TransactionId = Guid.NewGuid().ToString("N");
                    changed = true;
                }
                if (string.IsNullOrEmpty(transaction.State)) {
                    transaction.State = transaction.Attempts > 0 ? ClaimStateUncertain : ClaimStateCreated;
                    transaction.HadAmbiguousFailure = transaction.Attempts > 0;
                    changed = true;
                }
                else if (string.Equals(transaction.State, ClaimStateSent, StringComparison.Ordinal)) {
                    // The server stopped while a request was in flight, so the
                    // result is ambiguous and a later response 2 may be ours.
                    transaction.State = ClaimStateUncertain;
                    transaction.HadAmbiguousFailure = true;
                    changed = true;
                }
            }
            if (changed) {
                SavePendingClaimsData();
            }
            foreach (KeyValuePair<string, PendingClaimTransaction> entry in pendingClaimTransactions.ToList()) {
                if (entry.Value == null || string.Equals(entry.Value.State, ClaimStateManualReview, StringComparison.Ordinal)) {
                    continue;
                }
                QueuePendingClaimTransaction(entry.Key);
            }
        }
        private string GetPendingClaimKey(ulong playerId, string serverName, string site) {
            return $"{playerId}:{serverName}:{site}";
        }
        private void RemovePendingClaimTransaction(string pendingClaimKey) {
            scheduledClaimRetries.Remove(pendingClaimKey);
            PendingClaimTransaction transaction;
            if (pendingClaimTransactions.TryGetValue(pendingClaimKey, out transaction) && transaction != null) {
                pendingClaims.Remove($"{pendingClaimKey}:{transaction.TransactionId}");
            }
            if (pendingClaimTransactions.Remove(pendingClaimKey)) {
                SavePendingClaimsData();
            }
        }
        private BasePlayer FindPlayer(ulong playerId) {
            return BasePlayer.FindByID(playerId) ?? BasePlayer.FindSleeping(playerId);
        }
        private string GetResolvedPlayerName(BasePlayer player, string fallbackName, string steamId) {
            if (player != null && !string.IsNullOrWhiteSpace(player.displayName)) {
                return player.displayName;
            }
            return string.IsNullOrWhiteSpace(fallbackName) ? steamId : fallbackName;
        }
        private string FormatApiUrl(Dictionary<string, string> apiConfiguration, string apiKey, BasePlayer player, string serverId, string serverKey) {
            if (player == null) {
                throw new ArgumentNullException(nameof(player));
            }
            return FormatApiUrl(apiConfiguration, apiKey, player.userID, player.displayName, serverId, serverKey);
        }
        private string FormatApiUrl(Dictionary<string, string> apiConfiguration, string apiKey, ulong playerId, string playerName, string serverId, string serverKey) {
            string apiLink = apiConfiguration[apiKey];
            bool usernameApiEnabled = apiConfiguration[ConfigDefaultKeys.apiUsername].ToBool();
            string encodedServerKey = Uri.EscapeDataString(serverKey);
            string encodedServerId = Uri.EscapeDataString(serverId);
            string encodedPlayerIdentifier = usernameApiEnabled ? Uri.EscapeDataString(playerName ?? string.Empty) : playerId.ToString();
            // Extra format arguments are ignored when the URL does not use them.
            // {0} = API key/token, {1} = Steam ID or username, {2} = server ID.
            return string.Format(apiLink, encodedServerKey, encodedPlayerIdentifier, encodedServerId);
        }
        private bool TryGetVoteSiteConfiguration(string site, out string configuredSiteName, out Dictionary<string, string> apiConfiguration) {
            configuredSiteName = null;
            apiConfiguration = null;
            if (_config.VoteSitesAPI == null) {
                return false;
            }
            foreach (KeyValuePair<string, Dictionary<string, string>> voteSite in _config.VoteSitesAPI) {
                if (!voteSite.Key.Equals(site, StringComparison.OrdinalIgnoreCase)) {
                    continue;
                }
                configuredSiteName = voteSite.Key;
                apiConfiguration = voteSite.Value;
                break;
            }
            if (apiConfiguration == null || !apiConfiguration.ContainsKey(ConfigDefaultKeys.apiClaim) || !apiConfiguration.ContainsKey(ConfigDefaultKeys.apiStatus) || !apiConfiguration.ContainsKey(ConfigDefaultKeys.apiLink) || !apiConfiguration.ContainsKey(ConfigDefaultKeys.apiUsername) || string.IsNullOrWhiteSpace(apiConfiguration[ConfigDefaultKeys.apiClaim]) || string.IsNullOrWhiteSpace(apiConfiguration[ConfigDefaultKeys.apiStatus]) || string.IsNullOrWhiteSpace(apiConfiguration[ConfigDefaultKeys.apiLink])) {
                return false;
            }
            return true;
        }
        private bool TryParseServerCredentials(string configuredValue, out string serverId, out string serverKey) {
            serverId = null;
            serverKey = null;
            if (string.IsNullOrWhiteSpace(configuredValue)) {
                return false;
            }
            string[] idKey = configuredValue.Split(new[] {
                ':'
            }
            , 2);
            if (idKey.Length != 2) {
                return false;
            }
            serverId = idKey[0].Trim();
            serverKey = idKey[1].Trim();
            if (string.IsNullOrWhiteSpace(serverId) || string.IsNullOrWhiteSpace(serverKey)) {
                return false;
            }
            if (serverId.Equals("ID", StringComparison.OrdinalIgnoreCase) || serverKey.Equals("KEY", StringComparison.OrdinalIgnoreCase)) {
                return false;
            }
            return true;
        }
        private string MaskApiUrl(string url) {
            if (string.IsNullOrEmpty(url)) {
                return url;
            }
            return Regex.Replace(url, @"([?&][^=&]*(?:key|token|secret|auth|password)[^=&]*=)[^&]*", "$1********", RegexOptions.IgnoreCase);
        }
        private int GetStoredVoteCount(string steamId) {
            if (string.IsNullOrEmpty(steamId) || DataFile[steamId] == null) {
                return 0;
            }
            object storedValue = DataFile[steamId];
            try {
                return Convert.ToInt32(storedValue);
            }
            catch {
                int parsedValue;
                return int.TryParse(storedValue.ToString(), out parsedValue) ? parsedValue : 0;
            }
        }
        private List<string> BuildRewardCommands(ulong playerId, string playerName, int playerVoteCount, bool cumulativeRewards) {
            List<string> commands = new List<string>();
            string steamId = playerId.ToString();
            AddRewardCommands(commands, "@", steamId, playerName);
            if (playerVoteCount == 1) {
                AddRewardCommands(commands, "first", steamId, playerName);
            }
            foreach (KeyValuePair<string, List<string>> reward in _config.Rewards) {
                int requiredVoteCount;
                if (!TryGetNumericRewardVoteCount(reward.Key, out requiredVoteCount)) {
                    continue;
                }
                if (cumulativeRewards ? requiredVoteCount <= playerVoteCount : requiredVoteCount == playerVoteCount) {
                    AddRewardCommands(commands, reward.Key, steamId, playerName);
                }
            }
            return commands;
        }
        private void AddRewardCommands(List<string> destination, string rewardKey, string steamId, string playerName) {
            List<string> configuredCommands;
            if (destination == null || !_config.Rewards.TryGetValue(rewardKey, out configuredCommands) || configuredCommands == null) {
                return;
            }
            foreach (string configuredCommand in configuredCommands) {
                if (string.IsNullOrWhiteSpace(configuredCommand)) {
                    continue;
                }
                string command = ParseRewardCommand(steamId, playerName, configuredCommand);
                if (!string.IsNullOrWhiteSpace(command)) {
                    destination.Add(command);
                }
            }
        }
        private bool QueuePendingRewardTransaction(string steamId, string transactionId, List<string> commands) {
            if (string.IsNullOrWhiteSpace(steamId) || string.IsNullOrWhiteSpace(transactionId) || commands == null || commands.Count == 0) {
                return false;
            }
            List<string> pendingCommands;
            if (!pendingRewardCommands.TryGetValue(steamId, out pendingCommands) || pendingCommands == null) {
                pendingCommands = new List<string>();
                pendingRewardCommands[steamId] = pendingCommands;
            }
            string marker = GetPendingRewardTransactionMarker(transactionId);
            if (pendingCommands.Contains(marker)) {
                return true;
            }
            int originalCount = pendingCommands.Count;
            pendingCommands.Add(marker);
            pendingCommands.AddRange(commands.Where(command => !string.IsNullOrWhiteSpace(command)));
            if (!SavePendingRewardsData()) {
                pendingCommands.RemoveRange(originalCount, pendingCommands.Count - originalCount);
                if (pendingCommands.Count == 0) {
                    pendingRewardCommands.Remove(steamId);
                }
                return false;
            }
            _Debug($"Queued {commands.Count} pending reward command(s) for {steamId}, transaction {transactionId}.");
            return true;
        }
        private string GetPendingRewardTransactionMarker(string transactionId) {
            return PendingRewardTransactionPrefix + transactionId;
        }
        private bool IsPendingRewardTransactionMarker(string value) {
            return !string.IsNullOrEmpty(value) && value.StartsWith(PendingRewardTransactionPrefix, StringComparison.Ordinal);
        }
        private bool TryGetNumericRewardVoteCount(string rewardKey, out int requiredVoteCount) {
            return int.TryParse(rewardKey, out requiredVoteCount) && requiredVoteCount > 0;
        }
        private bool HasRewardCommands(List<string> rewardCommands) {
            return rewardCommands != null && rewardCommands.Any(command => !string.IsNullOrWhiteSpace(command));
        }
        private string GetRewardDescription(string rewardKey, string playerId) {
            string description;
            if (_config.RewardDescriptions.TryGetValue(rewardKey, out description) && !string.IsNullOrWhiteSpace(description)) {
                return description;
            }
            return _lang("RewardDescriptionMissing", playerId);
        }
        private int GetRewardDisplayOrder(string rewardKey) {
            if (rewardKey == "@") {
                return int.MinValue;
            }
            if (rewardKey == "first") {
                return int.MinValue + 1;
            }
            int requiredVoteCount;
            return TryGetNumericRewardVoteCount(rewardKey, out requiredVoteCount) ? requiredVoteCount : int.MaxValue;
        }
        private bool DeliverPendingRewards(BasePlayer player, bool notifyPlayer = true) {
            if (player == null || !player.IsConnected || player.IsSleeping()) {
                return false;
            }
            List<string> commands;
            if (!pendingRewardCommands.TryGetValue(player.UserIDString, out commands) || commands == null || commands.Count == 0) {
                return false;
            }
            bool deliveredCommand = false;
            while (pendingRewardCommands.TryGetValue(player.UserIDString, out commands) && commands != null && commands.Count > 0) {
                string command = commands[0];
                commands.RemoveAt(0);
                bool removedPlayerEntry = commands.Count == 0;
                if (removedPlayerEntry) {
                    pendingRewardCommands.Remove(player.UserIDString);
                }
                // Persist removal before execution. This gives at-most-once delivery
                // across crashes and prevents a command from being replayed after it
                // has already been dispatched to the server console.
                if (!SavePendingRewardsData()) {
                    if (removedPlayerEntry) {
                        pendingRewardCommands[player.UserIDString] = commands;
                    }
                    commands.Insert(0, command);
                    return deliveredCommand;
                }
                if (IsPendingRewardTransactionMarker(command)) {
                    continue;
                }
                if (string.IsNullOrWhiteSpace(command)) {
                    continue;
                }
                _Debug($"Delivering pending reward command to {player.displayName}/{player.UserIDString}: {command}");
                rust.RunServerCommand(command);
                deliveredCommand = true;
            }
            if (deliveredCommand && notifyPlayer) {
                player.ChatMessage(_lang("PendingRewardsDelivered", player.UserIDString, _config.PluginSettings[ConfigDefaultKeys.Prefix]));
            }
            return deliveredCommand;
        }
        private string ParseRewardCommand(string steamId, string playerName, string command) {
            string safePlayerName = string.IsNullOrWhiteSpace(playerName) ? steamId : playerName .Replace("\r", " ") .Replace("\n", " ") .Replace(";", string.Empty) .Replace("\"", "'");
            return command .Replace("{playerid}", steamId) .Replace("{playername}", safePlayerName);
        }
        private void CheckIfPlayerDataExists(BasePlayer player) {
            _Debug("------------------------------");
            _Debug("Method: CheckIfPlayerDataExists");
            _Debug($"Player: {player.displayName}/{player.UserIDString}");
            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<string, object> player in DataFile.ToList()) {
                DataFile[player.Key] = 0;
                _Debug($"Player {player.Key} vote count reset...");
            }
            SaveDataFile(DataFile);
            pendingStatusChecks.Clear();
            statusRequestTokens.Clear();
            voteRequestQueue.Clear();
            if (pendingRewardCommands.Count > 0) {
                pendingRewardCommands.Clear();
                SavePendingRewardsData();
                _Debug("All pending vote rewards have been cleared together with the vote data.");
            }
            if (pendingClaimTransactions.Count > 0) {
                pendingClaimTransactions.Clear();
                pendingClaims.Clear();
                scheduledClaimRetries.Clear();
                SavePendingClaimsData();
                _Debug("All pending vote claim transactions have been cleared together with the vote data.");
            }
        }
        private void WarnConfigurationOnce(string warningKey, string message) {
            if (reportedConfigurationWarnings.Add(warningKey)) {
                ConsoleWarn(message);
            }
        }
        private void CheckVotingStatus(BasePlayer player, bool notifyPlayer = true) {
            if (player == null || !player.IsConnected) {
                return;
            }
            _Debug("------------------------------");
            _Debug("Method: CheckVotingStatus");
            _Debug($"Player: {player.displayName}/{player.UserIDString}");
            bool waitMessageSent = false;
            foreach (KeyValuePair<string, Dictionary<string, string>> server in _config.Servers) {
                if (server.Value == null) {
                    continue;
                }
                _Debug($"Server: {server.Key}");
                foreach (KeyValuePair<string, string> configuredVoteSite in server.Value) {
                    string configuredSiteName;
                    Dictionary<string, string> apiConfiguration;
                    if (!TryGetVoteSiteConfiguration(configuredVoteSite.Key, out configuredSiteName, out apiConfiguration)) {
                        WarnConfigur
// ... [Code preview truncated due to length. Full source in attached file] ...

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