这个东西是什么
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;