C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
#define RUusing Newtonsoft.Json;
using Oxide.Core;
using Oxide.Plugins;
using Oxide.Core.Plugins;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
using Oxide.Core.Libraries;
using Oxide.Game.Rust.Cui;
using System.Linq;
using UnityEngine;
using Network;
using UnityEngine.Networking;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
using System.IO;
using ConVar;
using Oxide.Core.Database;
using Facepunch;
using Rust;
using Steamworks;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using UnityEngine;
using Color = System.Drawing.Color;
using Graphics = System.Drawing.Graphics;
using Star = ProtoBuf.PatternFirework.Star;
namespace Oxide.Plugins {
[Info("RustApp", "Hougan & Xacku & Olkuts & FourTeen", "1.7.7")]
public class RustApp : RustPlugin {
#region Classes
// From RustStats by Sanlerus
public static class EncodingBase64 {
public static string Encode(string text) {
var textAsBytes = Encoding.UTF8.GetBytes(text);
return Convert.ToBase64String(textAsBytes);
}
public static string Decode(string encodedText) {
var textAsBytes = Convert.FromBase64String(encodedText);
return Encoding.UTF8.GetString(textAsBytes);
}
}
// Base code idea from RustStore by Sanlerus
public class StableRequest<T> {
private static Dictionary<string, string> DefaultHeaders(string secret) {
return new Dictionary<string, string> {
["x-plugin-auth"]
= secret, ["x-plugin-version"] = _RustApp.Version.ToString(), ["x-plugin-port"] = ConVar.Server.port.ToString()
}
;
}
private string url;
private RequestMethod method;
private object data;
public Dictionary<string, string> headers = new Dictionary<string, string>();
public Action<string> onException;
public Action<T, string> onComplete;
public bool debug = true;
public bool silent = true;
private DateTime _created = DateTime.Now;
public StableRequest(string url, RequestMethod method, object? data, string secret) {
this.url = url;
this.method = method;
this.data = data;
this.headers = DefaultHeaders(secret);
this.onException += (a) => {
LastException.History.Insert(0, new LastException(url, data, a, secret));
if (LastException.History.Count > 10) {
LastException.History.RemoveRange(10, 1);
}
}
;
}
public double Elapsed() {
return (DateTime.Now - _created).TotalMilliseconds;
}
public void Execute(Action<T, string> onComplete, Action<string> onException) {
if (onComplete != null) {
this.onComplete += onComplete;
}
if (onException != null) {
this.onException += onException;
}
UnityWebRequest webRequest = null;
var body = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data != null ? data : new {
}
));
switch (method) {
case RequestMethod.GET : {
webRequest = UnityWebRequest.Get(url);
break;
}
case RequestMethod.PUT : {
webRequest = UnityWebRequest.Put(url, "{}");
webRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(body);
webRequest.uploadHandler.contentType = "application/json";
break;
}
case RequestMethod.POST : {
webRequest = UnityWebRequest.Post(url, "{}");
webRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(body);
webRequest.uploadHandler.contentType = "application/json";
break;
}
case RequestMethod.DELETE : {
webRequest = UnityWebRequest.Delete(url);
webRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(body);
webRequest.uploadHandler.contentType = "application/json";
break;
}
}
if (webRequest == null) {
throw new Exception("bad.request.type");
}
if (headers != null) {
foreach (var chunk in headers) {
webRequest.SetRequestHeader(chunk.Key, chunk.Value);
}
}
webRequest.SetRequestHeader("Content-Type", "application/json");
webRequest.timeout = 10;
Rust.Global.Runner.StartCoroutine(this.WaitForRequest(webRequest));
}
private IEnumerator WaitForRequest(UnityWebRequest request) {
yield return request.Send();
bool isError = request.isHttpError || request.isNetworkError || request.error != null;
var body = (request.downloadHandler?.text ?? "possible network errors, contact @rustapp_help if you see > 5 minutes").ToLower();
if (body.Length == 0) {
body = "possible network errors, contact @rustapp_help if you see > 5 minutes";
}
if (isError) {
if (body.Contains("502 bad gateway") || body.Contains("cloudflare")) {
onException?.Invoke("rustapp is restarting, wait please");
}
else {
onException?.Invoke(body);
}
}
else {
try {
if (typeof(T) == typeof(String)) {
onComplete?.Invoke((T)(object)request.downloadHandler?.text, request.downloadHandler?.text);
}
else {
var obj = JsonConvert.DeserializeObject<T>(request.downloadHandler?.text);
onComplete?.Invoke(obj, request.downloadHandler?.text);
}
}
catch (Exception parseException) {
var str = typeof(T).ToString();
_RustApp.Error($"Не удалось разобрать ответ от сервера ({request.method.ToUpper()} {request.url}): {parseException} (Response: {request.downloadHandler?.text})", $"Failed to parse server response: ({request.method.ToUpper()} {request.url}): {parseException} (Response: {request.downloadHandler?.text})" );
}
}
}
}
private class LastException {
public static List<LastException> History = new List<LastException>();
public string module;
public string secret;
public string payload;
public string response;
public DateTime time = DateTime.Now;
public LastException(string module, object payload, string response, string secret) {
this.module = module;
this.secret = secret;
this.payload = JsonConvert.SerializeObject(payload ?? "No request payload");
this.response = response;
}
}
private static class CourtUrls {
private static string Base = "https://court.rustapp.io";
public static string Pair = $"{CourtUrls.Base}/plugin/pair";
public static string Validate = $"{CourtUrls.Base}/plugin/";
public static string SendContact = $"{CourtUrls.Base}/plugin/contact";
public static string SendState = $"{CourtUrls.Base}/plugin/state";
public static string SendChat = $"{CourtUrls.Base}/plugin/chat";
public static string SendAlerts = $"{CourtUrls.Base}/plugin/alerts";
public static string SendCustomAlert = $"{CourtUrls.Base}/plugin/custom-alert";
public static string SendReports = $"{CourtUrls.Base}/plugin/reports";
public static string SendDestroyedSignage = $"{CourtUrls.Base}/plugin/signage";
public static string SendSignage = $"{CourtUrls.Base}/plugin/signage";
public static string SendWipe = $"{CourtUrls.Base}/plugin/wipe";
public static string BanCreate = $"{CourtUrls.Base}/plugin/ban";
public static string BanDelete = $"{CourtUrls.Base}/plugin/unban";
}
private static class QueueUrls {
private static string Base = "https://queue.rustapp.io";
public static string Fetch = $"{QueueUrls.Base}/";
public static string Response = $"{QueueUrls.Base}/";
}
private static class BanUrls {
private static string Base = "https://ban.rustapp.io";
public static string Fetch = $"{BanUrls.Base}/plugin/v2";
}
class PluginChatMessagesPayload {
public List<PluginChatMessageEntry> messages = new List<PluginChatMessageEntry>();
}
public class PluginChatMessageEntry {
public string steam_id;
[CanBeNull]
public string target_steam_id;
public bool is_team;
public string text;
}
class PluginReportsPayload {
public List<PluginReportEntry> reports = new List<PluginReportEntry>();
}
public static class PlayerAlertType {
public static readonly string join_with_ip_ban = "join_with_ip_ban";
public static readonly string dug_up_stash = "dug_up_stash";
public static readonly string custom_api = "custom_api";
}
public class PluginPlayerAlertEntry {
public string type;
public object meta;
}
public class PluginPlayerAlertJoinWithIpBanMeta {
public string steam_id;
public string ip;
public int ban_id;
}
public class PluginPlayerAlertDugUpStashMeta {
public string steam_id;
public string owner_steam_id;
public string position;
public string square;
}
public class PluginReportEntry {
public string initiator_steam_id;
[CanBeNull]
public string target_steam_id;
public List<string> sub_targets_steam_ids;
public string reason;
[CanBeNull]
public string message;
}
class PluginPairDto {
[CanBeNull]
public int ttl;
[CanBeNull]
public string token;
}
class PluginPlayerPayload {
private static bool IsRaidBlocked(BasePlayer player) {
if (_RustApp == null || !_RustApp.IsLoaded) {
return false;
}
var plugins = new List<Plugin> {
_RustApp.NoEscape, _RustApp.RaidZone, _RustApp.RaidBlock
}
;
var correct = plugins.Find(v => v != null);
if (correct != null) {
try {
switch (correct.Name) {
case "NoEscape" : {
return (bool)correct.Call("IsRaidBlocked", player);
}
case "RaidZone" : {
return (bool)correct.Call("HasBlock", player.userID.Get());
}
case "RaidBlock" : {
try {
return (bool)correct.Call("IsInRaid", player);
}
catch {
return (bool)correct.Call("IsRaidBlocked", player);
}
}
}
}
catch {
/**
_RustApp.Warning(
"Обнаружен плагин NoEscape, но не удалось вызвать API",
"Detected plugin NoEscape, but failed to call API"
);
*/
}
}
return false;
}
private static bool IsBuildingAuthed(BasePlayer player) {
var list = new List<BuildingPrivlidge>();
Vis.Entities(player.transform.position, 16f, list, Layers.PreventBuilding);
return list.FirstOrDefault()?.IsAuthed(player) ?? false;
}
private static bool NoLicense(Network.Connection connection) {
if (_RustApp.MultiFighting == null || !_RustApp.MultiFighting.IsLoaded) {
return false;
}
try {
var isSteam = (bool)_RustApp.MultiFighting.Call("IsSteam", connection);
return !isSteam;
}
catch {
return false;
}
}
public static PluginPlayerPayload FromPlayer(BasePlayer player) {
var payload = new PluginPlayerPayload();
payload.position = player.transform.position.ToString();
payload.rotation = player.eyes.rotation.ToString();
payload.coords = GridReference(player.transform.position);
payload.steam_id = player.UserIDString;
payload.steam_name = player.displayName.Replace("<blank>", "blank");
payload.ip = player.Connection.IPAddressWithoutPort();
payload.status = "active";
payload.can_build = PluginPlayerPayload.IsBuildingAuthed(player);
payload.is_raiding = PluginPlayerPayload.IsRaidBlocked(player);
payload.no_license = PluginPlayerPayload.NoLicense(player.Connection);
if (player.Team != null) {
payload.team = player.Team.members.Select(v => v.ToString()).Where(v => v != player.UserIDString).ToList();
}
return payload;
}
public static PluginPlayerPayload FromConnection(Network.Connection connection, string status) {
var payload = new PluginPlayerPayload();
payload.steam_id = connection.userid.ToString();
payload.steam_name = connection.username.Replace("<blank>", "blank");
payload.ip = connection.IPAddressWithoutPort();
payload.status = status;
payload.no_license = PluginPlayerPayload.NoLicense(connection);
var team = RelationshipManager.ServerInstance.FindPlayersTeam(connection.userid);
if (team != null) {
payload.team = team.members.Select(v => v.ToString()).Where(v => v != connection.userid.ToString()).ToList();
}
return payload;
}
public string steam_id;
public string steam_name;
public string ip;
[CanBeNull]
public string position;
[CanBeNull]
public string rotation;
[CanBeNull]
public string coords;
public bool can_build = false;
public bool is_raiding = false;
public bool no_license = false;
public string status;
public List<string> team = new List<string>();
}
public class MetaInfo {
public static MetaInfo Read() {
if (!Interface.Oxide.DataFileSystem.ExistsDatafile("RustApp_Configuration")) {
return null;
}
return Interface.Oxide.DataFileSystem.ReadObject<MetaInfo>("RustApp_Configuration");
}
public static void write(MetaInfo courtMeta) {
Interface.Oxide.DataFileSystem.WriteObject("RustApp_Configuration", courtMeta);
}
[JsonProperty("It is access for RustApp Court, never edit or share it")]
public string Value;
}
public class CheckInfo {
public static CheckInfo Read() {
if (!Interface.Oxide.DataFileSystem.ExistsDatafile("RustApp_CheckMeta")) {
return new CheckInfo();
}
return Interface.Oxide.DataFileSystem.ReadObject<CheckInfo>("RustApp_CheckMeta");
}
public static void write(CheckInfo courtMeta) {
// Clear checks > 30 days ago
courtMeta.LastChecks = courtMeta.LastChecks.Where(v => _RustApp.CurrentTime() - v.Value < 30 * 24 * 60 * 60).ToDictionary(v => v.Key, v => v.Value);
Interface.Oxide.DataFileSystem.WriteObject("RustApp_CheckMeta", courtMeta);
}
[JsonProperty("List of recent checks to show green-check on player")]
public Dictionary<string, double> LastChecks = new Dictionary<string, double>();
}
public class PairWorker : BaseWorker {
public string Code;
public void EnterCode(string code) {
@PairAssignData(code, (data) => {
Code = code;
if (data.token != null && data.token.Length > 0) {
Complete(data.token);
return;
}
Notify(data.ttl);
Invoke(nameof(PairWaitFinish), 1f);
}
, (err) => {
Destroy(this);
}
);
}
public void Notify(int left) {
_RustApp.Log($"Соединение установлено, завершите подключение на сайте.", $"Connection established, complete pair on site." );
}
public void Complete(string token) {
_RustApp.Log("Соединение установлено, перезагрузка...", "Connection established, reloading..." );
if (_RustApp != null && _RustApp._Worker != null) {
MetaInfo.write(new MetaInfo {
Value = token
}
);
_RustApp._Worker.Awake();
}
}
public void PairWaitFinish() {
if (Code == null) {
Destroy(this);
return;
}
@PairAssignData(Code, (data) => {
if (data.token == null || data.token.Length == 0) {
Invoke(nameof(PairWaitFinish), 1f);
Notify(data.ttl);
return;
}
Complete(data.token);
}
, (err) => {
Invoke(nameof(PairWaitFinish), 1f);
Interface.Oxide.LogError($"ошибка, {err}");
}
);
}
private void @PairAssignData(string code, Action<PluginPairDto> onComplete, Action<string> onException) {
var obj = new {
name = ConVar.Server.hostname, level = SteamServer.MapName ?? ConVar.Server.level, description = ConVar.Server.description + " " + ConVar.Server.motd, avatar_big = ConVar.Server.logoimage, banner_url = ConVar.Server.headerimage, online = BasePlayer.activePlayerList.Count + ServerMgr.Instance.connectionQueue.queue.Count + ServerMgr.Instance.connectionQueue.joining.Count, slots = ConVar.Server.maxplayers, port = ConVar.Server.port, branch = ConVar.Server.branch, connected = _RustApp._Worker?.Ban.IsAuthed()
}
;
Request<PluginPairDto>(CourtUrls.Pair + $"?code={code}", RequestMethod.POST, obj).Execute((data, raw) => {
if (data.ttl != null) {
data.ttl = (int)Math.Round((float)(data.ttl / 1000));
}
onComplete(data);
}
, (err) => {
if (err.Contains("code not exists")) {
_RustApp.Error("Ключ API более недействителен.", "API key is no longer valid." );
Destroy(this);
return;
}
onException(err);
}
);
}
}
public class CourtWorker : BaseWorker {
public ActionWorker Action;
public UpdateWorker Update;
public QueueWorker Queue;
public PairWorker Pair;
public BanWorker Ban;
private MetaInfo Meta;
public void Awake() {
OnDestroy();
_RustApp.Log("Добро пожаловать в RustApp! Подключение к серверам панели...", "Welcome to the RustApp! Connecting to servers..." );
Action = gameObject.AddComponent<ActionWorker>();
Update = gameObject.AddComponent<UpdateWorker>();
Queue = gameObject.AddComponent<QueueWorker>();
Ban = gameObject.AddComponent<BanWorker>();
Meta = MetaInfo.Read();
if (Meta != null) {
Connect();
}
else {
_RustApp.Warning("Ваш плагин не настроен, создайте сервер на сайте и следуйте указаниям", "Your plugin is not configured, create server on site and follow instructions" );
}
}
public void Connect() {
Auth(Meta.Value);
@ValidateSecret(() => EnableModules(), (err) => {
if (err.Contains("is lower than minimal")) {
_RustApp.Warning($"Ваша версия плагина слишком сильно устарела, необходимо скачать новую с сайта.", $"Your version of the plugin is too outdated, you need to download a new one from the website." );
return;
}
if (err.Contains("Authorization secret is corrupted")) {
_RustApp.Warning($"Ваш ключ более недействителен.", $"Your key is no longer valid." );
return;
}
if (err.Contains("Check server configuration, required ip") || err.Contains("Check server configuration, required port")) {
_RustApp.Warning($"Конфигурация вашего сервера изменилась, необходимо переподключить сервер через сайт.", $"Your server configuration has changed, you need to reconnect the server through the site." );
return;
}
_RustApp.Error($"Судя по всему RustApp временно недоступен, попробуем переподключиться через 10 секунд.", $"Apparently RustApp is temporarily unavailable, let's try reconnecting in 10 seconds." );
Invoke(nameof(Connect), 10f);
}
);
}
public void EnableModules() {
Action.Auth(Meta.Value);
Update.Auth(Meta.Value);
Queue.Auth(Meta.Value);
Ban.Auth(Meta.Value);
_RustApp.Log("Соединение установлено, плагин готов к работе!", "Connection established, plugin is ready!" );
_Settings.report_ui_commands.ForEach(v => {
_RustApp.cmd.AddChatCommand(v, _RustApp, nameof(_RustApp.ChatCmdReport));
}
);
_RustApp.cmd.AddChatCommand(_Settings.check_contact_command, _RustApp, nameof(_RustApp.CmdChatContact));
}
public void @ValidateSecret(Action? onComplete, Action<string>? onException) {
Request<object>(CourtUrls.Validate, RequestMethod.GET).Execute((data, raw) => onComplete?.Invoke(), (err) => onException?.Invoke(err) );
}
public void StartPair(string code) {
if (Pair != null) {
_RustApp.Warning("Вы уже подключаетесь, вы можете отменить запрос.", "You already connecting, you can reset state." );
return;
}
Pair = gameObject.AddComponent<PairWorker>();
Pair.EnterCode(code);
}
public void OnDestroy() {
if (Action != null) {
Destroy(Action);
}
if (Update != null) {
Destroy(Update);
}
if (Queue != null) {
Destroy(Queue);
}
if (Pair != null) {
Destroy(Pair);
}
if (Ban != null) {
Destroy(Ban);
}
}
}
public class ActionWorker : BaseWorker {
public void @SendContact(string steam_id, string message, Action<bool> callback) {
if (!IsReady()) {
return;
}
Request<object>(CourtUrls.SendContact, RequestMethod.POST, new {
steam_id, message
}
).Execute((data, raw) => callback(true), (err) => callback(false) );
}
public void @SendWipe(Action<bool> callback) {
if (!IsReady()) {
return;
}
Request<object>(CourtUrls.SendWipe, RequestMethod.POST).Execute((data, raw) => callback(true), (err) => callback(false) );
}
public void @SendBan(string steam_id, string reason, string duration, bool global, bool ban_ip) {
if (!IsReady()) {
return;
}
Request<object>(CourtUrls.BanCreate, RequestMethod.POST, new {
target_steam_id = steam_id, reason = reason, global = global, ban_ip = ban_ip, duration = duration.Length > 0 ? duration : null,
}
).Execute((data, raw) => {
_RustApp.Log($"Игрок {steam_id} заблокирован за {reason}", $"Player {steam_id} banned for {reason}" );
_RustApp.CloseConnection(steam_id, reason);
}
, (err) => _RustApp.Log($"Не удалось заблокировать {steam_id}.Причина: {err}", $"Failed to ban {steam_id}.Reason: {err}" ) );
}
public void @SendBanDelete(string steam_id) {
if (!IsReady()) {
return;
}
Request<object>(CourtUrls.BanDelete, RequestMethod.POST, new {
target_steam_id = steam_id,
}
).Execute((data, raw) => {
_RustApp.Log($"Игрок {steam_id} разблокирован", $"Player {steam_id} unbanned" );
}
, (err) => _RustApp.Log($"Не удалось разблокировать {steam_id}.Причина: {err}", $"Failed to unban {steam_id}.Reason: {err}" ) );
}
class CustomAlertMeta {
public string custom_icon = null;
public string name = "";
public List<string> custom_links = null;
}
public void @SendCustomAlert(Plugin plugin, string message, object data = null, object meta = null) {
if (!IsReady()) {
return;
}
CustomAlertMeta json = new CustomAlertMeta();
try {
if (meta != null) {
json = JsonConvert.DeserializeObject<CustomAlertMeta>(JsonConvert.SerializeObject(meta ?? new CustomAlertMeta()));
}
}
catch {
_RustApp.Error("Переданы неверные параметры CustomAlertMeta, будут использованы стандартные!", "Wrong CustomAlertMeta params, default will be used!");
}
Request<object>(CourtUrls.SendCustomAlert, RequestMethod.POST, new {
msg = message, data = data, custom_icon = json.custom_icon, hide_in_table = false, category = $"{plugin.Name} • {json.name}", custom_links = json.custom_links
}
).Execute(null, (err) => {
_RustApp.Puts(err);
_RustApp.Error($"Не удалось отправить кастомное оповещение ({message})", $"Failed to send custom-alert ({message})" );
}
);
}
public void @SendSignage(BaseImageUpdate upload) {
if (!IsReady()) {
return;
}
var obj = new {
steam_id = upload.PlayerId.ToString(), net_id = upload.Entity.net.ID.Value, base64_image = Convert.ToBase64String(upload.GetImage()), type = upload.Entity.ShortPrefabName, position = upload.Entity.transform.position.ToString(), square = GridReference(upload.Entity.transform.position)
}
;
Request<string>(CourtUrls.SendSignage, RequestMethod.POST, obj).Execute((data, raw) => {
}
, (err) => {
_RustApp.Error($"Не удалось отправить табличку ({err})", $"Failed to send signage ({err})" );
}
);
}
}
public class BanWorker : BaseWorker {
public class BanFetchResponse {
public List<BanFetchEntry> entries;
}
public class BanFetchPayload {
public string steam_id;
public string ip;
}
public class BanFetchEntry : BanFetchPayload {
public List<BanEntry> bans;
}
public class BanEntry {
public int id;
public string steam_id;
public string ban_ip;
public string reason;
public long expired_at;
public bool ban_ip_active;
public bool computed_is_active;
public int sync_project_id = 0;
public bool sync_should_kick = false;
}
private Dictionary<string, string> PlayersCollection = new Dictionary<string, string>();
public void Awake() {
foreach (var player in BasePlayer.activePlayerList) {
FetchBan(player);
}
foreach (var queued in ServerMgr.Instance.connectionQueue.queue) {
FetchBan(queued.userid.ToString(), queued.IPAddressWithoutPort());
}
foreach (var loading in ServerMgr.Instance.connectionQueue.joining) {
FetchBan(loading.userid.ToString(), loading.IPAddressWithoutPort());
}
}
protected override void OnReady() {
InvokeRepeating(nameof(FetchBans), 0f, 2f);
}
public void FetchBan(BasePlayer player) {
FetchBan(player.UserIDString, player.Connection.IPAddressWithoutPort());
}
public void FetchBan(string steamId, string ip) {
// Вызов хука на возможность игнорировать проверку
var over = Interface.Oxide.CallHook("RustApp_CanIgnoreBan", steamId);
if (over != null) {
return;
}
if (!PlayersCollection.ContainsKey(steamId)) {
PlayersCollection.Add(steamId, ip);
return;
}
PlayersCollection[steamId] = ip;
}
private void FetchBans() {
if (!IsReady() || PlayersCollection.Count == 0) {
return;
}
var banChecks = PlayersCollection.ToDictionary(v => v.Key, v => v.Value);
@FetchBans(banChecks, (steamId, ban) => {
if (PlayersCollection.ContainsKey(steamId)) {
PlayersCollection.Remove(steamId);
}
if (ban != null) {
if (ban.sync_project_id != 0 && !ban.sync_should_kick) {
return;
}
if (ban.steam_id == steamId) {
var format = ban.expired_at == 0 ? _RustApp.lang.GetMessage("System.Ban.Perm.Kick", _RustApp, steamId) : _RustApp.lang.GetMessage("System.Ban.Temp.Kick", _RustApp, steamId);
if (ban.sync_project_id != 0) {
format = ban.expired_at == 0 ? _RustApp.lang.GetMessage("System.BanSync.Perm.Kick", _RustApp, steamId) : _RustApp.lang.GetMessage("System.BanSync.Temp.Kick", _RustApp, steamId);
}
var time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(ban.expired_at + 3 * 60 * 60 * 1_000).ToString("dd.MM.yyyy HH:mm");
var text = format.Replace("%REASON%", ban.reason).Replace("%TIME%", time);
_RustApp.CloseConnection(steamId, text);
}
else {
_RustApp.CloseConnection(steamId, _RustApp.lang.GetMessage("System.Ban.Ip.Kick", _RustApp, steamId));
CreateAlertForIpBan(ban, steamId);
}
}
}
, () => {
_RustApp.Error($"Ошибка проверки блокировок ({banChecks.Keys.Count} шт.), пытаемся снова...", $"Ban check error ({banChecks.Keys.Count} total), attempting again..." );
// Возвращаем неудачно отправленные сообщения обратно в массив
var resurrectCollection = new Dictionary<string, string>();
foreach (var ban in banChecks) {
if (resurrectCollection.ContainsKey(ban.Key)) {
continue;
}
resurrectCollection.Add(ban.Key, ban.Value);
}
foreach (var ban in PlayersCollection) {
if (resurrectCollection.ContainsKey(ban.Key)) {
continue;
}
resurrectCollection.Add(ban.Key, ban.Value);
}
PlayersCollection = resurrectCollection;
}
);
PlayersCollection = new Dictionary<string, string>();
}
public void CreateAlertForIpBan(BanEntry ban, string steamId) {
if (ban.steam_id == steamId) {
return;
}
if (!ban.ban_ip_active) {
return;
}
_RustApp._Worker.Update.SaveAlert(new PluginPlayerAlertEntry {
type = PlayerAlertType.join_with_ip_ban, meta = new PluginPlayerAlertJoinWithIpBanMeta {
steam_id = steamId, ip = ban.ban_ip, ban_id = ban.id
}
}
);
}
public void @FetchBans(Dictionary<string, string> entries, Action<string, BanEntry> onBan, Action onException) {
/*
_RustApp.Log(
$"Проверяем блокировки игроков ({entries.Keys.Count} шт)",
$"Fetch players bans ({entries.Keys.Count} pc)"
);
*/
var players = new List<BanFetchPayload>();
foreach (var entry in entries) {
players.Add(new BanFetchPayload {
steam_id = entry.Key, ip = entry.Value
}
);
}
Request<BanFetchResponse>(BanUrls.Fetch, RequestMethod.POST, new {
players
}
).Execute((data, raw) => {
foreach (var player in players) {
var exists = data.entries.Find(v => v.steam_id == player.steam_id);
if (exists == null) {
_RustApp.Error($"Ответ бан-сервиса не содержит запрошенного игрока: {player.steam_id}", $"The ban service response does not contain the requested player: {player.steam_id}" );
return;
}
var active = exists.bans.FirstOrDefault(v => v.computed_is_active);
onBan?.Invoke(player.steam_id, active);
}
}
, (err) => {
Interface.Oxide.LogWarning(err);
onException?.Invoke();
}
);
}
}
public class UpdateWorker : BaseWorker {
private List<PluginPlayerAlertEntry> PlayerAlertCollection = new List<PluginPlayerAlertEntry>();
private List<PluginReportEntry> ReportCollection = new List<PluginReportEntry>();
private List<PluginChatMessageEntry> ChatCollection = new List<PluginChatMessageEntry>();
private Dictionary<string, string> DisconnectHistory = new Dictionary<string, string>();
private Dictionary<string, string> TeamChangeHistory = new Dictionary<string, string>();
private List<string> DestroyedSignsCollection = new List<string>();
protected override void OnReady() {
CancelInvoke(nameof(SendChat));
CancelInvoke(nameof(SendUpdate));
CancelInvoke(nameof(SendReports));
CancelInvoke(nameof(SendAlerts));
CancelInvoke(nameof(SendDestroyedSigns));
InvokeRepeating(nameof(SendChat), 0, 1f);
InvokeRepeating(nameof(SendUpdate), 0, 5f);
InvokeRepeating(nameof(SendReports), 0, 1f);
InvokeRepeating(nameof(SendAlerts), 0, 5f);
InvokeRepeating(nameof(SendDestroyedSigns), 0, 5f);
}
public void SaveChat(PluginChatMessageEntry message) {
ChatCollection.Add(message);
}
public void SaveAlert(PluginPlayerAlertEntry playerAlert) {
PlayerAlertCollection.Add(playerAlert);
}
public void SaveDestroyedSign(string net_id) {
DestroyedSignsCollection.Add(net_id);
}
public void SaveDisconnect(BasePlayer player, string reason) {
if (!DisconnectHistory.ContainsKey(player.UserIDString)) {
DisconnectHistory.Add(player.UserIDString, reason);
}
DisconnectHistory[player.UserIDString] = reason;
}
public void SaveTeamHistory(string initiator_steam_id, string target_steam_id) {
if (!TeamChangeHistory.ContainsKey(target_steam_id)) {
TeamChangeHistory.Add(target_steam_id, initiator_steam_id);
}
TeamChangeHistory[target_steam_id] = initiator_steam_id;
}
public void SaveReport(PluginReportEntry report) {
// Вызов хука на возможность игнорировать проверку
var over = Interface.Oxide.CallHook("RustApp_CanIgnoreReport", report.target_steam_id, report.initiator_steam_id);
if (over != null) {
return;
}
ReportCollection.Add(report);
}
private void SendDestroyedSigns() {
if (!IsReady() || DestroyedSignsCollection.Count == 0) {
return;
}
var destroyed_signs = DestroyedSignsCollection.ToList();
Request<string>(CourtUrls.SendDestroyedSignage, RequestMethod.DELETE, new {
net_ids = destroyed_signs
}
).Execute(null, (err) => {
_RustApp.Puts(err);
_RustApp.Error($"Не удалось отправить удаленные картинки ({destroyed_signs.Count} шт)", $"Failed to send destroyed signs ({destroyed_signs.Count} pc)" );
// Возвращаем неудачно отправленные сообщения обратно в массив
var resurrectCollection = new List<string>();
resurrectCollection.AddRange(destroyed_signs);
resurrectCollection.AddRange(DestroyedSignsCollection);
DestroyedSignsCollection = resurrectCollection;
}
);
DestroyedSignsCollection = new List<string>();
}
private void SendAlerts() {
if (!IsReady() || PlayerAlertCollection.Count == 0) {
return;
}
var alerts = PlayerAlertCollection.ToList();
Request<object>(CourtUrls.SendAlerts, RequestMethod.POST, new {
alerts
}
).Execute(null, (err) => {
_RustApp.Puts(err);
_RustApp.Error($"Не удалось отправить алерты ({alerts.Count} шт)", $"Failed to send alerts for player ({alerts.Count} pc)" );
// Возвращаем неудачно отправленные сообщения обратно в массив
var resurrectCollection = new List<PluginPlayerAlertEntry>();
resurrectCollection.AddRange(alerts);
resurrectCollection.AddRange(PlayerAlertCollection);
PlayerAlertCollection = resurrectCollection;
}
);
PlayerAlertCollection = new List<PluginPlayerAlertEntry>();
}
private void SendChat() {
if (!IsReady() || ChatCollection.Count == 0) {
return;
}
var messages = ChatCollection.ToList();
Request<object>(CourtUrls.SendChat, RequestMethod.POST, new {
messages
}
).Execute(null, (err) => {
_RustApp.Error($"Не удалось отправить сообщения из чата ({messages.Count} шт)", $"Failed to send messages from the chat ({messages.Count} pc)" );
// Возвращаем неудачно отправленные сообщения обратно в массив
var resurrectCollection = new List<PluginChatMessageEntry>();
resurrectCollection.AddRange(messages);
resurrectCollection.AddRange(ChatCollection);
ChatCollection = resurrectCollection;
}
);
ChatCollection = new List<PluginChatMessageEntry>();
}
public void SendReports() {
if (!IsReady() || ReportCollection.Count == 0) {
return;
}
var reports = ReportCollection.ToList();
Request<object>(CourtUrls.SendReports, RequestMethod.POST, new {
reports
}
).Execute(null, (err) => {
_RustApp.Error($"Не удалось отправить жалобы ({reports.Count} шт)", $"Failed to send messages from the chat ({reports.Count} pc)" );
// Возвращаем неудачно отправленные репорты обратно в массив
var resurrectCollection = new List<PluginReportEntry>();
resurrectCollection.AddRange(reports);
resurrectCollection.AddRange(ReportCollection);
ReportCollection = resurrectCollection;
}
);
ReportCollection = new List<PluginReportEntry>();
}
public void SendUpdate() {
if (!IsReady()) {
return;
}
var players = new List<PluginPlayerPayload>();
foreach (var player in BasePlayer.activePlayerList) {
try {
players.Add(PluginPlayerPayload.FromPlayer(player));
}
catch (Exception exc) {
}
}
foreach (var player in ServerMgr.Instance.connectionQueue.queue) {
try {
players.Add(PluginPlayerPayload.FromConnection(player, "queued"));
}
catch (Exception exc) {
}
}
foreach (var player in ServerMgr.Instance.connectionQueue.joining) {
try {
players.Add(PluginPlayerPayload.FromConnection(player, "joining"));
}
catch (Exception exc) {
}
}
var disconnected = DisconnectHistory.ToDictionary(v => v.Key, v => v.Value);
var team_changes = TeamChangeHistory.ToDictionary(v => v.Key, v => v.Value);
var payload = new {
hostname = ConVar.Server.hostname, level = SteamServer.MapName ?? ConVar.Server.level, avatar_url = ConVar.Server.logoimage, banner_url = ConVar.Server.headerimage, slots = ConVar.Server.maxplayers, version = _RustApp.Version.ToString(), performance = _RustApp.TotalHookTime.ToString(), players, disconnected = disconnected, team_changes = team_changes
}
;
Request<object>(CourtUrls.SendState, RequestMethod.PUT, payload).Execute((data, raw) => {
}
, (err) => {
//_RustApp.Error(
// $"Не удалось отправить состояние сервера ({err})",
// $"Failed to send server status ({err})"
//);
/**
Возвращаем неудачно отправленные дисконекты
*/
var resurrectCollectionDisconnects = new Dicti
// ... [Code preview truncated due to length. Full source in attached file] ...