C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Plugins;
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace Oxide.Plugins {
[Info("Tebex", "Tebex", "2.0.13")]
[Description("Official support for the Tebex server monetization platform")]
public class TebexPlugin : CovalencePlugin {
private static TebexOxideAdapter _adapter;
private Dictionary<string, DateTime> _lastNoteGiven = new Dictionary<string, DateTime>();
public static string GetPluginVersion() {
return "2.0.13";
}
public TebexPlatform GetPlatform(IServer server) {
String gameId = "";
#if RUST
gameId = "Rust";
#else
gameId = "7 Days";
#endif
return new TebexPlatform(gameId, GetPluginVersion(), new TebexTelemetry("Oxide", server.Version, server.Protocol));
}
private void Init() {
// Setup our API and adapter
_adapter = new TebexOxideAdapter(this);
TebexApi.Instance.InitAdapter(_adapter);
BaseTebexAdapter.PluginConfig = Config.ReadObject<BaseTebexAdapter.TebexConfig>();
if (!Config.Exists()) {
//Creates new config file
LoadConfig();
}
// Register permissions
permission.RegisterPermission("tebexplugin.secret", this);
permission.RegisterPermission("tebexplugin.sendlink", this);
permission.RegisterPermission("tebexplugin.forcecheck", this);
permission.RegisterPermission("tebexplugin.refresh", this);
permission.RegisterPermission("tebexplugin.report", this);
permission.RegisterPermission("tebexplugin.ban", this);
permission.RegisterPermission("tebexplugin.lookup", this);
permission.RegisterPermission("tebexplugin.debug", this);
permission.RegisterPermission("tebexplugin.setup", this);
// Register user permissions
permission.RegisterPermission("tebexplugin.info", this);
permission.RegisterPermission("tebexplugin.categories", this);
permission.RegisterPermission("tebexplugin.packages", this);
permission.RegisterPermission("tebexplugin.checkout", this);
// Check if auto reporting is disabled and show a warning if so.if (!BaseTebexAdapter.PluginConfig.AutoReportingEnabled)
{
_adapter.LogWarning("Auto reporting issues to Tebex is disabled.", "To enable, please set 'AutoReportingEnabled' to 'true' in config/Tebex.json");
PluginEvent.IS_DISABLED = true;
}
// Check if secret key has been set.If so, get store information and place in cache
if (BaseTebexAdapter.PluginConfig.SecretKey != "your-secret-key-here") {
_adapter.FetchStoreInfo((info => {
PluginEvent.SERVER_IP = server.Address.ToString();
PluginEvent.SERVER_ID = info.ServerInfo.Id.ToString();
PluginEvent.STORE_URL = info.AccountInfo.Domain;
new PluginEvent(this, this.GetPlatform(server), EnumEventLevel.INFO, "Server Init").Send(_adapter);
_adapter.SetSecretKeyValidated(true);
}
));
return;
}
_adapter.LogInfo("Tebex detected a new configuration file.");
_adapter.LogInfo("Use tebex:secret <secret> to add your store's secret key.");
_adapter.LogInfo("Alternatively, add the secret key to 'Tebex.json' and reload the plugin.");
}
public WebRequests WebRequests() {
return webrequest;
}
public IPlayerManager PlayerManager() {
return players;
}
public PluginTimers PluginTimers() {
return timer;
}
public IServer Server() {
return server;
}
public string GetGame() {
return game;
}
public void Warn(string message) {
if (!BaseTebexAdapter.PluginConfig.SuppressWarnings) {
LogWarning("{0}", message);
}
}
public void Error(string message) {
if (!BaseTebexAdapter.PluginConfig.SuppressErrors) {
LogError("{0}", message);
}
}
public void Info(string info) {
Puts("{0}", info);
}
private void OnUserConnected(IPlayer player) {
// Check for default config and inform the admin that configuration is waiting.if (player.IsAdmin && BaseTebexAdapter.PluginConfig.SecretKey == "your-secret-key-here")
{
player.Command("chat.add", 0, player.Id, "Tebex is not configured.Use tebex:secret <secret> from the F1 menu to add your key.");
player.Command("chat.add", 0, player.Id, "Get your secret key by logging in at:");
player.Command("chat.add", 0, player.Id, "https://tebex.io/");
}
_adapter.LogDebug($"Player login event: {player.Id}@{player.Address}");
_adapter.OnUserConnected(player.Id, player.Address);
}
#if RUST // VIP notes are enabled on Rust only
void OnLootEntity(BasePlayer player, BaseEntity entity) {
if (!BaseTebexAdapter.PluginConfig.VipNotesEnabled) {
return;
}
// If the user configured no VIP codes, skip.if (BaseTebexAdapter.PluginConfig.VipCodes.Count == 0)
{
return;
}
// Check if we have a target loot crate by prefab name
string prefabName = entity?.ShortPrefabName;
if (string.IsNullOrEmpty(prefabName) || (prefabName != "crate_normal_2" && prefabName != "crate_normal_2_food" && prefabName != "crate_normal_2_tools")) {
return;
}
// Ensure the player was provided
string userID = player?.UserIDString;
if (string.IsNullOrEmpty(userID)) {
return;
}
// If the player is already in a VIP group, they won't receive a VIP note
if (BaseTebexAdapter.PluginConfig.VipGroups.Any(group => permission.UserHasGroup(userID, group))) {
return;
}
// Make sure we haven't spawned a note too recently for the user.if (_lastNoteGiven.TryGetValue(userID, out DateTime lastGivenTime) &&
(DateTime.Now - lastGivenTime).Seconds < BaseTebexAdapter.PluginConfig.NoteCooldown) {
return;
}
// Spawn chance check
if (Oxide.Core.Random.Range(0.0f, 1.0f) > BaseTebexAdapter.PluginConfig.NoteSpawnChance) {
return;
}
// Create the note
Item item = ItemManager.CreateByName("note", 1, 0UL);
if (item == null) {
return;
}
List<string> messages = BaseTebexAdapter.PluginConfig.NoteMessages["en"];
string message = messages[Oxide.Core.Random.Range(0, messages.Count)];
string vipCode = BaseTebexAdapter.PluginConfig.VipCodes[Oxide.Core.Random.Range(0, BaseTebexAdapter.PluginConfig.VipCodes.Count)];
var info = BaseTebexAdapter.Cache.Instance.Get("information").Value as TebexApi.TebexStoreInfo;
if (info != null) {
item.text = string.Format(message, player.displayName, info.AccountInfo.Domain, vipCode);
item.MarkDirty();
player.GiveItem(item, BaseEntity.GiveItemReason.Generic);
_lastNoteGiven[userID] = DateTime.Now;
}
else {
_adapter.LogDebug("Store information not present in cache when trying to spawn VIP note!");
}
}
#endif
private void OnServerShutdown() {
// Make sure join queue is always emptied on shutdown
_adapter.ProcessJoinQueue();
}
private void PrintCategories(IPlayer player, List<TebexApi.Category> categories) {
// Index counter for selecting displayed items
var categoryIndex = 1;
var packIndex = 1;
// Line separator for category response
_adapter.ReplyPlayer(player, "---------------------------------");
// Sort categories in order and display
var orderedCategories = categories.OrderBy(category => category.Order).ToList();
for (int i = 0; i < categories.Count; i++) {
var listing = orderedCategories[i];
_adapter.ReplyPlayer(player, $"[C{categoryIndex}] {listing.Name}");
categoryIndex++;
// Show packages for the category in order from API
if (listing.Packages.Count > 0) {
var packages = listing.Packages.OrderBy(category => category.Order).ToList();
_adapter.ReplyPlayer(player, $"Packages");
foreach (var package in packages) {
// Add additional flair on sales
if (package.Sale != null && package.Sale.Active) {
_adapter.ReplyPlayer(player, $"-> [P{packIndex}] {package.Name} {package.Price - package.Sale.Discount} (SALE {package.Sale.Discount} off)");
}
else {
_adapter.ReplyPlayer(player, $"-> [P{packIndex}] {package.Name} {package.Price}");
}
packIndex++;
}
}
// At the end of each category add a line separator
_adapter.ReplyPlayer(player, "---------------------------------");
}
}
private static void PrintPackages(IPlayer player, List<TebexApi.Package> packages) {
// Index counter for selecting displayed items
var packIndex = 1;
_adapter.ReplyPlayer(player, "---------------------------------");
_adapter.ReplyPlayer(player, " PACKAGES AVAILABLE ");
_adapter.ReplyPlayer(player, "---------------------------------");
// Sort categories in order and display
var orderedPackages = packages.OrderBy(package => package.Order).ToList();
for (var i = 0; i < packages.Count; i++) {
var package = orderedPackages[i];
// Add additional flair on sales
_adapter.ReplyPlayer(player, $"[P{packIndex}] {package.Name}");
_adapter.ReplyPlayer(player, $"Category: {package.Category.Name}");
_adapter.ReplyPlayer(player, $"Description: {package.Description}");
if (package.Sale != null && package.Sale.Active) {
_adapter.ReplyPlayer(player, $"Original Price: {package.Price} {package.GetFriendlyPayFrequency()} SALE: {package.Sale.Discount} OFF!");
}
else {
_adapter.ReplyPlayer(player, $"Price: {package.Price} {package.GetFriendlyPayFrequency()}");
}
_adapter.ReplyPlayer(player, $"Purchase with 'tebex.checkout P{packIndex}' or 'tebex.checkout {package.Id}'");
_adapter.ReplyPlayer(player, "--------------------------------");
packIndex++;
}
}
protected override void LoadDefaultConfig() {
Config.WriteObject(GetDefaultConfig(), true);
}
private BaseTebexAdapter.TebexConfig GetDefaultConfig() {
return new BaseTebexAdapter.TebexConfig();
}
[Command("tebex.secret", "tebex:secret")]
private void TebexSecretCommand(IPlayer player, string command, string[] args) {
// Secret can only be ran as the admin
if (!player.HasPermission("tebexplugin.secret")) {
_adapter.ReplyPlayer(player, "You do not have permission to run this command.");
_adapter.ReplyPlayer(player, "If you are an admin, grant permission to use `tebex.secret`");
return;
}
if (args.Length != 1) {
_adapter.ReplyPlayer(player, "Invalid syntax.Usage: \"tebex.secret <secret>\"");
return;
}
_adapter.ReplyPlayer(player, "Setting your secret key...");
BaseTebexAdapter.PluginConfig.SecretKey = args[0];
Config.WriteObject(BaseTebexAdapter.PluginConfig);
// Reset store info so that we don't fetch from the cache
BaseTebexAdapter.Cache.Instance.Remove("information");
// Any failure to set secret key is logged to console automatically
_adapter.FetchStoreInfo(info => {
_adapter.ReplyPlayer(player, $"Successfully set your secret key.");
_adapter.ReplyPlayer(player, $"Store set as: {info.ServerInfo.Name} for the web store {info.AccountInfo.Name}");
PluginEvent.SERVER_ID = info.ServerInfo.Id.ToString();
PluginEvent.STORE_URL = info.AccountInfo.Domain;
_adapter.SetSecretKeyValidated(true);
}
);
}
[Command("tebex.info", "tebex:info", "tebex.information", "tebex:information")]
private void TebexInfoCommand(IPlayer player, string command, string[] args) {
if (!player.HasPermission("tebexplugin.info")) {
_adapter.ReplyPlayer(player, "You do not have permission to run that command.");
return;
}
_adapter.ReplyPlayer(player, "Getting store information...");
_adapter.FetchStoreInfo(info => {
_adapter.ReplyPlayer(player, "Information for this server:");
_adapter.ReplyPlayer(player, $"{info.ServerInfo.Name} for webstore {info.AccountInfo.Name}");
_adapter.ReplyPlayer(player, $"Server prices are in {info.AccountInfo.Currency.Iso4217}");
_adapter.ReplyPlayer(player, $"Webstore domain {info.AccountInfo.Domain}");
}
);
}
[Command("tebex.checkout", "tebex:checkout")]
private void TebexCheckoutCommand(IPlayer player, string command, string[] args) {
if (!player.HasPermission("tebexplugin.checkout")) {
_adapter.ReplyPlayer(player, "You do not have permission to run that command.");
return;
}
if (player.IsServer) {
_adapter.ReplyPlayer(player, $"{command} cannot be executed via server.Use tebex:sendlink <username> <packageId> to specify a target player.");
return;
}
// Only argument will be the package ID of the item in question
if (args.Length != 1) {
_adapter.ReplyPlayer(player, "Invalid syntax: Usage \"tebex.checkout <packageId>\"");
return;
}
// Lookup the package by provided input and respond with the checkout URL
var package = _adapter.GetPackageByShortCodeOrId(args[0].Trim());
if (package == null) {
_adapter.ReplyPlayer(player, "A package with that ID was not found.");
return;
}
_adapter.ReplyPlayer(player, "Creating your checkout URL...");
_adapter.CreateCheckoutUrl(player.Name, package, checkoutUrl => {
player.Command("chat.add", 0, player.Id, "Please visit the following URL to complete your purchase:");
player.Command("chat.add", 0, player.Id, $"{checkoutUrl.Url}");
}
, error => {
_adapter.ReplyPlayer(player, $"{error.ErrorMessage}");
}
);
}
[Command("tebex.help", "tebex:help")]
private void TebexHelpCommand(IPlayer player, string command, string[] args) {
_adapter.ReplyPlayer(player, "Tebex Commands Available:");
if (player.IsAdmin) //Always show help to admins regardless of perms, for new server owners
{
_adapter.ReplyPlayer(player, "-- Administrator Commands --");
_adapter.ReplyPlayer(player, "tebex.secret <secretKey> - Sets your server's secret key.");
_adapter.ReplyPlayer(player, "tebex.debug <on/off> - Enables or disables debug logging.");
_adapter.ReplyPlayer(player, "tebex.sendlink <player> <packId> - Sends a purchase link to the provided player.");
_adapter.ReplyPlayer(player, "tebex.forcecheck - Forces the command queue to check for any pending purchases.");
_adapter.ReplyPlayer(player, "tebex.refresh - Refreshes store information, packages, categories, etc.");
_adapter.ReplyPlayer(player, "tebex.report - Generates a report for the Tebex support team.");
_adapter.ReplyPlayer(player, "tebex.ban <playerId> - Bans a player from using your Tebex store.");
_adapter.ReplyPlayer(player, "tebex.lookup <playerId> - Looks up store statistics for the given player.");
}
_adapter.ReplyPlayer(player, "-- User Commands --");
_adapter.ReplyPlayer(player, "tebex.info - Get information about this server's store.");
_adapter.ReplyPlayer(player, "tebex.categories - Shows all item categories available on the store.");
_adapter.ReplyPlayer(player, "tebex.packages <opt:categoryId> - Shows all item packages available in the store or provided category.");
_adapter.ReplyPlayer(player, "tebex.checkout <packId> - Creates a checkout link for an item.Visit to purchase.");
}
[Command("tebex.debug", "tebex:debug")]
private void TebexDebugCommand(IPlayer player, string command, string[] args) {
if (!player.HasPermission("tebexplugin.debug")) {
_adapter.ReplyPlayer(player, "You do not have permission to run that command.");
return;
}
if (args.Length != 1) {
_adapter.ReplyPlayer(player, "Usage: tebex.debug <on/off>");
return;
}
if (args[0].Equals("on")) {
BaseTebexAdapter.PluginConfig.DebugMode = true;
Config.WriteObject(BaseTebexAdapter.PluginConfig);
_adapter.ReplyPlayer(player, "Debug mode is enabled.");
}
else if (args[0].Equals("off")) {
BaseTebexAdapter.PluginConfig.DebugMode = false;
Config.WriteObject(BaseTebexAdapter.PluginConfig);
_adapter.ReplyPlayer(player, "Debug mode is disabled.");
}
else {
_adapter.ReplyPlayer(player, "Usage: tebex.debug <on/off>");
}
}
[Command("tebex.forcecheck", "tebex:forcecheck")]
private void TebexForceCheckCommand(IPlayer player, string command, string[] args) {
if (!player.HasPermission("tebexplugin.forcecheck")) {
_adapter.ReplyPlayer(player, "You do not have permission to run that command.");
return;
}
_adapter.RefreshStoreInformation(true);
_adapter.ProcessCommandQueue(true);
_adapter.ProcessJoinQueue(true);
_adapter.DeleteExecutedCommands(true);
}
[Command("tebex.refresh", "tebex:refresh")]
private void TebexRefreshCommand(IPlayer player, string command, string[] args) {
if (!player.HasPermission("tebexplugin.refresh")) {
_adapter.ReplyPlayer(player, "You do not have permission to run that command.");
return;
}
_adapter.ReplyPlayer(player, "Refreshing listings...");
BaseTebexAdapter.Cache.Instance.Remove("packages");
BaseTebexAdapter.Cache.Instance.Remove("categories");
_adapter.RefreshListings((code, body) => {
if (BaseTebexAdapter.Cache.Instance.HasValid("packages") && BaseTebexAdapter.Cache.Instance.HasValid("categories")) {
var packs = (List<TebexApi.Package>)BaseTebexAdapter.Cache.Instance.Get("packages").Value;
var categories = (List<TebexApi.Category>)BaseTebexAdapter.Cache.Instance.Get("categories").Value;
_adapter.ReplyPlayer(player, $"Fetched {packs.Count} packages out of {categories.Count} categories");
}
}
);
}
[Command("tebex.ban", "tebex:ban")]
private void TebexBanCommand(IPlayer commandRunner, string command, string[] args) {
if (!commandRunner.HasPermission("tebexplugin.ban")) {
_adapter.ReplyPlayer(commandRunner, $"{command} can only be used by administrators.");
return;
}
if (args.Length < 2) {
_adapter.ReplyPlayer(commandRunner, $"Usage: tebex.ban <playerName> <reason>");
return;
}
var player = players.FindPlayer(args[0].Trim());
if (player == null) {
_adapter.ReplyPlayer(commandRunner, $"Could not find that player on the server.");
return;
}
var reason = string.Join(" ", args.Skip(1));
_adapter.ReplyPlayer(commandRunner, $"Processing ban for player {player.Name} with reason '{reason}'");
_adapter.BanPlayer(player.Name, player.Address, reason, (code, body) => {
_adapter.ReplyPlayer(commandRunner, "Player banned successfully.");
}
, error => {
_adapter.ReplyPlayer(commandRunner, $"Could not ban player.{error.ErrorMessage}");
}
);
}
[Command("tebex.unban", "tebex:unban")]
private void TebexUnbanCommand(IPlayer commandRunner, string command, string[] args) {
if (!commandRunner.IsAdmin) {
_adapter.ReplyPlayer(commandRunner, $"{command} can only be used by administrators.");
return;
}
_adapter.ReplyPlayer(commandRunner, $"You must unban players via your webstore.");
}
[Command("tebex.categories", "tebex:categories", "tebex.listings", "tebex:listings")]
private void TebexCategoriesCommand(IPlayer player, string command, string[] args) {
if (!player.HasPermission("tebexplugin.categories")) {
_adapter.ReplyPlayer(player, "You do not have permission to run that command.");
return;
}
_adapter.GetCategories(categories => {
PrintCategories(player, categories);
}
);
}
[Command("tebex.packages", "tebex:packages")]
private void TebexPackagesCommand(IPlayer player, string command, string[] args) {
if (!player.HasPermission("tebexplugin.packages")) {
_adapter.ReplyPlayer(player, "You do not have permission to run that command.");
return;
}
_adapter.GetPackages(packages => {
PrintPackages(player, packages);
}
);
}
[Command("tebex.lookup", "tebex:lookup")]
private void TebexLookupCommand(IPlayer player, string command, string[] args) {
if (!player.HasPermission("tebexplugin.lookup")) {
_adapter.ReplyPlayer(player, "You do not have permission to run that command.");
return;
}
if (args.Length != 1) {
_adapter.ReplyPlayer(player, $"Usage: tebex.lookup <playerId/playerUsername>");
return;
}
// Try to find the given player
var target = players.FindPlayer(args[0]);
if (target == null) {
_adapter.ReplyPlayer(player, $"Could not find a player matching the name or id {args[0]}.");
return;
}
_adapter.GetUser(target.Id, (code, body) => {
var response = JsonConvert.DeserializeObject<TebexApi.UserInfoResponse>(body);
_adapter.ReplyPlayer(player, $"Username: {response.Player.Username}");
_adapter.ReplyPlayer(player, $"Id: {response.Player.Id}");
_adapter.ReplyPlayer(player, $"Payments Total: ${response.Payments.Sum(payment => payment.Price)}");
_adapter.ReplyPlayer(player, $"Chargeback Rate: {response.ChargebackRate}%");
_adapter.ReplyPlayer(player, $"Bans Total: {response.BanCount}");
_adapter.ReplyPlayer(player, $"Payments: {response.Payments.Count}");
}
, error => {
_adapter.ReplyPlayer(player, error.ErrorMessage);
}
);
}
[Command("tebex.sendlink", "tebex:sendlink")]
private void TebexSendLinkCommand(IPlayer commandRunner, string command, string[] args) {
if (!commandRunner.HasPermission("tebexplugin.sendlink")) {
_adapter.ReplyPlayer(commandRunner, "You must be an administrator to run this command.");
return;
}
if (args.Length != 2) {
_adapter.ReplyPlayer(commandRunner, "Usage: tebex.sendlink <username> <packageId>");
return;
}
var username = args[0].Trim();
var package = _adapter.GetPackageByShortCodeOrId(args[1].Trim());
if (package == null) {
_adapter.ReplyPlayer(commandRunner, "A package with that ID was not found.");
return;
}
_adapter.ReplyPlayer(commandRunner, $"Creating checkout URL with package '{package.Name}'|{package.Id} for player {username}");
var player = players.FindPlayer(username);
if (player == null) {
_adapter.ReplyPlayer(commandRunner, $"Couldn't find that player on the server.");
return;
}
_adapter.CreateCheckoutUrl(player.Name, package, checkoutUrl => {
player.Command("chat.add", 0, player.Id, "Please visit the following URL to complete your purchase:");
player.Command("chat.add", 0, player.Id, $"{checkoutUrl.Url}");
}
, error => {
_adapter.ReplyPlayer(player, $"{error.ErrorMessage}");
}
);
}
}
public abstract class BaseTebexAdapter {
public static BaseTebexAdapter Instance => _adapterInstance.Value;
private static readonly Lazy<BaseTebexAdapter> _adapterInstance = new Lazy<BaseTebexAdapter>();
public static TebexConfig PluginConfig {
get;
set;
}
= new TebexConfig();
private static bool _isSecretKeyValidated = false;
/** For rate limiting command queue based on next_check */
private static DateTime _nextCheckCommandQueue = DateTime.Now;
// Time checks for our plugin timers.private static DateTime _nextCheckDeleteCommands = DateTime.Now;
private static DateTime _nextCheckJoinQueue = DateTime.Now;
private static DateTime _nextCheckRefresh = DateTime.Now;
private static List<TebexApi.TebexJoinEventInfo> _eventQueue = new List<TebexApi.TebexJoinEventInfo>();
/** For storing successfully executed commands and deleting them from API */
protected static readonly List<TebexApi.Command> ExecutedCommands = new List<TebexApi.Command>();
/** Allow pausing all web requests if rate limits are received from remote */
protected bool IsRateLimited = false;
public abstract void Init();
public void DeleteExecutedCommands(bool ignoreWaitCheck = false) {
LogDebug("Deleting executed commands...");
if (!IsSecretKeyValidated()) {
LogDebug("Store key is not set or incorrect.Skipping command queue.");
return;
}
if (!CanProcessNextDeleteCommands() && !ignoreWaitCheck) {
LogDebug("Skipping check for completed commands - not time to be processed");
return;
}
if (ExecutedCommands.Count == 0) {
LogDebug(" No commands to flush.");
return;
}
LogDebug($" Found {ExecutedCommands.Count} commands to flush.");
List<int> ids = new List<int>();
foreach (var command in ExecutedCommands) {
ids.Add(command.Id);
}
_nextCheckDeleteCommands = DateTime.Now.AddSeconds(60);
TebexApi.Instance.DeleteCommands(ids.ToArray(), (code, body) => {
LogDebug("Successfully flushed completed commands.");
ExecutedCommands.Clear();
}
, (error) => {
LogDebug($"Failed to flush completed commands: {error.ErrorMessage}");
}
, (code, body) => {
LogDebug($"Unexpected error while flushing completed commands.API response code {code}.Response body follows:");
LogDebug(body);
}
);
}
/**
* Logs a warning to the console and game log.*/
public abstract void LogWarning(string message, string solution);
public abstract void LogWarning(string message, string solution, Dictionary<String, String> metadata);
/**
* Logs an error to the console and game log.*/
public abstract void LogError(string message);
public abstract void LogError(string message, Dictionary<String, String> metadata);
/**
* Logs information to the console and game log.*/
public abstract void LogInfo(string message);
/**
* Logs debug information to the console and game log if debug mode is enabled.*/
public abstract void LogDebug(string message);
public void OnUserConnected(string steam64Id, string ip) {
var joinEvent = new TebexApi.TebexJoinEventInfo(steam64Id, "server.join", DateTime.Now, ip);
_eventQueue.Add(joinEvent);
// If we're already over a threshold, go ahead and send the events.if (_eventQueue.Count > 10)
{
ProcessJoinQueue();
}
}
public class TebexConfig {
// Enables additional debug logging, which may show raw user info in console.public bool DebugMode = false;
public bool SuppressWarnings = false;
public bool SuppressErrors = false;
// Automatically sends detected issues to Tebex
public bool AutoReportingEnabled = true;
//public bool AllowGui = false;
public string SecretKey = "your-secret-key-here";
public int CacheLifetime = 30;
//#if RUST
[JsonProperty(PropertyName = "VIP Notes Enabled")]
public bool VipNotesEnabled {
get;
set;
}
= false;
[JsonProperty(PropertyName = "VIP Codes")]
public List<string> VipCodes {
get;
set;
}
= new List<string>();
[JsonProperty(PropertyName = "VIP Groups")]
public List<string> VipGroups {
get;
set;
}
= new List<string>();
[JsonProperty(PropertyName = "Note Spawn Chance")]
public float NoteSpawnChance {
get;
set;
}
= 0.02f;
// 2%
[JsonProperty(PropertyName = "Note Cooldown (Seconds)")]
public float NoteCooldown {
get;
set;
}
= 600;
// 10 minutes
[JsonProperty(PropertyName = "Note Messages")]
public Dictionary<string, List<string>> NoteMessages {
get;
set;
}
= new Dictionary<string, List<string>> {
{
"en", new List<string> {
"Hey {0}, grab your exclusive 10% OFF VIP offer with code {2} at {1}! Limited time only!", "{0}, seize your special discount! Use code {2} at {1} for a limited time offer!", "Special surprise for you, {0}! Use code {2} at {1} for 10% off and enjoy your VIP benefits!"
}
}
,
}
;
//#endif
}
public class Cache {
public static Cache Instance => _cacheInstance.Value;
private static readonly Lazy<Cache> _cacheInstance = new Lazy<Cache>(() => new Cache());
private static Dictionary<string, CachedObject> _cache = new Dictionary<string, CachedObject>();
public CachedObject Get(string key) {
if (_cache.ContainsKey(key)) {
return _cache[key];
}
return null;
}
public void Set(string key, CachedObject obj) {
_cache[key] = obj;
}
public bool HasValid(string key) {
return _cache.ContainsKey(key) && !_cache[key].HasExpired();
}
public void Clear() {
_cache.Clear();
}
public void Remove(string key) {
_cache.Remove(key);
}
}
public class CachedObject {
public object Value {
get;
private set;
}
private DateTime _expires;
public CachedObject(object obj, int minutesValid) {
Value = obj;
_expires = DateTime.Now.AddMinutes(minutesValid);
}
public bool HasExpired() {
return DateTime.Now > _expires;
}
}
/** Callback type to use /information response */
public delegate void FetchStoreInfoResponse(TebexApi.TebexStoreInfo info);
/**
* Returns the store's /information payload.Info is cached according to configured cache lifetime.*/
public void FetchStoreInfo(FetchStoreInfoResponse response) {
if (Cache.Instance.HasValid("information")) {
response?.Invoke((TebexApi.TebexStoreInfo)Cache.Instance.Get("information").Value);
}
else {
TebexApi.Instance.Information((code, body) => {
var storeInfo = JsonConvert.DeserializeObject<TebexApi.TebexStoreInfo>(body);
if (storeInfo == null) {
LogError("Failed to parse fetched store information!", new Dictionary<string, string>() {
{
"response", body
}
,
}
);
return;
}
Cache.Instance.Set("information", new CachedObject(storeInfo, PluginConfig.CacheLifetime));
response?.Invoke(storeInfo);
}
);
}
}
/** Callback type for response from creating checkout url */
public delegate void CreateCheckoutUrlResponse(TebexApi.CheckoutUrlPayload checkoutUrl);
public TebexApi.Package GetPackageByShortCodeOrId(string value) {
var shortCodes = (Dictionary<String, TebexApi.Package>)Cache.Instance.Get("packageShortCodes").Value;
if (shortCodes.ContainsKey(value)) {
return shortCodes[value];
}
// No short code found, assume it's a package ID
var packages = (List<TebexApi.Package>)Cache.Instance.Get("packages").Value;
foreach (var package in packages) {
if (package.Id.ToString() == value) {
return package;
}
}
// Package not found
return null;
}
/**
* Refreshes cached categories and packages from the Tebex API.Can be used by commands or with no arguments
* to update the information while the server is idle.*/
public void RefreshListings(TebexApi.ApiSuccessCallback onSuccess = null) {
// Get our categories from the /listing endpoint as it contains all category data
TebexApi.Instance.GetListing((code, body) => {
var response = JsonConvert.DeserializeObject<TebexApi.ListingsResponse>(body);
if (response == null) {
LogError("Could not get refresh all listings!", new Dictionary<string, string>() {
{
"response", body
}
,
}
);
return;
}
Cache.Instance.Set("categories", new CachedObject(response.categories, PluginConfig.CacheLifetime));
if (onSuccess != null) {
onSuccess.Invoke(code, body);
}
}
);
// Get our packages from a verbose get all packages call so that we always have the description
// of the package cached.TebexApi.Instance.GetAllPackages(true, (code, body) =>
{
var response = JsonConvert.DeserializeObject<List<TebexApi.Package>>(body);
if (response == null) {
LogError("Could not refresh package listings!", new Dictionary<string, string>() {
{
"response", body
}
}
);
return;
}
Cache.Instance.Set("packages", new CachedObject(response, PluginConfig.CacheLifetime));
// Generate and save shortcodes for each package
var orderedPackages = response.OrderBy(package => package.Order).ToList();
var shortCodes = new Dictionary<String, TebexApi.Package>();
for (var i = 0; i < orderedPackages.Count; i++) {
var package = orderedPackages[i];
shortCodes.Add($"P{i + 1}", package);
}
Cache.Instance.Set("packageShortCodes", new CachedObject(shortCodes, PluginConfig.CacheLifetime));
onSuccess?.Invoke(code, body);
}
);
}
/** Callback type for getting all categories */
public delegate void GetCategoriesResponse(List<TebexApi.Category> categories);
/**
* Gets all categories and their packages (no description) from the API.Response is cached according to the
* configured cache lifetime.*/
public void GetCategories(GetCategoriesResponse onSuccess, TebexApi.ServerErrorCallback onServerError = null) {
if (Cache.Instance.HasValid("categories")) {
onSuccess.Invoke((List<TebexApi.Category>)Cache.Instance.Get("categories").Value);
}
else {
TebexApi.Instance.GetListing((code, body) => {
var response = JsonConvert.DeserializeObject<TebexApi.ListingsResponse>(body);
if (response == null) {
onServerError?.Invoke(code, body);
return;
}
Cache.Instance.Set("categories", new CachedObject(response.categories, PluginConfig.CacheLifetime));
onSuccess.Invoke(response.categories);
}
);
}
}
/** Callback type for working with packages received from the API */
public delegate void GetPackagesResponse(List<TebexApi.Package> packages);
/** Gets all package info from API.Response is cached according to the configured cache lifetime.*/
public void GetPackages(GetPackagesResponse onSuccess, TebexApi.ServerErrorCallback onServerError = null) {
try {
if (Cache.Instance.HasValid("packages")) {
onSuccess.Invoke((List<TebexApi.Package>)Cache.Instance.Get("packages").Value);
}
else {
// Updates both packages and shortcodes in the cache
RefreshListings((code, body) => {
onSuccess.Invoke((List<TebexApi.Package>)Cache.Instance.Get("packages").Value);
}
);
}
}
catch (Exception e) {
LogError("An error occurred while getting your store's packages." + e.Message, new Dictionary<string, string>() {
{
"trace", e.StackTrace
}
, {
"message", e.Message
}
}
);
}
}
// Periodically keeps store info updated from the API
public void RefreshStoreInformation(bool ignoreWaitCheck = false) {
LogDebug("Refreshing store information...");
if (!IsSecretKeyValidated()) {
LogDebug("Store key is not set or incorrect.Skipping command queue.");
return;
}
// Calling places the information in the cache
if (!CanProcessNextRefresh() && !ignoreWaitCheck) {
LogDebug(" Skipping store info refresh - not time to be processed");
return;
}
_nextCheckRefresh = DateTime.Now.AddMinutes(15);
FetchStoreInfo(info => {
}
);
// automatically stores in the cache
}
public void ProcessJoinQueue(bool ignoreWaitCheck = false) {
LogDebug("Processing player join queue...");
if (!IsSecretKeyValidated()) {
LogDebug("Store key is not set or incorrect.Skipping command queue.");
return;
}
if (!CanProcessNextJoinQueue() && !ignoreWaitCheck) {
LogDebug(" Skipping join queue - not time to be processed");
return;
}
_nextCheckJoinQueue = DateTime.Now.AddSeconds(60);
if (_eventQueue.Count > 0) {
LogDebug($" Found {_eventQueue.Count} join events.");
TebexApi.Instance.PlayerJoinEvent(_eventQueue, (code, body) => {
LogDebug("Join queue cleared successfully.");
_eventQueue.Clear();
}
, error => {
LogError($"Could not process join queue - error response from API: {error.ErrorMessage}");
}
, (code, body) => {
LogError("Could not process join queue - unexpected server error.", new Dictionary<string, string>() {
{
"response", body
}
, {
"code", code.ToString()
}
,
}
);
}
);
}
else // Empty queue
{
LogDebug($" No recent join events.");
}
}
public bool CanProcessNextCommandQueue() {
return DateTime.Now > _nextCheckCommandQueue;
}
public bool CanProcessNextDeleteCommands() {
return DateTime.Now > _nextCheckDeleteCommands;
}
public bool CanProcessNextJoinQueue() {
return DateTime.Now > _nextCheckJoinQueue;
}
public bool CanProcessNextRefresh() {
return DateTime.Now > _nextCheckRefresh;
}
public void ProcessCommandQueue(bool ignoreWaitCheck = false) {
LogDebug("Processing command queue...");
if (!IsSecretKeyValidated()) {
LogDebug("Store key is not set or incorrect.Skipping command queue.");
return;
}
if (!CanProcessNextCommandQueue() && !ignoreWaitCheck) {
var secondsToWait = (int)(_nextCheckCommandQueue - DateTime.Now).TotalSeconds;
LogDebug($" Tried to run command queue, but should wait another {secondsToWait} seconds.");
return;
}
// Get the state of the command queue
TebexApi.Instance.GetCommandQueue((cmdQueueCode, cmdQueueResponseBody) => {
var response = JsonConvert.DeserializeObject<TebexApi.CommandQueueResponse>(cmdQueueResponseBody);
if (response == null) {
LogError("Failed to get command queue.Could not parse response from API.", new Dictionary<string, string>() {
{
"response", cmdQueueResponseBody
}
, {
"code", cmdQueueCode.ToString()
}
,
}
);
return;
}
// Set next available check time
_nextCheckCommandQueue = DateTime.Now.AddSeconds(response.Meta.NextCheck);
// Process offline commands immediately
if (response.Meta != null && response.Meta.ExecuteOffline) {
LogDebug("Requesting offline commands from API...");
TebexApi.Instance.GetOfflineCommands((code, offlineCommandsBody) => {
var offlineCommands = JsonConvert.DeserializeObject<TebexApi.OfflineCommandsResponse>(offlineCommandsBody);
if (offlineCommands == null) {
LogError("Failed to get offline commands.Could not parse response from API.", new Dictionary<string, string>() {
{
"code", code.ToString()
}
, {
"responseBody", offlineCommandsBody
}
}
);
return;
}
LogDebug($"Found {offlineCommands.Commands.Count} offline commands to execute.");
fore
// ... [Code preview truncated due to length. Full source in attached file] ...