using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Microsoft.CodeAnalysis;
using ProjectM;
using ProjectM.Network;
using ScarletCore.Data;
using ScarletCore.Events;
using ScarletCore.Services;
using ScarletCore.Systems;
using ScarletCore.Utils;
using ScarletHooks.Data;
using ScarletHooks.Systems;
using ScarletRCON.Shared;
using Stunlock.Core;
using Unity.Collections;
using Unity.Entities;
using VampireCommandFramework;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("ScarletHooks")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("ScarletHooks")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+8089890c32adf5b83e2f9a20c8ecb1b3e23af955")]
[assembly: AssemblyProduct("ScarletHooks")]
[assembly: AssemblyTitle("ScarletHooks")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace ScarletHooks
{
public static class ECSExtensions
{
public static EntityManager EntityManager => GameSystems.EntityManager;
public unsafe static void Write<T>(this Entity entity, T componentData) where T : struct
{
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
ComponentType val = default(ComponentType);
((ComponentType)(ref val))..ctor(Il2CppType.Of<T>(), (AccessMode)0);
byte[] array = StructureToByteArray(componentData);
int num = Marshal.SizeOf<T>();
fixed (byte* ptr = array)
{
EntityManager entityManager = EntityManager;
((EntityManager)(ref entityManager)).SetComponentDataRaw(entity, val.TypeIndex, (void*)ptr, num);
}
}
public static byte[] StructureToByteArray<T>(T structure) where T : struct
{
int num = Marshal.SizeOf(structure);
byte[] array = new byte[num];
IntPtr intPtr = Marshal.AllocHGlobal(num);
Marshal.StructureToPtr(structure, intPtr, fDeleteOld: true);
Marshal.Copy(intPtr, array, 0, num);
Marshal.FreeHGlobal(intPtr);
return array;
}
public unsafe static T Read<T>(this Entity entity) where T : struct
{
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
ComponentType val = default(ComponentType);
((ComponentType)(ref val))..ctor(Il2CppType.Of<T>(), (AccessMode)0);
EntityManager entityManager = EntityManager;
void* componentDataRawRO = ((EntityManager)(ref entityManager)).GetComponentDataRawRO(entity, val.TypeIndex);
return Marshal.PtrToStructure<T>(new IntPtr(componentDataRawRO));
}
public static DynamicBuffer<T> ReadBuffer<T>(this Entity entity) where T : struct
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
EntityManager entityManager = EntityManager;
return ((EntityManager)(ref entityManager)).GetBuffer<T>(entity, false);
}
public static bool Has<T>(this Entity entity)
{
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
ComponentType val = default(ComponentType);
((ComponentType)(ref val))..ctor(Il2CppType.Of<T>(), (AccessMode)0);
EntityManager entityManager = EntityManager;
return ((EntityManager)(ref entityManager)).HasComponent(entity, val);
}
public static void Add<T>(this Entity entity)
{
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
ComponentType val = default(ComponentType);
((ComponentType)(ref val))..ctor(Il2CppType.Of<T>(), (AccessMode)0);
EntityManager entityManager = EntityManager;
((EntityManager)(ref entityManager)).AddComponent(entity, val);
}
public static void Remove<T>(this Entity entity)
{
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
ComponentType val = default(ComponentType);
((ComponentType)(ref val))..ctor(Il2CppType.Of<T>(), (AccessMode)0);
EntityManager entityManager = EntityManager;
((EntityManager)(ref entityManager)).RemoveComponent(entity, val);
}
}
[BepInPlugin("ScarletHooks", "ScarletHooks", "1.1.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BasePlugin
{
private static Harmony _harmony;
public static Harmony Harmony => _harmony;
public static Plugin Instance { get; private set; }
public static ManualLogSource LogInstance { get; private set; }
public static Settings Settings { get; private set; }
public static Database Database { get; private set; }
public override void Load()
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Expected O, but got Unknown
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Expected O, but got Unknown
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Expected O, but got Unknown
//IL_009a: Unknown result type (might be due to invalid IL or missing references)
//IL_00a4: Expected O, but got Unknown
Instance = this;
ManualLogSource log = ((BasePlugin)this).Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(27, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Plugin ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("ScarletHooks");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" version ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("1.1.0");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" is loaded!");
}
log.LogInfo(val);
_harmony = new Harmony("ScarletHooks");
_harmony.PatchAll(Assembly.GetExecutingAssembly());
Settings = new Settings("ScarletHooks", (BasePlugin)(object)Instance);
Database = new Database("ScarletHooks");
LoadSettings();
MessageDispatchSystem.Initialize();
CommandRegistry.RegisterAll();
RconCommandRegistrar.RegisterAll();
}
public override bool Unload()
{
CommandRegistry.UnregisterAssembly();
RconCommandRegistrar.UnregisterAssembly();
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
return true;
}
public static void LoadSettings()
{
SettingsSection val = Settings.Section("General");
val.Add<string>("AdminWebhookURL", "null", "Admin Webhook URL. All messages configured for admin will be sent to this address.");
val.Add<string>("PublicWebhookURL", "null", "Public Webhook URL. All messages configured for public will be sent to this address.");
val.Add<string>("LoginWebhookURL", "null", "Login Webhook URL. Only login messages will be sent to this address.");
val.Add<string>("PvpKillWebhookURL", "null", "PvP Kill Webhook URL. Only PvP kill messages will be sent to this address.");
val.Add<string>("VBloodWebhookURL", "null", "VBlood Death Webhook URL. Only VBlood death messages will be sent to this address.");
val.Add<bool>("EnableBatching", true, "Enable or disable batching messages to avoid rate limiting.\nUseful for large servers or when many messages are sent at once.");
val.Add<float>("MessageInterval", 0.2f, "Interval in seconds between sending messages.\nUseful to prevent rate limiting when sending messages to webhooks.");
val.Add<float>("OnFailInterval", 2f, "Interval in seconds to wait before retrying after a webhook failure.");
SettingsSection val2 = Settings.Section("Customization");
val2.Add<string>("LoginMessageFormat", "{playerName} has joined the game.", "Format for login messages.\nAvailable placeholders: {playerName}, {clanName}");
val2.Add<string>("LogoutMessageFormat", "{playerName} has left the game.", "Format for logout messages.\nAvailable placeholders: {playerName}, {clanName}");
val2.Add<string>("GlobalPrefix", "[Global] {playerName}:", "Prefix for global chat messages.\nAvailable placeholders: {playerName}, {clanName}");
val2.Add<string>("LocalPrefix", "[Local] {playerName}:", "Prefix for local chat messages.\nAvailable placeholders: {playerName}, {clanName}");
val2.Add<string>("ClanPrefix", "[Clan][{clanName}] {playerName}:", "Prefix for clan chat messages.\nAvailable placeholders: {playerName}, {clanName}");
val2.Add<string>("WhisperPrefix", "[Whisper to {targetName}] {playerName}:", "Prefix for whisper messages.\nAvailable placeholders: {playerName}, {clanName}, {targetName}");
val2.Add<string>("VBloodDeathMessageFormat", "{playerName} has killed {VBloodName}.", "Format for VBlood death messages.\nAvailable placeholders: {playerName}, {vBloodName}");
val2.Add<string>("PvpKillMessageFormat", "[{clanName}] {playerName} has killed [{clanName}] {targetName}.", "Format for PvP kill messages.\nAvailable placeholders: {playerName}, {targetName}, {clanName}");
SettingsSection val3 = Settings.Section("Admin");
val3.Add<bool>("AdminGlobalMessages", true, "Enable or disable sending global chat messages to the Admin Webhook.");
val3.Add<bool>("AdminLocalMessages", true, "Enable or disable sending local chat messages to the Admin Webhook.");
val3.Add<bool>("AdminClanMessages", true, "Enable or disable sending clan chat messages to the Admin Webhook.");
val3.Add<bool>("AdminWhisperMessages", true, "Enable or disable sending whisper messages to the Admin Webhook.");
val3.Add<bool>("AdminLoginMessages", true, "Enable or disable sending login messages to the Admin Webhook.");
val3.Add<bool>("AdminVBloodMessages", true, "Enable or disable sending VBlood death messages to the Admin Webhook.");
val3.Add<bool>("AdminPvpMessages", true, "Enable or disable sending PvP kill messages to the Admin Webhook.");
SettingsSection val4 = Settings.Section("Public");
val4.Add<bool>("PublicGlobalMessages", true, "Enable or disable sending global chat messages to the Public Webhook.");
val4.Add<bool>("PublicLocalMessages", false, "Enable or disable sending local chat messages to the Public Webhook.");
val4.Add<bool>("PublicClanMessages", false, "Enable or disable sending clan chat messages to the Public Webhook.");
val4.Add<bool>("PublicWhisperMessages", false, "Enable or disable sending whisper messages to the Public Webhook.");
val4.Add<bool>("PublicLoginMessages", false, "Enable or disable sending login messages to the Public Webhook.");
val4.Add<bool>("PublicVBloodMessages", true, "Enable or disable sending VBlood death messages to the Public Webhook.");
val4.Add<bool>("PublicPvpMessages", true, "Enable or disable sending PvP kill messages to the Public Webhook.");
SettingsSection val5 = Settings.Section("Clans");
val5.Add<bool>("ClanLoginMessages", true, "Enable or disable sending login messages to clans.");
}
public static void ReloadSettings()
{
Settings.Dispose();
LoadSettings();
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "ScarletHooks";
public const string PLUGIN_NAME = "ScarletHooks";
public const string PLUGIN_VERSION = "1.1.0";
}
}
namespace ScarletHooks.Systems
{
public static class MessageDispatchSystem
{
private static readonly HttpClient _httpClient = new HttpClient();
public static readonly ConcurrentDictionary<string, Queue<string>> MessageQueues = new ConcurrentDictionary<string, Queue<string>>();
public static readonly ConcurrentDictionary<string, DateTime> LastUsed = new ConcurrentDictionary<string, DateTime>();
private static readonly ConcurrentDictionary<string, int> ConsecutiveFailures = new ConcurrentDictionary<string, int>();
private const int MaxFailuresBeforeDiscard = 5;
private static CancellationTokenSource _cts;
public static bool ShowRunning = false;
private static bool _isRunning = false;
private static Settings Settings => Plugin.Settings;
private static Database Database => Plugin.Database;
public static string AdminWebHookUrl => Settings.Get<string>("AdminWebhookURL");
public static string PublicWebHookUrl => Settings.Get<string>("PublicWebhookURL");
public static string LoginWebhookURL => Settings.Get<string>("LoginWebhookURL");
public static string PvpKillWebhookURL => Settings.Get<string>("PvpKillWebhookURL");
public static string VBloodWebhookURL => Settings.Get<string>("VBloodWebhookURL");
public static float MessageInterval => Settings.Get<float>("MessageInterval");
public static float OnFailInterval => Settings.Get<float>("OnFailInterval");
public static bool EnableBatching => Settings.Get<bool>("EnableBatching");
public static Dictionary<string, string> ClanWebHookUrls { get; set; } = new Dictionary<string, string>();
public static void Initialize()
{
_isRunning = true;
LoadFromFile();
EventManager.OnUserConnected += HandleConnectionEvent;
EventManager.OnUserDisconnected += HandleDisconnectionEvent;
EventManager.OnChatMessage += HandleMessageEvent;
EventManager.OnPlayerDeath += HandlePvpDeathEvent;
EventManager.OnVBloodDeath += HandleVBloodDeathEvent;
_cts = new CancellationTokenSource();
Task.Run(() => ProcessQueueLoop(_cts.Token));
}
public static void Shutdown()
{
_cts?.Cancel();
_isRunning = false;
}
public static void ForceShutdown()
{
_cts?.Cancel();
_isRunning = false;
ClearAll();
}
public static void ClearAll()
{
MessageQueues.Clear();
LastUsed.Clear();
ConsecutiveFailures.Clear();
}
public static void LoadFromFile()
{
if (Database.Has("ClanWebHookUrls"))
{
ClanWebHookUrls = Database.Load<Dictionary<string, string>>("ClanWebHookUrls");
}
}
public static void HandlePvpDeathEvent(object _, DeathEventArgs args)
{
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Unknown result type (might be due to invalid IL or missing references)
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
//IL_0068: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
if (args == null || !_isRunning)
{
return;
}
List<DeathInfo> deaths = args.Deaths;
PlayerData val2 = default(PlayerData);
PlayerData val3 = default(PlayerData);
foreach (DeathInfo item in deaths)
{
Entity died = item.Died;
Entity killer = item.Killer;
if (died.Has<PlayerCharacter>() && killer.Has<PlayerCharacter>())
{
PlayerCharacter val = died.Read<PlayerCharacter>();
if (!PlayerService.TryGetByName(((object)(FixedString64Bytes)(ref val.Name)).ToString(), ref val2))
{
break;
}
val = killer.Read<PlayerCharacter>();
if (!PlayerService.TryGetByName(((object)(FixedString64Bytes)(ref val.Name)).ToString(), ref val3))
{
break;
}
string text = Settings.Get<string>("PvpKillMessageFormat");
string content = text.Replace("{playerName}", val3.Name).Replace("{targetName}", val2.Name).Replace("[{clanName}]", "[" + val3.ClanName + "]")
.Replace("{clanName}", (val3.ClanName ?? "") ?? "");
if (Settings.Get<bool>("AdminPvpMessages"))
{
AddToQueue(AdminWebHookUrl, content);
}
if (Settings.Get<bool>("PublicPvpMessages"))
{
AddToQueue(PublicWebHookUrl, content);
}
AddToQueue(PvpKillWebhookURL, content);
}
}
}
public static void HandleVBloodDeathEvent(object _, DeathEventArgs args)
{
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_0097: Unknown result type (might be due to invalid IL or missing references)
if (args == null || !_isRunning)
{
return;
}
List<DeathInfo> deaths = args.Deaths;
PlayerData val2 = default(PlayerData);
foreach (DeathInfo item in deaths)
{
Entity died = item.Died;
Entity killer = item.Killer;
if (killer.Has<PlayerCharacter>())
{
PlayerCharacter val = killer.Read<PlayerCharacter>();
if (!PlayerService.TryGetByName(((object)(FixedString64Bytes)(ref val.Name)).ToString(), ref val2))
{
break;
}
Dictionary<int, string> names = VBloods.Names;
PrefabGUID val3 = died.Read<PrefabGUID>();
string newValue = names[((PrefabGUID)(ref val3)).GuidHash];
string text = Settings.Get<string>("VBloodDeathMessageFormat");
string content = text.Replace("{playerName}", val2.Name).Replace("{VBloodName}", newValue);
if (Settings.Get<bool>("AdminVBloodMessages"))
{
AddToQueue(AdminWebHookUrl, content);
}
if (Settings.Get<bool>("PublicVBloodMessages"))
{
AddToQueue(PublicWebHookUrl, content);
}
AddToQueue(VBloodWebhookURL, content);
}
}
}
private static void HandleConnectionEvent(object _, UserConnectedEventArgs args)
{
if (args != null && _isRunning)
{
PlayerData player = args.Player;
DateTime utcNow = DateTime.UtcNow;
if (!((utcNow - player.ConnectedSince).TotalSeconds < 10.0))
{
HandleLoginMessage(player.Name, player.ClanName);
}
player.SetData<DateTime>(utcNow);
}
}
private static void HandleDisconnectionEvent(object _, UserDisconnectedEventArgs args)
{
PlayerData player = args.Player;
DateTime utcNow = DateTime.UtcNow;
DateTime data = player.GetData<DateTime>();
if (!(data != default(DateTime)) || !((utcNow - data).TotalSeconds < 10.0))
{
HandleLogoutMessage(player.Name, player.ClanName);
}
}
public static void HandleMessageEvent(object _, ChatMessageEventArgs args)
{
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
if (args != null && _isRunning && !string.IsNullOrEmpty(args.Message) && !string.IsNullOrEmpty(args.Sender.Name))
{
string message = args.Message;
string name = args.Sender.Name;
ChatMessageType messageType = args.MessageType;
string clanName = args.Sender.ClanName;
PlayerData receiver = args.Receiver;
HandleMessage(message, name, messageType, clanName, (receiver != null) ? receiver.Name : null);
}
}
private static void HandleMessage(string content, string playerName, ChatMessageType messageType, string clanName, string targetName)
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Expected I4, but got Unknown
if (!string.IsNullOrEmpty(content) && _isRunning)
{
switch ((int)messageType)
{
case 0:
GlobalMessage(content, playerName, clanName);
break;
case 4:
LocalMessage(content, playerName, clanName);
break;
case 2:
ClanMessage(content, playerName, clanName);
break;
case 3:
WhisperMessage(content, playerName, targetName, clanName);
break;
case 1:
break;
}
}
}
public static void HandleLoginMessage(string playerName, string clanName)
{
if (string.IsNullOrEmpty(playerName) || !_isRunning)
{
return;
}
string text = Settings.Get<string>("LoginMessageFormat");
if (!string.IsNullOrEmpty(text))
{
string content = text.Replace("{playerName}", playerName).Replace("{clanName}", clanName);
if (Settings.Get<bool>("AdminLoginMessages"))
{
AddToQueue(AdminWebHookUrl, content);
}
if (Settings.Get<bool>("PublicLoginMessages"))
{
AddToQueue(PublicWebHookUrl, content);
}
if (!string.IsNullOrEmpty(clanName) && Settings.Get<bool>("ClanLoginMessages") && ClanWebHookUrls.TryGetValue(clanName, out var value))
{
AddToQueue(value, content);
}
AddToQueue(LoginWebhookURL, content);
}
}
public static void HandleLogoutMessage(string playerName, string clanName)
{
if (string.IsNullOrEmpty(playerName) || !_isRunning)
{
return;
}
string text = Settings.Get<string>("LogoutMessageFormat");
if (!string.IsNullOrEmpty(text))
{
string content = text.Replace("{playerName}", playerName).Replace("{clanName}", clanName);
if (Settings.Get<bool>("AdminLoginMessages"))
{
AddToQueue(AdminWebHookUrl, content);
}
if (Settings.Get<bool>("PublicLoginMessages"))
{
AddToQueue(PublicWebHookUrl, content);
}
if (!string.IsNullOrEmpty(clanName) && Settings.Get<bool>("ClanLoginMessages") && ClanWebHookUrls.TryGetValue(clanName, out var value))
{
AddToQueue(value, content);
}
AddToQueue(LoginWebhookURL, content);
}
}
public static void GlobalMessage(string content, string playerName, string clanName)
{
SendMessage(content, playerName, clanName, "GlobalPrefix", "AdminGlobalMessages", "PublicGlobalMessages");
}
public static void LocalMessage(string content, string playerName, string clanName)
{
SendMessage(content, playerName, clanName, "LocalPrefix", "AdminLocalMessages", "PublicLocalMessages");
}
public static void ClanMessage(string content, string playerName, string clanName)
{
if (!string.IsNullOrEmpty(clanName))
{
string text = BuildPrefix("ClanPrefix", playerName, clanName);
if (Settings.Get<bool>("AdminClanMessages"))
{
AddToQueue(AdminWebHookUrl, text + " " + content);
}
if (Settings.Get<bool>("PublicClanMessages"))
{
AddToQueue(PublicWebHookUrl, text + " " + content);
}
if (ClanWebHookUrls.TryGetValue(clanName, out var value))
{
AddToQueue(value, text + " " + content);
}
}
}
public static void WhisperMessage(string content, string playerName, string targetName, string clanName)
{
string text = Settings.Get<string>("WhisperPrefix").Replace("{playerName}", playerName).Replace("{targetName}", targetName ?? "")
.Replace("{clanName}", clanName ?? "");
if (Settings.Get<bool>("AdminWhisperMessages"))
{
AddToQueue(AdminWebHookUrl, text + " " + content);
}
if (Settings.Get<bool>("PublicWhisperMessages"))
{
AddToQueue(PublicWebHookUrl, text + " " + content);
}
}
private static void SendMessage(string content, string playerName, string clanName, string prefixKey, string adminSettingKey, string publicSettingKey)
{
string text = BuildPrefix(prefixKey, playerName, clanName);
if (Settings.Get<bool>(adminSettingKey))
{
AddToQueue(AdminWebHookUrl, text + " " + content);
}
if (Settings.Get<bool>(publicSettingKey))
{
AddToQueue(PublicWebHookUrl, text + " " + content);
}
}
private static string BuildPrefix(string prefixKey, string playerName, string clanName)
{
return Settings.Get<string>(prefixKey).Replace("{playerName}", playerName).Replace("{clanName}", clanName ?? "");
}
public static void AddToQueue(string webhookUrl, string content)
{
if (!string.IsNullOrEmpty(webhookUrl) && IsValidUrl(webhookUrl))
{
Queue<string> orAdd = MessageQueues.GetOrAdd(webhookUrl, (string _) => new Queue<string>());
lock (orAdd)
{
orAdd.Enqueue(content);
}
LastUsed.TryAdd(webhookUrl, DateTime.MinValue);
}
}
public static void AddClan(string clanName)
{
ClanWebHookUrls[clanName] = null;
Database.Save<Dictionary<string, string>>("ClanWebHookUrls", ClanWebHookUrls);
}
public static void RemoveClan(string clanName)
{
ClanWebHookUrls.Remove(clanName);
Database.Save<Dictionary<string, string>>("ClanWebHookUrls", ClanWebHookUrls);
}
public static async Task<bool> TrySendMessage(string content, string webhookUrl = null)
{
if (string.IsNullOrEmpty(webhookUrl) || !IsValidUrl(webhookUrl))
{
Console.WriteLine("Invalid webhook url.");
return false;
}
var payload = new { content };
string json = JsonSerializer.Serialize(payload);
StringContent contentData = new StringContent(json, Encoding.UTF8, "application/json");
try
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, webhookUrl)
{
Content = contentData
};
HttpResponseMessage response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Failed to send webhook. Status: {response.StatusCode}, Content: {errorContent}");
return false;
}
return true;
}
catch (Exception ex)
{
Console.WriteLine("Exception while sending webhook message: " + ex.Message);
return false;
}
}
private static async Task ProcessQueueLoop(CancellationToken token)
{
while (_isRunning && !token.IsCancellationRequested)
{
foreach (KeyValuePair<string, Queue<string>> kvp in MessageQueues)
{
string webhookUrl = kvp.Key;
Queue<string> queue = kvp.Value;
DateTime lastUsed = LastUsed.GetOrAdd(webhookUrl, DateTime.MinValue);
if (!((DateTime.Now - lastUsed).TotalSeconds >= (double)MessageInterval))
{
continue;
}
int itemsToDequeue = 1;
string content;
lock (queue)
{
if (queue.Count == 0)
{
continue;
}
if (EnableBatching)
{
content = string.Join("\n", queue);
itemsToDequeue = queue.Count;
}
else
{
content = queue.Peek();
}
goto IL_016b;
}
IL_016b:
int value;
Queue<string> value2;
if (await TrySendMessage(content, webhookUrl))
{
lock (queue)
{
int i = 0;
while (i < itemsToDequeue && queue.Count > 0)
{
queue.Dequeue();
value = i++;
}
}
if (queue.Count == 0)
{
MessageQueues.TryRemove(webhookUrl, out value2);
}
ConsecutiveFailures[webhookUrl] = 0;
LastUsed[webhookUrl] = DateTime.Now;
}
else
{
int failures = ConsecutiveFailures.AddOrUpdate(webhookUrl, 1, (string _, int count) => count + 1);
LastUsed[webhookUrl] = DateTime.Now.AddSeconds(OnFailInterval);
if (failures >= 5)
{
Console.WriteLine($"[Webhook Error] Discarding queue for {webhookUrl} after {failures} failures.");
MessageQueues.TryRemove(webhookUrl, out value2);
ConsecutiveFailures.TryRemove(webhookUrl, out value);
}
}
content = null;
}
try
{
await Task.Delay(TimeSpan.FromSeconds(MessageInterval), token);
}
catch (TaskCanceledException ex)
{
Console.WriteLine("[Webhook Error] Task was canceled: " + ex.Message);
break;
}
}
}
public static bool IsValidUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return false;
}
return Regex.IsMatch(url, "^https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)$");
}
}
}
namespace ScarletHooks.Data
{
[StructLayout(LayoutKind.Sequential, Size = 1)]
public readonly struct VBloods
{
public static readonly Dictionary<int, string> Names = new Dictionary<int, string>
{
{ 1106149033, "Grayson the Armourer" },
{ 2122229952, "Rufus the Foreman" },
{ -2025101517, "Errol the Stonebreaker" },
{ 1112948824, "Lord Styx the Night Champion" },
{ -1936575244, "Gorecrusher the Behemoth" },
{ -910296704, "Matka the Curse Weaver" },
{ -203043163, "Albert the Duke of Balaton" },
{ -1905691330, "Alpha the White Wolf" },
{ -1065970933, "Terah the Geomancer" },
{ -2013903325, "Mairwyn the Elementalist" },
{ 1896428751, "Clive the Firestarter" },
{ -1659822956, "Quincey the Bandit King" },
{ -548489519, "Ungora the Spider Queen" },
{ 685266977, "Morian the Stormwing Matriarch" },
{ -393555055, "Talzur the Winged Horror" },
{ 939467639, "Leandra the Shadow Priestess" },
{ 326378955, "Cyril the Cursed Smith" },
{ -680831417, "Raziel the Shepherd" },
{ -29797003, "Vincent the Frostbringer" },
{ 1688478381, "Octavian the Militia Captain" },
{ 850622034, "Meredith the Bright Archer" },
{ 153390636, "Nicholaus the Fallen" },
{ 577478542, "Goreswine the Ravager" },
{ 613251918, "Bane the Shadowblade" },
{ -1968372384, "Jade the Vampire Hunter" },
{ 24378719, "Frostmaw the Mountain Terror" },
{ -1347412392, "Terrorclaw the Ogre" },
{ -1365931036, "Kriig the Undead General" },
{ -1208888966, "Foulrot the Soultaker" },
{ -2039908510, "Putrid Rat" },
{ -1449631170, "Tristan the Vampire Hunter" },
{ 109969450, "Ben the Old Wanderer" },
{ -1942352521, "Beatrice the Tailor" },
{ 1124739990, "Keely the Frost Archer" },
{ 763273073, "Lidia the Chaos Archer" },
{ -2122682556, "Finn the Fisherman" },
{ 114912615, "Azariel the Sunbringer" },
{ -26105228, "Sir Magnus the Overseer" },
{ 192051202, "Baron du Bouchon the Sommelier" },
{ 1233988687, "Adam the Firstborn" },
{ -740796338, "Solarus the Immaculate" },
{ -1391546313, "Kodia the Ferocious Bear" },
{ 172235178, "Ziva the Engineer" },
{ 814083983, "Henry Blackbrew the Doctor" },
{ -1101874342, "Domina the Blade Dancer" },
{ 910988233, "Grethel the Glassblower" },
{ -99012450, "Christina the Sun Priestess" },
{ 106480588, "Angram the Purifier" },
{ 2054432370, "Voltatia the Power Master" },
{ 1945956671, "Maja the Dark Savant" },
{ 495971434, "General Valencia the Depraved" },
{ 591725925, "Megara the Serpent Queen" },
{ -327335305, "Dracula the Immortal King" },
{ -496360395, "General Cassius the Betrayer" },
{ 795262842, "General Elena the Hollow" },
{ -1505705712, "Willfred the Village Elder" },
{ -484556888, "Polora the Feywalker" },
{ 336560131, "Simon Belmont the Vampire Hunter" },
{ -1383529374, "Jakira the Shadow Huntress" },
{ 619948378, "Sir Erwin the Gallant Cavalier" },
{ -753453016, "Gaius the Cursed Champion" },
{ -1669199769, "Stavros the Carver" },
{ 173259239, "Dantos the Forgebinder" },
{ 1295855316, "Lucile the Venom Alchemist" }
};
}
}
namespace ScarletHooks.Commands
{
[CommandGroup("hooks", null)]
public static class AdminCommands
{
private static Settings Settings => Plugin.Settings;
[Command("add", null, null, "Add a new clan to the list. The clan name is case-sensitive. If you are unsure, use 'hooks afp' to add from the player's name.", null, true)]
public static void Add(ChatCommandContext ctx, string clanName)
{
if (string.IsNullOrEmpty(clanName))
{
ctx.Reply(RichTextFormatter.FormatError("You must provide a clan name."));
}
else if (MessageDispatchSystem.ClanWebHookUrls.ContainsKey(clanName))
{
ctx.Reply(RichTextFormatter.FormatError("This clan already exists, please go to the config file to change the webhook url."));
ctx.Reply(RichTextFormatter.FormatError("Don't forget to reload the webhooks after changing the webhook url."));
}
else
{
MessageDispatchSystem.AddClan(clanName);
ctx.Reply(RichTextFormatter.Format("Clan ~" + clanName + "~ added to the list of clans, please go to the config file to set the webhook url.", (List<string>)null));
ctx.Reply(RichTextFormatter.Format("Don't forget to reload the webhooks after changing the webhook url.", (List<string>)null));
}
}
[Command("afp", null, null, null, null, true)]
public static void AddFromPlayer(ChatCommandContext ctx, string playerName)
{
PlayerData val = default(PlayerData);
if (!PlayerService.TryGetByName(playerName, ref val) || string.IsNullOrEmpty(val.ClanName))
{
ctx.Reply(RichTextFormatter.FormatError("Player '" + playerName + "' not found or does not belong to a clan."));
return;
}
string clanName = val.ClanName;
if (MessageDispatchSystem.ClanWebHookUrls.ContainsKey(clanName))
{
ctx.Reply(RichTextFormatter.FormatError("This clan already exists, please go to the config file to change the webhook url."));
ctx.Reply(RichTextFormatter.FormatError("Don't forget to reload the webhooks after changing the webhook url."));
return;
}
MessageDispatchSystem.AddClan(clanName);
ctx.Reply(RichTextFormatter.Format($"Clan ~{clanName}~ (from player '{playerName}') added to the list of clans, please go to the config file to set the webhook url.", (List<string>)null));
ctx.Reply(RichTextFormatter.Format("Don't forget to reload the webhooks after changing the webhook url.", (List<string>)null));
}
[Command("remove", null, null, null, null, true)]
public static void Remove(ChatCommandContext ctx, string name)
{
string text = null;
PlayerData val = default(PlayerData);
if (PlayerService.TryGetByName(name, ref val))
{
text = val.ClanName;
}
if (string.IsNullOrEmpty(text))
{
text = name;
}
if (!string.IsNullOrEmpty(text))
{
if (!MessageDispatchSystem.ClanWebHookUrls.ContainsKey(text))
{
ctx.Reply(RichTextFormatter.FormatError("No clan named ~" + text + "~ is loaded."));
return;
}
MessageDispatchSystem.RemoveClan(text);
ctx.Reply(RichTextFormatter.Format("Clan ~" + text + "~ removed from the list of clans.", (List<string>)null));
}
}
[Command("reload settings", null, null, "Reload all settings.", null, true)]
public static void RealodSettings(ChatCommandContext ctx)
{
Plugin.ReloadSettings();
ctx.Reply(RichTextFormatter.Format("~Settings reloaded.~", (List<string>)null));
}
[Command("reload webhooks", null, null, "Reload all webhooks.", null, true)]
public static void RealodWebhooks(ChatCommandContext ctx)
{
MessageDispatchSystem.LoadFromFile();
ctx.Reply(RichTextFormatter.Format("~Webhooks reloaded.~", (List<string>)null));
}
[Command("reload", null, null, "Reload all settings and webhooks.", null, true)]
public static void Realod(ChatCommandContext ctx)
{
Plugin.ReloadSettings();
MessageDispatchSystem.LoadFromFile();
ctx.Reply(RichTextFormatter.Format("~Settings and webhooks reloaded.~", (List<string>)null));
}
[Command("list", null, null, "List all webhooks.", null, true)]
public static void ListClanWebHookUrls(ChatCommandContext ctx)
{
ctx.Reply(RichTextFormatter.Format("~Admin~: " + Settings.Get<string>("AdminWebhookURL") + ".", (List<string>)null));
ctx.Reply(RichTextFormatter.Format("~Public~: " + Settings.Get<string>("PublicWebhookURL") + ".", (List<string>)null));
ctx.Reply(RichTextFormatter.Format("List of clans webhooks: ", (List<string>)null));
foreach (var (value, value2) in MessageDispatchSystem.ClanWebHookUrls)
{
ctx.Reply(RichTextFormatter.Format($"~{value}~: {value2}.", (List<string>)null));
}
ctx.Reply(RichTextFormatter.Format($"Total webhook urls: ~{MessageDispatchSystem.ClanWebHookUrls.Count + 2}~.", (List<string>)null));
}
[Command("settings", null, null, null, null, true)]
public static void ChangeSettings(ChatCommandContext ctx, string settings, bool value)
{
if (!Settings.Has(settings))
{
ctx.Reply(RichTextFormatter.Format("~" + settings + "~ does not exist.", (List<string>)null));
return;
}
List<string> list = new List<string>(4) { "AdminWebhookURL", "PublicWebhookURL", "MessageInterval", "OnFailInterval" };
if (list.Contains(settings))
{
ctx.Reply(RichTextFormatter.Format("~" + settings + "~ cannot be changed via command.", (List<string>)null));
return;
}
Settings.Set<bool>(settings, value);
ctx.Reply(RichTextFormatter.Format($"~{settings}~ changed to ~{value}~.", (List<string>)null));
}
[Command("settings", null, null, null, null, true)]
public static void ShowSettings(ChatCommandContext ctx)
{
ctx.Reply(RichTextFormatter.Format("~Current settings:~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format("AdminWebhookURL: ~" + Settings.Get<string>("AdminWebhookURL") + "~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format("PublicWebhookURL: ~" + Settings.Get<string>("PublicWebhookURL") + "~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"MessageInterval: ~{Settings.Get<float>("MessageInterval")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"OnFailInterval: ~{Settings.Get<float>("OnFailInterval")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"EnableBatching: ~{Settings.Get<bool>("EnableBatching")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"AdminGlobalMessages: ~{Settings.Get<bool>("AdminGlobalMessages")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"AdminClanMessages: ~{Settings.Get<bool>("AdminClanMessages")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"AdminLocalMessages: ~{Settings.Get<bool>("AdminLocalMessages")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"AdminWhisperMessages: ~{Settings.Get<bool>("AdminWhisperMessages")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"PublicGlobalMessages: ~{Settings.Get<bool>("PublicGlobalMessages")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"PublicClanMessages: ~{Settings.Get<bool>("PublicClanMessages")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"PublicLocalMessages: ~{Settings.Get<bool>("PublicLocalMessages")}~", (List<string>)null));
ctx.Reply(RichTextFormatter.Format($"PublicWhisperMessages: ~{Settings.Get<bool>("PublicWhisperMessages")}~", (List<string>)null));
}
[Command("start", null, null, null, null, true)]
public static void Start(ChatCommandContext ctx)
{
MessageDispatchSystem.Initialize();
ctx.Reply(RichTextFormatter.Format("~Message dispatch system started.~", (List<string>)null));
}
[Command("stop", null, null, null, null, true)]
public static void Stop(ChatCommandContext ctx)
{
MessageDispatchSystem.Shutdown();
ctx.Reply(RichTextFormatter.Format("~Message dispatch system stopped.~", (List<string>)null));
}
[Command("forcestop", null, null, null, null, true)]
public static void ForceStop(ChatCommandContext ctx)
{
MessageDispatchSystem.ForceShutdown();
ctx.Reply(RichTextFormatter.Format("~Message dispatch system stopped and cleared all cache.~", (List<string>)null));
}
}
[RconCommandCategory("ScarletHooks")]
public class AdminRconCommands
{
private static Settings Settings => Plugin.Settings;
[RconCommand("add", "Add a new clan to the list. The clan name is case-sensitive. If you are unsure, use 'scarlethooks.afp' to add from the player's name.", null)]
public static string Add(string clanName)
{
if (string.IsNullOrEmpty(clanName))
{
return "You must provide a clan name.";
}
if (MessageDispatchSystem.ClanWebHookUrls.ContainsKey(clanName))
{
return "This clan already exists, please use the 'scarlethooks.setclanwebhook' command to change the webhook url.";
}
MessageDispatchSystem.AddClan(clanName);
return "Clan " + clanName + " added to the list of clans, please use the 'scarlethooks.setclanwebhook' command to set the webhook url.";
}
[RconCommand("afp", "Add a clan to the list using a player's name.", null)]
public static string AddFromPlayer(string playerName)
{
PlayerData val = default(PlayerData);
if (!PlayerService.TryGetByName(playerName, ref val) || string.IsNullOrEmpty(val.ClanName))
{
return "Player '" + playerName + "' not found or does not belong to a clan.";
}
string clanName = val.ClanName;
if (MessageDispatchSystem.ClanWebHookUrls.ContainsKey(clanName))
{
return "This clan already exists, please use the 'scarlethooks.setclanwebhook' command to change the webhook url.";
}
MessageDispatchSystem.AddClan(clanName);
return $"Clan {clanName} (from player '{playerName}') added to the list of clans, please use the 'scarlethooks.setclanwebhook' command to set the webhook url.";
}
[RconCommand("remove", "Remove a clan from the list by clan or player name.", null)]
public static string Remove(string name)
{
string text = null;
PlayerData val = default(PlayerData);
if (PlayerService.TryGetByName(name, ref val))
{
text = val.ClanName;
}
if (string.IsNullOrEmpty(text))
{
text = name;
}
if (string.IsNullOrEmpty(text))
{
return "";
}
if (!MessageDispatchSystem.ClanWebHookUrls.ContainsKey(text))
{
return "No clan named " + text + " is loaded.";
}
MessageDispatchSystem.RemoveClan(text);
return "Clan " + text + " removed from the list of clans.";
}
[RconCommand("reload settings", "Reload the settings from the configuration file.", null)]
public static string ReloadSettings()
{
Plugin.ReloadSettings();
return "Settings reloaded.";
}
[RconCommand("reload webhooks", "Reload the webhooks from the configuration file.", null)]
public static string ReloadWebhooks()
{
MessageDispatchSystem.LoadFromFile();
return "Webhooks reloaded.";
}
[RconCommand("reload", "Reload both settings and webhooks from the configuration file.", null)]
public static string Reload()
{
Plugin.ReloadSettings();
MessageDispatchSystem.LoadFromFile();
return "Settings and webhooks reloaded.";
}
[RconCommand("list", "List all configured webhook URLs.", null)]
public static string ListClanWebHookUrls()
{
StringBuilder stringBuilder = new StringBuilder();
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(8, 1, stringBuilder2);
handler.AppendLiteral("Admin: ");
handler.AppendFormatted(Settings.Get<string>("AdminWebhookURL"));
handler.AppendLiteral(".");
stringBuilder3.AppendLine(ref handler);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
handler.AppendLiteral("Public: ");
handler.AppendFormatted(Settings.Get<string>("PublicWebhookURL"));
handler.AppendLiteral(".");
stringBuilder4.AppendLine(ref handler);
stringBuilder.AppendLine("List of clans webhooks:");
foreach (KeyValuePair<string, string> clanWebHookUrl in MessageDispatchSystem.ClanWebHookUrls)
{
clanWebHookUrl.Deconstruct(out var key, out var value);
string value2 = key;
string value3 = value;
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(3, 2, stringBuilder2);
handler.AppendFormatted(value2);
handler.AppendLiteral(": ");
handler.AppendFormatted(value3);
handler.AppendLiteral(".");
stringBuilder5.AppendLine(ref handler);
}
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder6 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(21, 1, stringBuilder2);
handler.AppendLiteral("Total webhook urls: ");
handler.AppendFormatted(MessageDispatchSystem.ClanWebHookUrls.Count + 2);
handler.AppendLiteral(".");
stringBuilder6.AppendLine(ref handler);
return stringBuilder.ToString();
}
[RconCommand("settings", "Change a boolean setting.", null)]
public static string ChangeSettings(string settings, bool value)
{
if (!Settings.Has(settings))
{
return settings + " does not exist.";
}
List<string> list = new List<string>(4) { "AdminWebhookURL", "PublicWebhookURL", "MessageInterval", "OnFailInterval" };
if (list.Contains(settings))
{
return settings + " cannot be changed via command.";
}
Settings.Set<bool>(settings, value);
return $"{settings} changed to {value}.";
}
[RconCommand("settings", "Show all current settings.", null)]
public static string ShowSettings()
{
string text = "Current settings:";
text = text + "AdminWebhookURL: " + Settings.Get<string>("AdminWebhookURL") + "\n";
text = text + "PublicWebhookURL: " + Settings.Get<string>("PublicWebhookURL") + "\n";
text += $"MessageInterval: {Settings.Get<float>("MessageInterval")}\n";
text += $"OnFailInterval: {Settings.Get<float>("OnFailInterval")}\n";
text += $"EnableBatching: {Settings.Get<bool>("EnableBatching")}\n";
text += $"AdminGlobalMessages: {Settings.Get<bool>("AdminGlobalMessages")}\n";
text += $"AdminClanMessages: {Settings.Get<bool>("AdminClanMessages")}\n";
text += $"AdminLocalMessages: {Settings.Get<bool>("AdminLocalMessages")}\n";
text += $"AdminWhisperMessages: {Settings.Get<bool>("AdminWhisperMessages")}\n";
text += $"PublicGlobalMessages: {Settings.Get<bool>("PublicGlobalMessages")}\n";
text += $"PublicClanMessages: {Settings.Get<bool>("PublicClanMessages")}\n";
text += $"PublicLocalMessages: {Settings.Get<bool>("PublicLocalMessages")}\n";
return text + $"PublicWhisperMessages: {Settings.Get<bool>("PublicWhisperMessages")}\n";
}
[RconCommand("start", "Start the message dispatch system.", null)]
public static string Start()
{
MessageDispatchSystem.Initialize();
return "Message dispatch system started.";
}
[RconCommand("stop", "Stop the message dispatch system.", null)]
public static string Stop()
{
MessageDispatchSystem.Shutdown();
return "Message dispatch system stopped.";
}
[RconCommand("forcestop", "Force stop the message dispatch system and clear all cache.", null)]
public static string ForceStop()
{
MessageDispatchSystem.ForceShutdown();
return "Message dispatch system stopped and cleared all cache.";
}
[RconCommand("setadminwebhook", "Set the admin webhook URL.", null)]
public static string SetAdminWebhook(string url)
{
if (string.IsNullOrEmpty(url))
{
return "You must provide a valid URL.";
}
Settings.Set<string>("AdminWebhookURL", url);
return "Admin webhook URL set to: " + url;
}
[RconCommand("setpublicwebhook", "Set the public webhook URL.", null)]
public static string SetPublicWebhook(string url)
{
if (string.IsNullOrEmpty(url))
{
return "You must provide a valid URL.";
}
Settings.Set<string>("PublicWebhookURL", url);
return "Public webhook URL set to: " + url;
}
[RconCommand("setloginwebhook", "Set the login webhook URL.", null)]
public static string SetLoginWebhook(string url)
{
if (string.IsNullOrEmpty(url))
{
return "You must provide a valid URL.";
}
Settings.Set<string>("LoginWebhookURL", url);
return "Login webhook URL set to: " + url;
}
[RconCommand("setclanwebhook", "Set the webhook URL for a specific clan.", null)]
public static string SetClanWebhook(string clanName, string url)
{
if (string.IsNullOrEmpty(clanName) || string.IsNullOrEmpty(url))
{
return "You must provide a clan name and a valid URL.";
}
if (!MessageDispatchSystem.ClanWebHookUrls.ContainsKey(clanName))
{
return "Clan " + clanName + " does not exist. Use the 'add' command to add it first.";
}
MessageDispatchSystem.ClanWebHookUrls[clanName] = url;
Plugin.Database.Save<Dictionary<string, string>>("ClanWebHookUrls", MessageDispatchSystem.ClanWebHookUrls);
return "Webhook URL for clan " + clanName + " set to: " + url;
}
}
}
namespace ScarletRCON.Shared
{
[AttributeUsage(AttributeTargets.Class)]
public class RconCommandCategoryAttribute : Attribute
{
public string Name { get; }
public RconCommandCategoryAttribute(string categoryName)
{
Name = categoryName;
base..ctor();
}
}
[AttributeUsage(AttributeTargets.Method)]
public class RconCommandAttribute : Attribute
{
public string Name { get; set; }
public string? Description { get; set; }
public string? Usage { get; set; }
public RconCommandAttribute(string name, string? description = null, string? usage = null)
{
Name = name;
Description = description;
Usage = usage;
base..ctor();
}
}
public static class RconCommandRegistrar
{
private static bool? _isScarletRconAvailable;
public static bool IsScarletRconAvailable()
{
bool valueOrDefault = _isScarletRconAvailable.GetValueOrDefault();
if (!_isScarletRconAvailable.HasValue)
{
valueOrDefault = Type.GetType("ScarletRCON.CommandSystem.CommandHandler, ScarletRCON") != null;
_isScarletRconAvailable = valueOrDefault;
}
return _isScarletRconAvailable.Value;
}
public static void RegisterAll()
{
if (!IsScarletRconAvailable())
{
return;
}
Assembly callingAssembly = Assembly.GetCallingAssembly();
string item = callingAssembly.GetName().Name.ToLowerInvariant() + ".";
List<(string, string, MethodInfo, string, string, string)> list = new List<(string, string, MethodInfo, string, string, string)>();
Type[] types = callingAssembly.GetTypes();
foreach (Type type in types)
{
string item2 = type.GetCustomAttribute<RconCommandCategoryAttribute>()?.Name ?? "Uncategorized";
MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo methodInfo in methods)
{
RconCommandAttribute customAttribute = methodInfo.GetCustomAttribute<RconCommandAttribute>();
if (customAttribute == null)
{
continue;
}
string text = customAttribute.Usage;
if (string.IsNullOrWhiteSpace(text))
{
ParameterInfo[] parameters = methodInfo.GetParameters();
text = ((parameters.Length == 0) ? "" : string.Join(" ", parameters.Select((ParameterInfo p) => (p.ParameterType == typeof(List<string>)) ? "<...text>" : ("<" + p.Name + ">"))));
}
list.Add((item2, item, methodInfo, customAttribute.Name, customAttribute.Description, text));
}
}
Type type2 = Type.GetType("ScarletRCON.CommandSystem.CommandHandler, ScarletRCON");
MethodInfo method = type2.GetMethod("RegisterExternalCommandsBatch", BindingFlags.Static | BindingFlags.Public);
if (type2 == null)
{
throw new InvalidOperationException("ScarletRCON.CommandSystem.CommandHandler not found.");
}
if (method == null)
{
throw new InvalidOperationException("ScarletRCON.CommandSystem.CommandHandler.RegisterExternalCommandsBatch not found.");
}
method.Invoke(null, new object[1] { list });
}
public static void UnregisterAssembly(Assembly? assembly = null)
{
if (IsScarletRconAvailable())
{
if ((object)assembly == null)
{
assembly = Assembly.GetCallingAssembly();
}
Type type = Type.GetType("ScarletRCON.CommandSystem.CommandHandler, ScarletRCON");
MethodInfo methodInfo = type?.GetMethod("UnregisterAssembly", BindingFlags.Static | BindingFlags.Public);
if (type == null)
{
throw new InvalidOperationException("ScarletRCON.CommandSystem.CommandHandler not found.");
}
if (methodInfo == null)
{
throw new InvalidOperationException("ScarletRCON.CommandSystem.CommandHandler.UnregisterAssembly not found.");
}
methodInfo.Invoke(null, new object[1] { assembly });
}
}
}
}