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

FREE Tip4serv : 1.5.0 1.5.0

Нет прав для скачивания
C#:
/***********************************************************************************************************************/
/*** DO NOT edit this file! Edit the files under `oxide/config` and/or `oxide/lang`, created once plugin has loaded. ***/
/***********************************************************************************************************************/

using System.Collections.Generic;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core;
using Oxide.Core.Libraries;
using System;
using System.Text;
using System.Security.Cryptography;

namespace Oxide.Plugins
{
    [Info("Tip4serv", "Murgator & Duster", "1.5.0")]
    [Description("Allows Admin to monetize their 7 Days to die & Rust server from their Tip4serv store")]
    public class Tip4serv : CovalencePlugin
    {
        private class PluginConfig
        {
            public int request_interval_in_minutes;
            public string configkey;
            public string order_received_text;
        }
        [Serializable]
        public class Payment
        {
            public PaymentCmd[] cmds;
            public string player;
            public string username;
            public string action;
            public string id;
            public string steam_id;
        }
        [Serializable]
        public class PaymentCmd
        {
            public string str;
            public int id;
            public string state;
        }

        [Serializable]
        public class PaymentUpdate
        {
            public string action;
            public Dictionary<string, string> cmds = new Dictionary<string, string>(); // cmd id -> "Executed" / "Not Executed"
        }

        private const string STATUS_EXECUTED = "Executed";
        private const string STATUS_NOT_EXECUTED = "Not Executed";
        private const string STATE_MUST_BE_ONLINE = "Must be online";

        private const string API_BASE = "https://api.tip4serv.com/v1/store/server/";
        private const string RESPONSE_FILE = "tip4serv_response";

        private String key_msg = "Please set the config key to a valid key in your config/Tip4Serv.json file. Make sure you have copied the entire key on Tip4Serv.com (Ctrl+A then CTRL+C)";
        private bool Stopped = false;
        private bool Busy = false;
        private Timer PaymentTimer;
        private PluginConfig config;

        protected override void LoadDefaultConfig()
        {
            LogWarning("Creating a new configuration file");
            Config.WriteObject(GetDefaultConfig(), true);
        }
        private PluginConfig GetDefaultConfig()
        {
            return new PluginConfig
            {
                request_interval_in_minutes = 1,
                configkey = "YOUR_CONFIG_KEY",
                order_received_text = "[#cyan][Tip4serv][/#] You have received your order. Thank you !"
            };
        }
        private Dictionary<String, IPlayer> GetPlayers()
        {
            IEnumerable<IPlayer> players = covalence.Players.Connected;
            Dictionary<String, IPlayer> tip4customers = new Dictionary<string, IPlayer>();
            foreach (IPlayer player in players)
            {
                tip4customers[player.Id] = player;
            }
            return tip4customers;
        }
        private void Loaded()
        {
            #if !SEVENDAYSTODIE && !RUST
               LogError("This plugin only works for the 7 Days to Die or Rust Game");
               Stopped = true;
            #else
               Tip4Print("Tip4serv plugin has started");
               config = Config.ReadObject<PluginConfig>();
            #endif
        }
        private void Unload()
        {
            key_msg = null;
            if (PaymentTimer != null && !PaymentTimer.Destroyed)
                PaymentTimer.Destroy();
        }
        void OnServerInitialized()
        {
            if (!Stopped)
            {
                //check Tip4serv connection on script start
                string[] key_part = config.configkey.Split('.');
                if (key_part.Length != 3)
                {
                    Tip4Print(key_msg);
                    return;
                }
                CheckConnection(key_part, GetUnixTime());
                PaymentTimer = timer.In((float)config.request_interval_in_minutes * 60f, () => PaymentChecker());
            }
        }
        private void PaymentChecker()
        {
            string[] key_part = config.configkey.Split('.');
            if (key_part.Length != 3)
            {
                Tip4Print(key_msg);
                RescheduleTimer();
                return;
            }
            if (Busy)
            {
                // previous cycle is still executing commands, skip this tick
                RescheduleTimer();
                return;
            }
            check_pending_commands(key_part, GetUnixTime());
            RescheduleTimer();
        }
        private void RescheduleTimer()
        {
            if (PaymentTimer != null && !PaymentTimer.Destroyed)
                PaymentTimer.Destroy();
            PaymentTimer = timer.In((float)config.request_interval_in_minutes * 60f, () => PaymentChecker());
        }
        private string GetUnixTime()
        {
            long unixTime = ((DateTimeOffset)DateTime.UtcNow).ToUnixTimeSeconds();
            return unixTime.ToString();
        }
        private void Tip4Print(string content)
        {
            LogWarning(content);
        }
        private string ServerId(string[] key_parts)
        {
            return key_parts[0];
        }
        private Dictionary<string, string> BuildHeaders(string timestamp)
        {
            //HMAC calculation
            string[] key_parts = config.configkey.Split('.');
            string HMAC = calculateHMAC(key_parts, timestamp);
            return new Dictionary<string, string>
            {
                { "Authorization", "Bearer SERVER_KEY:" + HMAC },
                { "Content-Type", "application/json" }
            };
        }

        // Connection check on plugin start (GET on the commands route)
        private void CheckConnection(string[] key_parts, string timestamp)
        {
            string url = API_BASE + ServerId(key_parts) + "/commands?time=" + timestamp;
            Dictionary<string, string> headers = BuildHeaders(timestamp);
            webrequest.Enqueue(url, null, (code, response) =>
            {
                if (code == 200)
                    Tip4Print("Server has been successfully connected");
                else
                    Tip4Print("Tip4serv connection failed (HTTP " + code + "). Please check your config key. " + (response ?? ""));
            }, this, RequestMethod.GET, headers, 15f);
        }

        private void check_pending_commands(string[] key_parts, string timestamp)
        {
            string commandsUrl = API_BASE + ServerId(key_parts) + "/commands?time=" + timestamp;
            Dictionary<string, string> headers = BuildHeaders(timestamp);

            //GET pending commands
            webrequest.Enqueue(commandsUrl, null, (code, HTTPresponse) =>
            {
                if (code != 200 || string.IsNullOrEmpty(HTTPresponse))
                {
                    Tip4Print("Tip4serv API is temporarily unavailable (HTTP " + code + "). Will retry on the next cycle.");
                    return;
                }

                List<Payment> payments;
                try
                {
                    payments = Utility.ConvertFromJson<List<Payment>>(HTTPresponse);
                }
                catch (Exception)
                {
                    Tip4Print("Tip4serv returned an unexpected response: " + HTTPresponse);
                    return;
                }
                if (payments == null || payments.Count == 0)
                    return;

                // ledger of already-executed (payment -> cmd id -> status), persisted to survive a failed update POST
                Dictionary<string, PaymentUpdate> ledger = LoadLedger();
                Dictionary<string, PaymentUpdate> update = new Dictionary<string, PaymentUpdate>();
                Dictionary<String, IPlayer> players = GetPlayers();

                // commands that still need to be executed this cycle (spaced 1s apart)
                List<ExecItem> toExecute = new List<ExecItem>();

                for (int i = 0; i < payments.Count; i++)
                {
                    Payment p = payments[i];
                    if (p == null || string.IsNullOrEmpty(p.id))
                        continue;

                    PaymentUpdate pu = new PaymentUpdate { action = p.action };
                    IPlayer player_infos = checkifPlayerIsLoaded(p.steam_id, players);
                    if (player_infos != null)
                        player_infos.Message(config.order_received_text);

                    if (p.cmds != null)
                    {
                        for (int j = 0; j < p.cmds.Length; j++)
                        {
                            PaymentCmd cmd = p.cmds[j];
                            if (cmd == null || cmd.str == null)
                                continue;
                            string cmdId = cmd.id.ToString();

                            // already executed in a previous cycle but not yet acked -> keep it Executed, do not run again
                            if (AlreadyExecuted(ledger, p.id, cmdId))
                            {
                                pu.cmds[cmdId] = STATUS_EXECUTED;
                                continue;
                            }

                            bool mustBeOnline = cmd.state == STATE_MUST_BE_ONLINE || cmd.str.Contains("{");

                            if (player_infos == null && mustBeOnline)
                            {
                                // player offline and command needs them online -> stays IN PROGRESS on Tip4serv
                                pu.cmds[cmdId] = STATUS_NOT_EXECUTED;
                                continue;
                            }

                            string finalCmd = cmd.str;
                            if (player_infos != null)
                            {
                                #if SEVENDAYSTODIE
                                finalCmd = finalCmd.Replace("{7dtd_username}", player_infos.Name);
                                #elif RUST
                                finalCmd = finalCmd.Replace("{rust_username}", player_infos.Name);
                                #endif
                            }
                            toExecute.Add(new ExecItem { paymentId = p.id, cmdId = cmdId, command = finalCmd });
                        }
                    }
                    update[p.id] = pu;
                }

                // Execute queued commands with a minimum 1s delay between each, then POST the statuses back
                Busy = true;
                ProcessQueue(toExecute, 0, update, key_parts);
            }, this, RequestMethod.GET, headers, 15f);
        }

        private class ExecItem
        {
            public string paymentId;
            public string cmdId;
            public string command;
        }

        // Executes commands one by one, at least 1 second apart, then sends the status update
        private void ProcessQueue(List<ExecItem> queue, int index, Dictionary<string, PaymentUpdate> update, string[] key_parts)
        {
            if (index >= queue.Count)
            {
                SaveLedger(update);
                SendUpdate(update, key_parts);
                Busy = false;
                return;
            }

            ExecItem item = queue[index];
            // an invalid command must not prevent the following ones from running
            try
            {
                exe_command(item.command, new string[] { });
            }
            catch (Exception e)
            {
                Tip4Print("Tip4serv command failed but continuing: " + item.command + " (" + e.Message + ")");
            }
            // mark it executed (we attempted delivery) so it is not sent forever
            PaymentUpdate pu;
            if (update.TryGetValue(item.paymentId, out pu))
                pu.cmds[item.cmdId] = STATUS_EXECUTED;

            // schedule the next command at least 1 second later
            timer.In(1f, () => ProcessQueue(queue, index + 1, update, key_parts));
        }

        // POST the executed/not-executed statuses back to the new route
        private void SendUpdate(Dictionary<string, PaymentUpdate> update, string[] key_parts)
        {
            if (update.Count == 0)
                return;

            string timestamp = GetUnixTime();
            string url = API_BASE + ServerId(key_parts) + "/commands?time=" + timestamp;
            Dictionary<string, string> headers = BuildHeaders(timestamp);
            string body = Utility.ConvertToJson(update);

            webrequest.Enqueue(url, body, (code, response) =>
            {
                if (code == 200)
                {
                    // everything acknowledged: clear the ledger
                    ClearLedger();
                }
                else
                {
                    // keep the ledger so executed commands are not run again on the next cycle
                    Tip4Print("Tip4serv status update failed (HTTP " + code + "). It will be retried on the next cycle.");
                }
            }, this, RequestMethod.POST, headers, 15f);
        }

        private Dictionary<string, PaymentUpdate> LoadLedger()
        {
            try
            {
                var ledger = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, PaymentUpdate>>(RESPONSE_FILE);
                return ledger ?? new Dictionary<string, PaymentUpdate>();
            }
            catch (Exception)
            {
                return new Dictionary<string, PaymentUpdate>();
            }
        }
        private void SaveLedger(Dictionary<string, PaymentUpdate> update)
        {
            Interface.Oxide.DataFileSystem.WriteObject(RESPONSE_FILE, update);
        }
        private void ClearLedger()
        {
            Interface.Oxide.DataFileSystem.WriteObject(RESPONSE_FILE, new Dictionary<string, PaymentUpdate>());
        }
        private bool AlreadyExecuted(Dictionary<string, PaymentUpdate> ledger, string paymentId, string cmdId)
        {
            PaymentUpdate pu;
            string status;
            if (ledger != null && ledger.TryGetValue(paymentId, out pu) && pu.cmds != null && pu.cmds.TryGetValue(cmdId, out status))
                return status == STATUS_EXECUTED;
            return false;
        }

        private string calculateHMAC(string[] key_parts, string timestamp)
        {
            HMACSHA256 Encryptor = new HMACSHA256(Encoding.ASCII.GetBytes(key_parts[1]));
            key_parts[1] = "";
            string Total_Key = string.Join("", key_parts);
            Total_Key += timestamp;
            var signature = Encryptor.ComputeHash(Encoding.ASCII.GetBytes(Total_Key));
            var HMACstr = BitConverter.ToString(signature).Replace("-", "").ToLower();
            HMACstr = Convert.ToBase64String(Encoding.ASCII.GetBytes(HMACstr));
            return HMACstr;
        }
        private IPlayer checkifPlayerIsLoaded(string steam_id, Dictionary<string, IPlayer> players)
        {
            if (string.IsNullOrEmpty(steam_id))
                return null;
            IPlayer SteamPlayer = players.TryGetValue(steam_id, out SteamPlayer) ? SteamPlayer : null;
            if (SteamPlayer == null)
            {
                return null;
            }
            #if RUST
            if (SteamPlayer.IsConnected)
            {
                return SteamPlayer;
            }
            else
            {
                return null;
            }
            #elif SEVENDAYSTODIE
            try {
                //here is the trick we get the position
                //if the position of the player is returned it mean the player is connected
                //on the other case if Position() throw an exception it means that the player ain't connected
                GenericPosition JUNKPOS = SteamPlayer.Position();
                return SteamPlayer;
            } catch(KeyNotFoundException e) {
                return null;
            }
            #endif
            return null;
        }
        private void exe_command(string cmd, string[] CmdArgs)
        {
            Tip4Print("Tip4serv execute command: "+cmd);
            server.Command(cmd, CmdArgs);
        }
    }
}

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