返回 7 个源码总览

rust-donation-plugin-tip4serv

Oxide 插件源码

这个东西是什么

Tip4Serv 对接 Rust/Oxide 的插件源码,主要价值在游戏服发放奖励,不是网页商城。

预览说明

这个项目不是完整商城前台,不能直接像 PHP 店铺那样点购买。这里给你看源码说明、目录用途和关键代码,方便判断它是不是你要的东西。

README

This plugin connects your [Tip4serv.com](https://tip4serv.com?ads=umod) store to your Rust & 7 Days To Die Oxide server. It checks if a player has made a donation on your Tip4Serv store and delivers the order in a minute (group, inventory item...) by typing commands in the server console.

## HMAC authentication

Tip4serv adds a layer of security using HMAC authentication to communicate. It is a strong authentication method that is used by banks [HMAC WIKI](https://en.wikipedia.org/wiki/HMAC)

## Features for starter plan (only 5% fee)

- Unlimited game servers & commands
- Create subscriptions plan
- Commands status tracking
- Stock management
- Deliver roles & messages on Discord
- Create discount coupon
- Add managers for your store
- Purchase email and invoice
- Sales statistics
- Private flow for subscribers
- Custom sub-domain
- Resend commands
- No ads

## Features for PRO members (subscription required)

- Dynamic Dark/Light theme
- Account linking with avatars
- Product page with gallery & video
- GUI colors editor & additional CSS
- Top customers & related products

## Store available in 15 languages

English, Danish, Dutch, French, German, Hungarian, Italian, Norwegian, Polish, Portuguese, Romanian, Russian, Spanish, Swedish and Turkish.

## Several payment methods

Here are the payment methods you can offer your players: Card, Paypal, Venmo, Google Pay, Ideal, Giropay, Bancontact, Sofort, My Bank, Sepa, EPS, BACS, Multibanco, BECS, Przelexy24, BOLETO, OXXO, Mercado Pago.

## Installation via plugin

Open an account on [Tip4serv.com](https://tip4serv.com?ads=umod), follow the instructions and add a Rust or 7 Days To Die server.

- Drag and drop `tip4serv.cs` into the oxide `plugins` directory on your game server.
- Reload the plugin by typing `oxide.reload Tip4serv` in your game server console.
- Set `key` to your tip4serv API key in the config file `tip4serv.json`.
- Reload plugin by typing `oxide.reload Tip4serv` in console.

> You should get this message: **Server has been successfully connected**

## Installation via RCON (Rust only)
Open an account on [Tip4serv.com](https://tip4serv.com/), follow the instructions and add a Rust server.

- Click on Connect with RCON
- Enter your server IP  
- Enter your server RCON port
- Enter your server RCON password

> You should get this message: **Server has been successfully connected**

## Setting up commands on Tip4Serv

***Before setting up your commands on Tip4serv.com, you should know that command work in your server's console (not ingame as an admin).***

Here are some sample commands you can use in the products configuration: [MY PRODUCTS](https://tip4serv.com/dashboard/my-products)

But you can use any plugin commands you want.

## Advertise in public chat

`say Thank you {rust_username} for your donation of {total_paid} {currency}`

Example with colors:

`say {rust_username} just purchased <color=red>VIP</color> from <color=orange>tip4serv.com</color>`

## Give a group or permission

Add a player to a group previously created with oxide.group add [group-name]:

`oxide.usergroup add {steam_id} group-name`

Remove a player from a group:

`oxide.usergroup remove {steam_id} group-name`

Give a permission to a player:

`oxide.grant user {steam_id} permission-name`

Remove a permission from a player:

`oxide.revoke user {steam_id} permission-name`

## Give an inventory item with steam_id (Rust only)

**IMPORTANT: Please select the option [Player must be online] in your product editor**

`inventory.giveto {steam_id} item-short-name quantity`

Example: `inventory.giveto {steam_id} scientist 5`

See [Rust item list](https://www.corrosionhour.com/rust-item-list/)

## Give an inventory item with username (Rust only)

**IMPORTANT: Please select the option [Player must be online] in your product editor**

`inventory.giveto {rust_username} item-short-name quantity`

Example: `inventory.giveto {rust_username} scientist 5`

## Give an inventory item or a kit with Give plugin

**IMPORTANT: Please select the option [Player must be online] in your product editor**

You can also use [Give plugin](https://umod.org/plugins/give)

Give an item to a player inventory with Give plugin:

`giveto {steam_id} item-short-name quantity`

Example: `giveto {steam_id} fun.guitar 1`

Give a kit to a player with Give plugin:

`givekitto {steam_id} kit-name`

## Give money

Give money to a player with [Economics plugin](https://umod.org/plugins/economics)

`deposit {steam_id} amount`

## Give points (Rust only)

Give points to a player with [Server Rewards](https://umod.org/plugins/server-rewards)

`sr add {rust_username} amount`

## Quantity multiplier

You can also multiply the quantity choosen by the customer like this: `{quantity*50}`

Note: You must first activate the **Allow quantity choice** option in your product.

Use this command on Tip4serv if you want to sell bundles of $200 with economics plugin:
`deposit {steam_id} {quantity*200}`

This will run in your server console after a purchase if the player buys product 4 times:
`deposit 76561198181797231 800`

## Need help?

[Documentation](https://docs.tip4serv.com)

[Contact us](https://tip4serv.com/contact)

插件代码

/***********************************************************************************************************************/
/*** 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;