(06:57:06) | Web request callback raised an exception (FormatException: Index (zero based) must be greater than or equal to zero and less than the size of the argument list.)
at System.Text.StringBuilder.AppendFormatHelper (System.IFormatProvider provider, System.String format, System.ParamsArray args) [0x000f9] in <8ce0bd04a7a04b4b9395538239d3fdd8>:0
at System.String.FormatHelper (System.IFormatProvider provider, System.String format, System.ParamsArray args) [0x00023] in <8ce0bd04a7a04b4b9395538239d3fdd8>:0
at System.String.Format (System.String format, System.Object arg0, System.Object arg1) [0x00009] in <8ce0bd04a7a04b4b9395538239d3fdd8>:0
at Oxide.Plugins.Ember+<>cDisplayClass39_0.<HttpRequest>b0 (System.Int32 code, System.String response) [0x00069] in <1008f8a73ee74758b7d9660194d9809f>:0
at Oxide.Core.Libraries.WebRequests+WebRequest.<OnComplete>b__46_0 () [0x00034] in <112d89ea5d3348c8b949af0ab1a866d2>:0 Console error
I can offer you a modified plugin that has optimization and additional features.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core.Libraries;
using Oxide.Core.Plugins;
using UnityEngine;
namespace Oxide.Plugins {
[Info ("Ember", "Mkekala", "2.0.1")]
[Description ("Integrates Ember actions, ban management & role sync with Rust")]
public class Ember : RustPlugin {
#region Configuration
private class PluginConfig {
public string Host = "http://*.*.*.*";
public string Token;
public LogLevel LogLevel = LogLevel.Info;
[JsonProperty (PropertyName = "_MapImageParamsComment")]
public string MapImageParamsComment = "Параметры генерации карты (RustMapApi). Загружается в store при подключении сервера.";
public float MapImageScale = 0.5f;
public string MapImageName = "Icons";
[JsonProperty (PropertyName = "_MapImageScale_desc")]
public string MapImageScaleDesc = "Масштаб карты (0.0–1.0). Разрешение = MapImageScale × размер мира. 0.5 = половина размера карты в пикселях.";
[JsonProperty (PropertyName = "_MapImageName_desc")]
public string MapImageNameDesc = "Icons — карта с иконками монументов (порты, аэродром, военка и т.д.). Default — карта без иконок (только рельеф).";
}
private enum LogLevel { Trace, Debug, Info, Warning, Error };
private PluginConfig config;
private class IntegrationSettings {
[JsonProperty ("ban_log")]
public bool banLog;
[JsonProperty ("role_sync")]
public RoleSyncSettings roleSync = new ();
}
private class RoleSyncSettings {
[JsonProperty ("create")]
public bool Create;
[JsonProperty ("send")]
public bool Send;
[JsonProperty ("receive")]
public bool Receive;
}
private IntegrationSettings settings = new ();
#endregion
#region Constants
private const string commandNamespace = "ember";
private static readonly string[] permissions = {
"ban",
"unban",
};
private static readonly string[] userHooks = {
"OnUserBanned",
"OnUserUnbanned",
"OnUserGroupAdded",
"OnUserGroupRemoved",
};
private const float retryApiInitIntervalSeconds = 60f,
maxPollTimerDelayUserConnectingSeconds = 5f;
private const BindingFlags selfReflectionFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic;
private const string mapImageBlobPath = "map.jpg";
private const RustMapApiEncodingMode mapImageEncoding = RustMapApiEncodingMode.Jpg;
private enum RustMapApiEncodingMode { Jpg = 1, Png = 2 };
#endregion
#region Variables
private bool connected;
private Dictionary<string, Action<JToken>> actionHandlers = new ();
private Dictionary<string, string> connectingUsers = new ();
private Timer retryApiInitTimer;
private TimerManager.TimerProxy pollTimer;
[PluginReference]
private Plugin RustMapApi;
private string _cmdBanName;
private string _cmdUnbanName;
#endregion
#region Domain methods
#region Polling/action flow
private void AddConnectingUser (string steamid, string name = "") {
if (connectingUsers.ContainsKey (steamid))
return;
connectingUsers.Add (steamid, name);
if (connected) RunPollWithin (maxPollTimerDelayUserConnectingSeconds);
}
private void RunPoll () {
var connectingUsersBatch = Interlocked.Exchange (ref connectingUsers, new ());
int playerCount = connectingUsersBatch.Count + BasePlayer.activePlayerList.Count;
LogMessage ($"Poll invoked with {playerCount} player(s)", LogLevel.Trace);
if (playerCount == 0)
return;
List<string> names = new ();
foreach (string name in connectingUsersBatch.Values)
names.Add (name.Replace (",", ""));
List<string> connectedUserIds = new ();
foreach (BasePlayer ply in BasePlayer.activePlayerList)
if (!connectingUsersBatch.ContainsKey (ply.UserIDString))
connectedUserIds.Add (ply.UserIDString);
else playerCount -= 1;
JObject payload = JObject.FromObject (new {
players = new {
steam = new {
connecting = new {
ids = string.Join (",", connectingUsersBatch.Keys),
names = string.Join (",", names),
},
connected = new {
ids = string.Join (",", connectedUserIds)
}
}
}
});
LogMessage (string.Format (lang.GetMessage ("ConsolePolling", this), playerCount));
var failureHandler = (int code, string response) => {
LogMessage (string.Format (lang.GetMessage ("ConsoleApiRequestFailed", this), code, response), LogLevel.Error);
if (connectingUsersBatch.Count > 0)
LogMessage ($"Queuing poll retry for {connectingUsersBatch.Count} connecting player(s)", LogLevel.Debug);
foreach (var (steamid, name) in connectingUsersBatch.Select ((u) => (u.Key, u.Value)))
AddConnectingUser (steamid, name);
};
var successHandler = (JToken response) => {
foreach (JToken action in response as JArray)
DispatchAction (action);
};
HttpRequest (RequestMethod.POST, "poll", payload, successHandler, failureHandler);
}
private void RunPollWithin (float seconds) {
LogMessage (string.Format ("Timer interval: {0}s, time remaining: {1}s, run within: {2}s", pollTimer.DefaultInterval, pollTimer.Remaining (), seconds), LogLevel.Trace);
pollTimer.InvokeWithin (seconds);
}
private void DispatchAction (JToken action) {
string type = (string) action["type"];
LogMessage (action.ToString (), LogLevel.Trace);
if (!actionHandlers.ContainsKey (type)) {
LogMessage (string.Format (lang.GetMessage ("ActionUnrecognized", this), type), LogLevel.Warning);
return;
}
LogMessage (string.Format (lang.GetMessage ("ActionDispatching", this), type), LogLevel.Debug);
var handler = actionHandlers[type];
string error = string.Empty;
try {
handler (action["payload"]);
} catch (Exception ex) {
error = ex.Message;
LogMessage (string.Format ("Action execution failed for type \"{0}\". Error: {1}", type, error), LogLevel.Warning);
}
var execuctionId = (float?) action["execution_id"];
if (execuctionId != null)
HttpRequest (RequestMethod.POST, $"actions/executions/{execuctionId}/finish", JObject.FromObject (new { error }));
}
#endregion
#region Player endpoints
public void Ban (string offenderSteamid, string durationMinutes, string reason, bool global, string adminSteamid, BasePlayer caller) {
string scope = global ? "global" : null;
var parameters = JObject.FromObject (new {
scope,
reason,
duration_minutes = durationMinutes,
});
if (adminSteamid != null)
parameters["admin_steam_account_id"] = adminSteamid;
string url = $"players/steam/{offenderSteamid}/bans";
var failureHandler = (int code, string response) => {
if (caller != null)
SendLogMessage (caller, string.Format (lang.GetMessage ("PlayerBanFailed", this, caller.UserIDString), offenderSteamid), LogLevel.Warning);
};
var successHandler = (JToken _) => {
LogMessage (string.Format (lang.GetMessage ("PlayerBanned", this), offenderSteamid));
if (caller != null)
SendLogMessage (caller, string.Format (lang.GetMessage ("PlayerBanned", this, caller.UserIDString), offenderSteamid));
};
HttpRequest (RequestMethod.POST, url, parameters, successHandler, failureHandler, "Ban");
}
public void Unban (string offenderSteamid, BasePlayer caller) {
string url = $"players/steam/{offenderSteamid}/bans";
var failureHandler = (int code, string response) => {
if (caller != null)
SendLogMessage (caller, string.Format (lang.GetMessage ("PlayerUnbanFailed", this, caller.UserIDString), offenderSteamid), LogLevel.Warning);
};
var successHandler = (JToken _) => {
string actor = caller?.UserIDString ?? "console";
LogMessage (string.Format (lang.GetMessage ("PlayerUnbanned", this), offenderSteamid, actor));
if (caller != null)
SendLogMessage (caller, string.Format (lang.GetMessage ("PlayerUnbanned", this, caller.UserIDString), offenderSteamid, actor));
};
HttpRequest (RequestMethod.DELETE, url, null, successHandler, failureHandler, "Unban");
}
private void PostRole (string steamid, string role) {
if (role == "default")
return;
string url = $"players/steam/{steamid}/roles";
JObject parameters = new (new JProperty ("name", role));
var successHandler = (JToken _) =>
LogMessage (string.Format (lang.GetMessage ("ConsoleRoleAdded", this), role, steamid));
HttpRequest (RequestMethod.POST, url, parameters, successHandler, null, "RoleAdd");
}
private void DeleteRole (string steamid, string role) {
string url = $"players/steam/{steamid}/roles/{role}";
var successHandler = (JToken _) =>
LogMessage (string.Format (lang.GetMessage ("ConsoleRoleRevoked", this), role, steamid));
HttpRequest (RequestMethod.DELETE, url, null, successHandler, null, "RoleRevoke");
}
#endregion
#endregion
#region Utilities
private void CallUntilReturns<T> (Func<T> callback, T value, float intervalSeconds = 5f, int maxRetries = 10)
{
if (maxRetries <= 0) {
LogMessage ("CallUntilReturns: достигнут лимит попыток, останавливаем", LogLevel.Warning);
return;
}
if (callback ().Equals (value))
return;
timer.In (intervalSeconds, () => CallUntilReturns (callback, value, intervalSeconds, maxRetries - 1));
}
private static List<BasePlayer> GetPlayersByName (string name) {
List<BasePlayer> matches = new List<BasePlayer> ();
string nameLower = name.ToLower ();
foreach (BasePlayer ply in BasePlayer.activePlayerList)
if (ply.displayName.ToLower ().Contains (nameLower))
matches.Add (ply);
return matches;
}
private static BasePlayer GetPlayerBySteamID (string steamid) {
foreach (BasePlayer ply in BasePlayer.activePlayerList)
if (ply.UserIDString == steamid)
return ply;
return null;
}
#region Reflection
private static string GetMethodCommandName (string methodName) {
return typeof (Ember).GetMethod (methodName, selfReflectionFlags)
.GetCustomAttribute<ConsoleCommandAttribute> ().Command;
}
#endregion
#region Timers
protected class TimerManager {
protected static readonly Oxide.Core.Libraries.Timer CoreTimer =
Oxide.Core.Interface.Oxide.GetLibrary<Oxide.Core.Libraries.Timer> ("Timer");
protected readonly Plugin Plugin;
public TimerManager (Plugin plugin) {
Plugin = plugin;
}
public TimerProxy Repeat (float interval, int repetitions, Action callback) {
return new TimerProxy (Plugin, CoreTimer, interval, repetitions, callback);
}
public class TimerProxy {
protected readonly Oxide.Core.Libraries.Timer.TimerInstance Instance;
protected float StartedAt;
public float DefaultInterval;
public bool Destroyed => Instance.Destroyed;
public TimerProxy (Plugin plugin, Oxide.Core.Libraries.Timer coreTimer, float interval, int repetitions, Action callback) {
Action callbackProxy = () => {
RestoreInterval ();
callback ();
};
Instance = coreTimer.Repeat (interval, repetitions, callbackProxy, plugin);
StartedAt = Oxide.Core.Interface.Oxide.Now;
DefaultInterval = Instance.Delay;
}
protected void RestoreInterval () {
if (Instance.Delay == DefaultInterval)
return;
Reset (DefaultInterval, Instance.Repetitions);
}
protected void Reset (float interval = -1, int repetitions = 1) {
Instance.Reset (interval, repetitions);
StartedAt = Oxide.Core.Interface.Oxide.Now;
}
public float Remaining () {
if (Destroyed)
return -1f;
return Instance.Delay - (Oxide.Core.Interface.Oxide.Now - StartedAt) % Instance.Delay;
}
public void InvokeWithin (float seconds) {
if (seconds <= 0f || Remaining () <= seconds)
return;
Reset (seconds, Instance.Repetitions);
}
public void Destroy () {
Instance.DestroyToPool ();
}
}
}
#endregion
#region Logging
private void LogMessage (string message, LogLevel level = LogLevel.Info, [CallerMemberName] string callerName = "", [CallerLineNumber] int callerLine = -1) {
if (level < config.LogLevel)
return;
if (level == LogLevel.Trace)
message = string.Format ("[{0}:{1}] {2}", callerLine, callerName, message);
message = string.Format ("[{0}] {1}", level.ToString (), message);
if (level < LogLevel.Warning)
Puts (message);
else if (level < LogLevel.Error)
PrintWarning (message);
else
PrintError (message);
}
private void SendLogMessage (BasePlayer player, string messageKey, LogLevel level = LogLevel.Info) {
string message = string.Format (
"{1}<color={0}>{2}</color>",
GetLogLevelColor (level),
lang.GetMessage ("DebugChatPrefix", this),
lang.GetMessage (messageKey, this, player.UserIDString)
);
PrintToConsole (player, message);
PrintToChat (player, message);
}
private void SendCommandReplies (ConsoleSystem.Arg arg, string messageKey, LogLevel level = LogLevel.Info) {
BasePlayer player = arg.Player ();
string format = player ? "<color={0}>{1}</color>" : "{1}",
message = string.Format (
format,
GetLogLevelColor (level),
lang.GetMessage (messageKey, this, player?.UserIDString)
);
SendReply (arg, message); // Caller console
if (player) SendReply (player, message); // Caller chat
}
private static string GetLogLevelColor (LogLevel level = LogLevel.Info) {
if (level >= LogLevel.Error)
return "red";
if (level >= LogLevel.Warning)
return "orange";
return "white";
}
#endregion
#region Web API
private void HttpRequest (RequestMethod method, string path, JObject parameters, Action<JToken> onSuccess = null, Action<int, string> onFailure = null, string failureMessageKeySuffix = "") {
var headers = new Dictionary<string, string> { { "Authorization", "Bearer " + config.Token }, { "Accept", "application/json" } };
string url = config.Host + "/api/game/" + path;
string body = null;
if (parameters != null && method != RequestMethod.GET) {
body = parameters.ToString ();
headers.Add ("Content-Type", "application/json");
}
LogMessage (string.Format ("HTTP Request: {0} {1} {2}", method, url, body?.Truncate (5000)), LogLevel.Trace);
webrequest.Enqueue (
url,
body,
(code, response) => {
if (code < 200 || code > 299 || response == null ||
(!TryParseJson (response, out JToken json) && response != string.Empty)
) {
if (failureMessageKeySuffix != string.Empty)
LogMessage (string.Format (lang.GetMessage ("ConsoleApiRequestFailed" + failureMessageKeySuffix, this), code, response), LogLevel.Warning);
onFailure?.Invoke (code, response);
return;
}
LogMessage (string.Format ("HTTP Response: {0} {1} {2} {3}", method, url, code, json?.ToString ()), LogLevel.Trace);
onSuccess?.Invoke (json ?? new JObject ());
},
this,
method,
headers
);
}
private static string NormalizeHostUri (string uri) {
return uri.TrimEnd (new Char[] { '/' });
}
private static bool TryParseJson<T> (string payload, out T result) where T : JToken {
result = null;
try {
result = (T) JToken.Parse (payload);
return true;
} catch (JsonReaderException) {
return false;
}
}
#endregion
#endregion
#region Init methods
protected override void LoadConfig () {
base.LoadConfig ();
config = Config.ReadObject<PluginConfig> ();
string normalizedHost = NormalizeHostUri (config.Host);
if (config.Host != normalizedHost) {
config.Host = normalizedHost;
Config.WriteObject (config);
}
}
protected override void LoadDefaultConfig () {
Config.WriteObject (new PluginConfig (), true);
}
public void RegisterActionHandler (string type, Action<JToken> handler) {
LogMessage ($"Registering action \"{type}\"", LogLevel.Debug);
actionHandlers[type] = handler;
}
private void RegisterActionHandlers () {
RegisterActionHandler ("command", HandleCommandAction);
RegisterActionHandler ("roles", HandleRolesAction);
RegisterActionHandler ("settings", HandleSettingsAction);
}
private void RegisterPermissions () {
foreach (string suffix in permissions) {
string name = string.Format ("{0}.{1}", Title.ToLower (), suffix);
permission.RegisterPermission (name, this);
}
}
private void InitPollTimer (float interval = 60f) {
if (pollTimer?.Destroyed == false)
return;
pollTimer = new TimerManager (this).Repeat (interval, -1, RunPoll);
}
private void SetPollInterval (float interval) {
if (interval < 0f || (pollTimer?.Destroyed == false && pollTimer.DefaultInterval == interval))
return;
LogMessage (string.Format (lang.GetMessage ("PollIntervalSetting", this), interval), LogLevel.Info);
if (pollTimer?.Destroyed != false) {
InitPollTimer (interval);
} else {
pollTimer.DefaultInterval = interval;
pollTimer.InvokeWithin (interval);
}
}
private void DeinitApi () {
pollTimer?.Destroy ();
foreach (string hook in userHooks)
Unsubscribe (hook);
connected = false;
}
private void TryInitApi () {
retryApiInitTimer?.Destroy ();
if (string.IsNullOrEmpty (config.Token)) {
LogMessage ("Token unconfigured; not attempting web API init", LogLevel.Debug);
return;
}
LogMessage (lang.GetMessage ("ConsoleApiConnectionChecking", this));
JObject payload = JObject.FromObject (new {
actions = new[] { "settings" },
metadata = GetServerMetadata (),
});
var failureHandler = (int code, string response) => {
LogMessage (string.Format (lang.GetMessage ("ConsoleApiRequestFailed", this), code, response), LogLevel.Error);
DeinitApi ();
foreach (BasePlayer player in BasePlayer.activePlayerList)
if (player.net.connection.authLevel >= 1)
SendLogMessage (player, "NoApiConnection", LogLevel.Error);
if (code < 400 || code > 499)
retryApiInitTimer = timer.Once (retryApiInitIntervalSeconds, () => TryInitApi ());
};
var successHandler = (JToken json) => {
if (json.Count () == 0) {
failureHandler (-1, string.Empty);
return;
}
foreach (JToken action in json["actions"])
DispatchAction (action);
if (pollTimer?.Destroyed != false)
InitPollTimer ();
foreach (string hook in userHooks)
Subscribe (hook);
connected = true;
LogMessage (lang.GetMessage ("ConsoleApiConnectionSuccess", this));
foreach (BasePlayer player in BasePlayer.activePlayerList)
if (player.net.connection.authLevel >= 2)
SendLogMessage (player, "ConsoleApiConnectionSuccess");
};
HttpRequest (RequestMethod.POST, "meta", payload, successHandler, failureHandler);
}
private object GetServerMetadata () {
bool customMap = World.CanLoadFromUrl () && !World.Url.Contains ("facepunch.com");
uint? seed = World.Seed;
uint size = World.Size;
string worldName = World.Name.ToString ();
DateTime? nextWipeUtc = WipeTimer.serverinstance?.GetWipeTime (DateTime.UtcNow).UtcDateTime;
DateTime wipedAtUtc = SaveRestore.SaveCreatedTime.ToUniversalTime ();
return new {
integration_version = Version.ToString (),
map = new {
seed = !customMap ? seed : null,
size,
name = worldName,
file_url = World.CanLoadFromUrl () ? World.Url : null,
image_blob_path = RustMapApi != null ? mapImageBlobPath : null,
},
game = new
{
mode = BaseGameMode.GetActiveGameMode (true)?.shortname ?? "vanilla",
ConVar.Server.maxplayers,
protocol = Rust.Protocol.printable,
wipe = new
{
id = SaveRestore.WipeId,
began_at = wipedAtUtc.ToString ("O"),
ends_at = nextWipeUtc?.ToString ("O"),
},
},
};
}
#endregion
#region Hooks
void Init () {
_cmdBanName = GetMethodCommandName (nameof (CmdBan));
_cmdUnbanName = GetMethodCommandName (nameof (CmdUnban));
RegisterPermissions ();
RegisterActionHandlers ();
}
void OnServerInitialized (bool initial)
{
TryInitApi ();
if (RustMapApi && initial) {
CallUntilReturns (() => {
if (!connected || !IsRustMapApiReady ())
return false;
TryUploadMapImage ();
return true;
}, true);
}
}
void OnUserBanned (string name, string id, string ipAddress, string reason) {
if (settings.banLog)
Ban (id, "0", reason, false, null, null);
}
void OnUserUnbanned (string name, string id, string ipAddress) {
if (settings.banLog)
Unban (id, null);
}
void OnUserGroupAdded (string id, string groupName) {
if (!settings.roleSync.Send)
return;
LogMessage ($"Syncing group addition for SteamID {id}, group \"{groupName}\"", LogLevel.Trace);
PostRole (id, groupName);
}
void OnUserGroupRemoved (string id, string groupName) {
if (!settings.roleSync.Send)
return;
LogMessage ($"Syncing group removal for SteamID {id}, group \"{groupName}\"", LogLevel.Trace);
DeleteRole (id, groupName);
}
void OnPlayerConnected (BasePlayer player) {
AddConnectingUser (player.UserIDString, player.displayName);
if (!connected)
if (player.net.connection.authLevel >= 1)
SendLogMessage (player, "NoApiConnection", LogLevel.Error);
}
#endregion
#region Action handlers
private void HandleCommandAction (JToken payload) {
var command = (string) payload;
LogMessage (string.Format (lang.GetMessage ("ActionCommandRunning", this), command));
rust.RunServerCommand (command);
}
private void HandleRolesAction (JToken payload) {
if (!settings.roleSync.Receive && !settings.roleSync.Send)
return;
string steamid = (string) payload["player_steam_account_id"];
if (string.IsNullOrEmpty (steamid)) {
LogMessage ("HandleRolesAction: player_steam_account_id is null or empty", LogLevel.Warning);
return;
}
BasePlayer player = GetPlayerBySteamID (steamid);
string name = player?.displayName ?? "Unknown";
LogMessage (string.Format (lang.GetMessage ("ConsoleSyncingRoles", this), name, steamid));
if (settings.roleSync.Receive == true) {
JToken grantToken = payload["grant"];
if (grantToken != null && grantToken.Type == JTokenType.Array) {
foreach (JToken roleToken in grantToken) {
string role = roleToken?.ToString ();
if (string.IsNullOrEmpty (role))
continue;
if (!permission.GroupExists (role)) {
if (settings.roleSync.Create) {
LogMessage (string.Format (lang.GetMessage ("ConsoleCreatingGroup", this), role));
permission.CreateGroup (role, role, 0);
} else {
continue;
}
} else if (permission.UserHasGroup (steamid, role))
continue;
LogMessage (string.Format (lang.GetMessage ("ConsoleGrantingGroup", this), role, name, steamid));
permission.AddUserGroup (steamid, role);
if (player != null)
SendReply (player, string.Format (lang.GetMessage ("GroupGranted", this, player.UserIDString), role));
}
}
JToken revokeToken = payload["revoke"];
if (revokeToken != null && revokeToken.Type == JTokenType.Array) {
foreach (JToken roleToken in revokeToken) {
string role = roleToken?.ToString ();
if (string.IsNullOrEmpty (role))
continue;
if (!permission.UserHasGroup (steamid, role))
continue;
LogMessage (string.Format (lang.GetMessage ("ConsoleRevokingGroup", this), role, name, steamid));
permission.RemoveUserGroup (steamid, role);
if (player != null)
SendReply (player, string.Format (lang.GetMessage ("GroupRevoked", this, player.UserIDString), role));
}
}
}
if (settings.roleSync.Send == true) {
JToken exhaustiveToken = payload["exhaustive"];
bool exhaustive = exhaustiveToken != null && exhaustiveToken.Type == JTokenType.Boolean && (bool) exhaustiveToken;
if (exhaustive) {
JToken grantToken = payload["grant"];
if (grantToken != null && grantToken.Type == JTokenType.Array) {
string[] roles = grantToken.ToObject<string[]> ();
foreach (string group in permission.GetGroups ())
if (permission.UserHasGroup (steamid, group))
if (Array.IndexOf (roles, group) == -1)
PostRole (steamid, group);
}
}
}
}
private void HandleSettingsAction (JToken payload) {
settings = payload?.ToObject<IntegrationSettings> () ?? new ();
var intervalSec = (float?) payload["poll_interval_seconds"];
LogMessage (string.Format ("Poll interval: {0}s", intervalSec ?? -1f), LogLevel.Trace);
if (intervalSec != null) SetPollInterval ((float) intervalSec);
}
#endregion
#region Console commands
[ConsoleCommand ($"{commandNamespace}.ban")]
private void CmdBan (ConsoleSystem.Arg arg) {
string name = "ban", usage = lang.GetMessage ("CommandUsageBan", this, arg.Player ()?.UserIDString), permissionSuffix = "ban";
const int minArgs = 3, minAuthLevel = 2;
var handler = (ConsoleSystem.Arg arg) => {
BasePlayer admin = arg.Player ();
string offenderSteamid = "";
if (arg.Args[0].IsSteamId ()) {
offenderSteamid = arg.Args[0];
} else {
List<BasePlayer> offenderMatches = GetPlayersByName (arg.Args[0]);
if (offenderMatches.Count > 0) {
if (offenderMatches.Count == 1) {
offenderSteamid = offenderMatches.First ().UserIDString;
} else {
SendCommandReplies (arg, lang.GetMessage ("MultiplePlayersFound", this, admin?.UserIDString));
return;
}
} else {
SendCommandReplies (arg, string.Format (lang.GetMessage ("NoPlayersFoundByName", this, admin?.UserIDString), arg.Args[0]));
return;
}
}
if (!int.TryParse (arg.Args[1], out int durationMinutes)) {
SendCommandReplies (arg, lang.GetMessage ("InvalidTime", this, admin?.UserIDString));
return;
}
BasePlayer offender = GetPlayerBySteamID (offenderSteamid);
if (offender != null)
offender.Kick (lang.GetMessage ("Banned", this, offender.UserIDString));
bool global = arg.Args.Length == 4 && arg.Args[3] == "true";
Ban (offenderSteamid, arg.Args[1], arg.Args[2], global, admin?.UserIDString, admin);
};
HandleConsoleCommand (arg, name, usage, handler, minArgs, minAuthLevel, permissionSuffix);
}
[ConsoleCommand ($"{commandNamespace}.unban")]
private void CmdUnban (ConsoleSystem.Arg arg) {
const string name = "unban", usage = "<SteamID64>", permissionSuffix = "unban";
const int minArgs = 1, minAuthLevel = 2;
var handler = (ConsoleSystem.Arg arg) => {
BasePlayer admin = arg.Player ();
string offenderSteamid = arg.Args[0];
if (!offenderSteamid.IsSteamId ()) {
SendCommandReplies (arg, string.Format (lang.GetMessage ("InvalidArgValue", this, admin.UserIDString), "SteamID64", offenderSteamid), LogLevel.Warning);
return;
}
Unban (offenderSteamid, admin);
};
HandleConsoleCommand (arg, name, usage, handler, minArgs, minAuthLevel, permissionSuffix);
}
[ConsoleCommand ($"{commandNamespace}.connect")]
private void CmdConnect (ConsoleSystem.Arg arg) {
const string name = "connect", usage = "<\"URL\"> <\"token\">";
const int minArgs = 2, minAuthLevel = 2;
var handler = (ConsoleSystem.Arg arg) => {
if (!Uri.TryCreate (arg.Args[0], UriKind.Absolute, out Uri uri)) {
SendReply (arg, string.Format (lang.GetMessage ("InvalidArgValue", this), "url", arg.Args[0]));
return;
}
LoadConfig ();
config.Host = NormalizeHostUri (uri.ToString ());
config.Token = arg.Args[1];
Config.WriteObject (config);
if (arg.Connection != null)
SendReply (arg, lang.GetMessage ("ConsoleApiConnectionChecking", this));
TryInitApi ();
};
HandleConsoleCommand (arg, name, usage, handler, minArgs, minAuthLevel);
}
[ConsoleCommand ($"{commandNamespace}.loglevel")]
private void CmdLogLevel (ConsoleSystem.Arg arg) {
const string name = "loglevel", usage = "[\"level\"]";
const int minArgs = 0, minAuthLevel = 2;
var handler = (ConsoleSystem.Arg arg) => {
if (!arg.HasArgs ()) {
SendReply (arg, string.Format (lang.GetMessage ("ConfigOptionSet", this), "LogLevel", config.LogLevel));
return;
}
if (!Enum.TryParse (arg.Args[0], true, out LogLevel level)) {
SendReply (arg, string.Format (lang.GetMessage ("InvalidArgValue", this), "level", arg.Args[0]));
return;
}
LoadConfig ();
config.LogLevel = level;
Config.WriteObject (config);
SendReply (arg, string.Format (lang.GetMessage ("ConfigOptionSet", this), "LogLevel", level));
};
HandleConsoleCommand (arg, name, usage, handler, minArgs, minAuthLevel);
}
[ConsoleCommand ($"{commandNamespace}.pollwithin")]
private void CmdPollWithin (ConsoleSystem.Arg arg) {
const string name = "pollwithin", usage = "<\"seconds\">";
const int minArgs = 1, minAuthLevel = 2;
var handler = (ConsoleSystem.Arg arg) => {
if (!connected) {
SendReply (arg, lang.GetMessage ("NoApiConnection", this));
return;
}
RunPollWithin (float.Parse (arg.Args[0]));
};
HandleConsoleCommand (arg, name, usage, handler, minArgs, minAuthLevel);
}
private void HandleConsoleCommand (ConsoleSystem.Arg arg, string name, string usage, Action<ConsoleSystem.Arg> handler, int minArgs = 0, int minAuthLevel = 2, string permissionSuffix = null) {
string namespacedCommand = $"{commandNamespace}.{name}";
if (arg.Connection != null) {
BasePlayer player = arg.Player ();
string namespacedPermission = permissionSuffix != null ?
$"{Title.ToLower ()}.{permissionSuffix}" : null;
if (player.net.connection.authLevel < minAuthLevel && !permission.UserHasPermission (player.UserIDString, namespacedPermission)) {
SendCommandReplies (arg, string.Format (lang.GetMessage ("CommandNoPermission", this, player.UserIDString), namespacedCommand), LogLevel.Warning);
return;
}
}
if (minArgs > 0 && (!arg.HasArgs () || arg.Args.Length < minArgs)) {
string format = $"{lang.GetMessage ("InvalidArgs", this)}. {lang.GetMessage ("CommandUsage", this)}",
fullUsageSyntax = $"{namespacedCommand} {usage}";
SendCommandReplies (arg, string.Format (format, fullUsageSyntax), LogLevel.Warning);
return;
}
handler (arg);
}
#endregion
#region Chat commands
[ChatCommand ("ban")]
private void ChatCmdBan (BasePlayer player, string _command, string[] args) {
player.SendConsoleCommand (_cmdBanName, args);
}
[ChatCommand ("unban")]
private void ChatCmdUnban (BasePlayer player, string _command, string[] args) {
player.SendConsoleCommand (_cmdUnbanName, args);
}
#endregion
#region Plugin interop
#region Rust Map Api
private bool IsRustMapApiReady () {
bool isReady = RustMapApi?.Call<bool> ("IsReady") ?? false;
if (!isReady) LogMessage ($"Rust Map Api is not ready", LogLevel.Trace);
return isReady;
}
private bool TryUploadMapImage () {
if (!IsRustMapApiReady ())
return false;
string mapName = !string.IsNullOrEmpty (config.MapImageName) ? config.MapImageName : "Icons";
int resolution = (int) (config.MapImageScale * World.Size);
object response = RustMapApi.Call ("CreatePluginImage", this, mapName, resolution, (int) mapImageEncoding);
if (response is string) {
LogMessage ($"Rust Map Api call failed: {response}", LogLevel.Warning);
return false;
}
var map = response as Hash<string, object>;
if (map?["image"] is byte[] mapBytes) {
LogMessage ($"Map image size: {mapBytes.Length} bytes", LogLevel.Trace);
} else {
LogMessage ($"Image missing from Rust Map Api response", LogLevel.Warning);
return false;
}
// Кодирование и отправка в фоновом потоке — не блокирует игровой сервер
Task.Run (() => {
try {
string base64 = Convert.ToBase64String (mapBytes);
string body = "{\"visibility\":\"public\",\"encoding\":\"base64\",\"content\":\"" + base64 + "\"}";
NextTick (() => {
var headers = new Dictionary<string, string> {
{ "Authorization", "Bearer " + config.Token },
{ "Accept", "application/json" },
{ "Content-Type", "application/json" }
};
string url = config.Host + "/api/game/blobs/" + mapImageBlobPath;
webrequest.Enqueue (url, body, (code, response) => {
if (code < 200 || code > 299)
LogMessage ($"Map upload failed: {code} {response}", LogLevel.Warning);
else
LogMessage ("Map image uploaded successfully", LogLevel.Debug);
}, this, RequestMethod.PUT, headers);
});
} catch (Exception ex) {
NextTick (() => LogMessage ($"Map image encoding failed: {ex.Message}", LogLevel.Warning));
}
});
return true;
}
#endregion
#endregion
#region Localization
protected override void LoadDefaultMessages () {
lang.RegisterMessages (new Dictionary<string, string> {
{ "ActionCommandRunning", "Running action command: {0}"},
{ "ActionDispatching", "Handling action \"{0}\""},
{ "ActionUnrecognized", "Unrecognized action \"{0}\""},
{ "Banned", "You've been banned from the server" },
{ "CommandUsage", "Usage: {0}" },
{ "CommandUsageBan", "<name/SteamID64> <duration:minutes, 0=permanent> \"<reason>\" [global:false/true]" },
{ "CommandNoPermission", "Insufficient permissions for running command \"{0}\"" },
{ "ConfigOptionSet", "Configuration option {0} set to {1}" },
{ "ConsoleApiConnectionChecking", "Checking connection to web API" },
{ "ConsoleApiConnectionSuccess", "Connection established and token validated successfully" },
{ "ConsoleApiRequestFailed", "Web API request failed. Code: {0}. Response: {1}" },
{ "ConsoleApiRequestFailedBan", "Failed to ban user. Code: {0}. Response: {1}" },
{ "ConsoleApiRequestFailedRoleAdd", "Failed to add role \"{0}\". Code: {1}. Response: {2}" },
{ "ConsoleApiRequestFailedRoleRevoke", "Failed to revoke role \"{0}\". Code: {1}. Response: {2}" },
{ "ConsoleApiRequestFailedUnban", "Failed to unban user. Code: {0}. Response: {1}" },
{ "ConsoleCreatingGroup", "Creating group \"{0}\"" },
{ "ConsoleGrantingGroup", "Granting the \"{0}\" group to {1} ({2})" },
{ "ConsolePolling", "Polling with {0} player(s)" },
{ "ConsoleRevokingGroup", "Revoking the \"{0}\" group from {1} ({2})" },
{ "ConsoleRoleAdded", "Role \"{0}\" added for {1}" },
{ "ConsoleRoleRevoked", "Role \"{0}\" revoked from {1}" },
{ "ConsoleSyncingRoles", "Syncing roles for {0} ({1})" },
{ "DebugChatPrefix", "[Ember] " },
{ "GroupGranted", "You've been granted the {0} group" },
{ "GroupRevoked", "Your {0} group has been revoked" },
{ "InvalidArgs", "Invalid arguments" },
{ "InvalidArgValue", "Invalid value for argument {0}: \"{1}\"" },
{ "InvalidTime", "Time must be a number, 0 for permanent" },
{ "MultiplePlayersFound", "Multiple players found, please be more specific" },
{ "NoApiConnection", "The plugin is not connected to the web API. Check the server console for details" },
{ "NoPlayersFoundByName", "Player not found by name \"{0}\"" },
{ "PlayerBanFailed", "Failed to ban player {0}" },
{ "PlayerBanned", "Player {0} banned" },
{ "PlayerUnbanFailed", "Failed to unban player {0}" },
{ "PlayerUnbanned", "Player {0} unbanned by {1}" },
{ "PollIntervalSetting", "Setting the polling interval to {0} second(s)" },
}, this, "en");
LogMessage ("Default messages created", LogLevel.Debug);
}
#endregion
}
} Thank you!
aykd520
Thank you!
Is everything working? Are there no more errors?
It's working very well at the moment!