Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Discord Screenshots v1.6.0
discordScreenshots.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; 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.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Extensions; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("discordScreenshots")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("discordScreenshots")] [assembly: AssemblyCopyright("Copyright © 2023")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("CA695DDD-2CE8-407B-B7A2-9FB6D3783286")] [assembly: AssemblyFileVersion("0.1.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] 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; } } } [HarmonyPatch(typeof(Player), "OnDamaged")] public static class RemoveDamageFlashOnDeath { [HarmonyPostfix] private static bool Prefix(Player __instance, HitData hit) { if (hit.GetTotalDamage() >= ((Character)__instance).GetHealth()) { return false; } return true; } } namespace discordScreenshots { public class BepinexConfiguration { public static ConfigFile Config; public static ConfigEntry<string> WebhookURL; public static ConfigEntry<string> WebhookUsername; public static ConfigEntry<string> WebhookAvatarURL; public static ConfigEntry<string> DeathMessage; public static ConfigEntry<KeyCode> ScreenshotHotkey; public static ConfigEntry<string> HotkeyScreenshotWebhookURL; public static ConfigEntry<string> HotkeyScreenshotWebhookUsername; public static ConfigEntry<string> HotkeyScreenshotWebhookAvatarURL; public static ConfigEntry<string> HotkeyScreenshotMessage; private static readonly Random _random = new Random(); public static string GetRandomDeathMessage() { string value = DeathMessage.Value; if (string.IsNullOrEmpty(value)) { return "met their demise!"; } string[] array = (from m in value.Split(new char[1] { ';' }) select m.Trim() into m where !string.IsNullOrEmpty(m) select m).ToArray(); if (array.Length == 0) { return "met their demise!"; } return array[_random.Next(array.Length)]; } public static void GenerateConfigs(ConfigFile configFile) { Config = configFile; ScreenshotHotkey = ConfigFileExtensions.BindConfig<KeyCode>(Config, "Screenshot", "Hotkey", (KeyCode)293, "The hotkey to take a screenshot and send to Discord.", false, (int?)1, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); WebhookURL = ConfigFileExtensions.BindConfig<string>(Config, "Webhook", "URL", "", "The URL of the Discord webhook to send messages to.", true, (int?)2, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); WebhookUsername = ConfigFileExtensions.BindConfig<string>(Config, "Webhook", "Username", "Valheim Death Bot", "The username of the Discord webhook to send messages to.", true, (int?)3, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); WebhookAvatarURL = ConfigFileExtensions.BindConfig<string>(Config, "Webhook", "AvatarURL", "", "The avatar URL of the Discord webhook to send messages to.", true, (int?)4, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); DeathMessage = ConfigFileExtensions.BindConfig<string>(Config, "Death Screenshot", "Message", "met their demise! Final moments captured...", "The message to send with death screenshots (player name will be prepended automatically).", true, (int?)5, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HotkeyScreenshotWebhookURL = ConfigFileExtensions.BindConfig<string>(Config, "Player Capture Webhook", "URL", "", "Optional separate webhook URL for player capture (F12) screenshots. If empty, uses the main Webhook URL.", true, (int?)6, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HotkeyScreenshotWebhookUsername = ConfigFileExtensions.BindConfig<string>(Config, "Player Capture Webhook", "Username", "Valheim Screenshot Bot", "The username for the player capture webhook.", true, (int?)7, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HotkeyScreenshotWebhookAvatarURL = ConfigFileExtensions.BindConfig<string>(Config, "Player Capture Webhook", "AvatarURL", "", "The avatar URL for the player capture webhook.", true, (int?)8, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HotkeyScreenshotMessage = ConfigFileExtensions.BindConfig<string>(Config, "Player Capture Webhook", "Message", "captured this screenshot!", "The message to send with player capture screenshots (player name will be prepended automatically).", true, (int?)9, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } } public class SimpleDiscordWebhook { private readonly string _webhookUrl; private readonly string? _username; private readonly string? _avatarUrl; public SimpleDiscordWebhook(string webhookUrl, string? username = null, string? avatarUrl = null) { if (string.IsNullOrEmpty(webhookUrl)) { throw new ArgumentException("Webhook URL cannot be null or empty", "webhookUrl"); } _webhookUrl = webhookUrl; _username = username; _avatarUrl = avatarUrl; } public async Task SendMessageAsync(string message) { if (string.IsNullOrEmpty(message)) { throw new ArgumentException("Message cannot be null or empty", "message"); } string jsonPayload = JsonConvert.SerializeObject((object)new SimpleWebhookPayload { content = message, username = _username, avatar_url = _avatarUrl }); await SendPayloadAsync(jsonPayload); } public void SendMessage(string message) { SendMessageAsync(message).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); } public async Task SendScreenshotAsync(string? message = null, string? filename = null) { string filename2 = filename; string message2 = message; try { if (string.IsNullOrEmpty(filename2)) { filename2 = $"valheim_screenshot_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png"; } else if (!filename2.EndsWith(".png")) { filename2 += ".png"; } Texture2D val = ScreenCapture.CaptureScreenshotAsTexture(); if ((Object)(object)val == (Object)null) { throw new Exception("Failed to capture screenshot - returned null texture"); } byte[] pngData = ImageConversion.EncodeToPNG(val); Object.DestroyImmediate((Object)(object)val); if (pngData == null || pngData.Length == 0) { throw new Exception("Failed to encode screenshot to PNG"); } Debug.Log((object)$"Screenshot captured and encoded - {pngData.Length} bytes, uploading..."); await Task.Run(async delegate { await SendFileAsync(pngData, filename2, message2); }); } catch (Exception ex) { throw new Exception("Error capturing and sending screenshot: " + ex.Message, ex); } } public Texture2D CaptureScreenshot() { Texture2D obj = ScreenCapture.CaptureScreenshotAsTexture(); if ((Object)(object)obj == (Object)null) { throw new Exception("Failed to capture screenshot - returned null texture"); } return obj; } public byte[] ProcessScreenshot(Texture2D screenshot) { byte[] result = ImageConversion.EncodeToPNG(screenshot); Object.DestroyImmediate((Object)(object)screenshot); return result; } public async Task SendFileAsync(byte[] fileData, string filename, string? message = null) { if (fileData == null || fileData.Length == 0) { throw new ArgumentException("File data cannot be null or empty", "fileData"); } if (string.IsNullOrEmpty(filename)) { throw new ArgumentException("Filename cannot be null or empty", "filename"); } string boundary = "----formdata-discord-" + DateTime.Now.Ticks.ToString("x"); using MemoryStream memoryStream = new MemoryStream(); await WriteMultipartFormDataAsync(memoryStream, boundary, fileData, filename, message); byte[] formData = memoryStream.ToArray(); await SendMultipartPayloadAsync(formData, boundary); } private async Task SendPayloadAsync(string jsonPayload) { try { byte[] bytes = Encoding.UTF8.GetBytes(jsonPayload); WebRequest request = WebRequest.Create(_webhookUrl); request.Method = "POST"; request.ContentType = "application/json"; request.ContentLength = bytes.Length; using (Stream stream = request.GetRequestStream()) { await stream.WriteAsync(bytes, 0, bytes.Length); } using WebResponse response = request.GetResponse(); if (response is HttpWebResponse httpWebResponse && httpWebResponse.StatusCode != HttpStatusCode.NoContent && httpWebResponse.StatusCode != HttpStatusCode.OK) { throw new Exception($"Discord webhook returned status: {httpWebResponse.StatusCode}"); } using Stream stream = response.GetResponseStream(); if (stream == null) { return; } using StreamReader streamReader = new StreamReader(stream); string text = await streamReader.ReadToEndAsync(); if (!string.IsNullOrEmpty(text)) { Debug.Log((object)("Discord response: " + text)); } } catch (WebException ex) { string errorMessage = "Failed to send webhook message"; WebResponse response2 = ex.Response; if (response2 is HttpWebResponse errorResponse) { using Stream stream = errorResponse.GetResponseStream(); if (stream != null) { using StreamReader streamReader = new StreamReader(stream); string arg = await streamReader.ReadToEndAsync(); errorMessage += $": {errorResponse.StatusCode} - {arg}"; } } throw new Exception(errorMessage, ex); } catch (Exception ex2) { throw new Exception("Error sending Discord webhook: " + ex2.Message, ex2); } } private async Task SendMultipartPayloadAsync(byte[] formData, string boundary) { try { WebRequest request = WebRequest.Create(_webhookUrl); request.Method = "POST"; request.ContentType = "multipart/form-data; boundary=" + boundary; request.ContentLength = formData.Length; using (Stream stream = request.GetRequestStream()) { await stream.WriteAsync(formData, 0, formData.Length); } using WebResponse response = request.GetResponse(); if (response is HttpWebResponse httpWebResponse && httpWebResponse.StatusCode != HttpStatusCode.NoContent && httpWebResponse.StatusCode != HttpStatusCode.OK) { throw new Exception($"Discord webhook returned status: {httpWebResponse.StatusCode}"); } using Stream stream = response.GetResponseStream(); if (stream == null) { return; } using StreamReader streamReader = new StreamReader(stream); string text = await streamReader.ReadToEndAsync(); if (!string.IsNullOrEmpty(text)) { Debug.Log((object)("Discord file upload response: " + text)); } } catch (WebException ex) { string errorMessage = "Failed to send webhook file"; WebResponse response2 = ex.Response; if (response2 is HttpWebResponse errorResponse) { using Stream stream = errorResponse.GetResponseStream(); if (stream != null) { using StreamReader streamReader = new StreamReader(stream); string arg = await streamReader.ReadToEndAsync(); errorMessage += $": {errorResponse.StatusCode} - {arg}"; } } throw new Exception(errorMessage, ex); } catch (Exception ex2) { throw new Exception("Error sending Discord webhook file: " + ex2.Message, ex2); } } private async Task WriteMultipartFormDataAsync(Stream stream, string boundary, byte[] fileData, string filename, string? message) { string newLine = "\r\n"; byte[] boundaryBytes = Encoding.UTF8.GetBytes("--" + boundary + newLine); await stream.WriteAsync(boundaryBytes, 0, boundaryBytes.Length); string s = "Content-Disposition: form-data; name=\"files[0]\"; filename=\"" + filename + "\"" + newLine + "Content-Type: image/png" + newLine + newLine; byte[] bytes = Encoding.UTF8.GetBytes(s); await stream.WriteAsync(bytes, 0, bytes.Length); await stream.WriteAsync(fileData, 0, fileData.Length); byte[] newLineBytes = Encoding.UTF8.GetBytes(newLine); await stream.WriteAsync(newLineBytes, 0, newLineBytes.Length); if (!string.IsNullOrEmpty(message)) { await stream.WriteAsync(boundaryBytes, 0, boundaryBytes.Length); SimpleWebhookPayload simpleWebhookPayload = new SimpleWebhookPayload { content = message, username = _username, avatar_url = _avatarUrl }; string jsonPayload = JsonConvert.SerializeObject((object)simpleWebhookPayload); string s2 = "Content-Disposition: form-data; name=\"payload_json\"" + newLine + "Content-Type: application/json" + newLine + newLine; byte[] bytes2 = Encoding.UTF8.GetBytes(s2); await stream.WriteAsync(bytes2, 0, bytes2.Length); byte[] bytes3 = Encoding.UTF8.GetBytes(jsonPayload); await stream.WriteAsync(bytes3, 0, bytes3.Length); await stream.WriteAsync(newLineBytes, 0, newLineBytes.Length); } byte[] bytes4 = Encoding.UTF8.GetBytes("--" + boundary + "--" + newLine); await stream.WriteAsync(bytes4, 0, bytes4.Length); } public static async Task SendQuickMessageAsync(string webhookUrl, string message, string? username = null, string? avatarUrl = null) { await new SimpleDiscordWebhook(webhookUrl, username, avatarUrl).SendMessageAsync(message); } public static void SendQuickMessage(string webhookUrl, string message, string? username = null, string? avatarUrl = null) { SendQuickMessageAsync(webhookUrl, message, username, avatarUrl).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); } public static async Task SendQuickScreenshotAsync(string webhookUrl, string? message = null, string? username = null, string? avatarUrl = null, string? filename = null) { await new SimpleDiscordWebhook(webhookUrl, username, avatarUrl).SendScreenshotAsync(message, filename); } public static void SendQuickScreenshot(string webhookUrl, string? message = null, string? username = null, string? avatarUrl = null, string? filename = null) { SendQuickScreenshotAsync(webhookUrl, message, username, avatarUrl, filename).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); } } internal class SimpleWebhookPayload { public string? content { get; set; } public string? username { get; set; } public string? avatar_url { get; set; } } [BepInPlugin("warpalicious.discordScreenshots", "discordScreenshots", "1.6.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class discordScreenshotsPlugin : BaseUnityPlugin { private const string ModName = "discordScreenshots"; private const string ModVersion = "1.6.0"; private const string Author = "warpalicious"; private const string ModGUID = "warpalicious.discordScreenshots"; private static string ConfigFileName = "warpalicious.discordScreenshots.cfg"; private static string ConfigFileFullPath; private readonly Harmony HarmonyInstance = new Harmony("warpalicious.discordScreenshots"); public static readonly ManualLogSource TemplateLogger; public static AssetBundle assetBundle; private DateTime _lastReloadTime; private const long RELOAD_DELAY = 10000000L; public void Awake() { Assembly executingAssembly = Assembly.GetExecutingAssembly(); HarmonyInstance.PatchAll(executingAssembly); SetupWatcher(); BepinexConfiguration.GenerateConfigs(((BaseUnityPlugin)this).Config); } public static void LoadAssetBundle() { assetBundle = AssetUtils.LoadAssetBundleFromResources("discordscreenshots", Assembly.GetExecutingAssembly()); } private void OnDestroy() { ((BaseUnityPlugin)this).Config.Save(); } private void SetupWatcher() { _lastReloadTime = DateTime.Now; FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName); fileSystemWatcher.Changed += ReadConfigValues; fileSystemWatcher.Created += ReadConfigValues; fileSystemWatcher.Renamed += ReadConfigValues; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.EnableRaisingEvents = true; } private void ReadConfigValues(object sender, FileSystemEventArgs e) { DateTime now = DateTime.Now; long num = now.Ticks - _lastReloadTime.Ticks; if (File.Exists(ConfigFileFullPath) && num >= 10000000) { try { TemplateLogger.LogInfo((object)"Attempting to reload configuration..."); ((BaseUnityPlugin)this).Config.Reload(); TemplateLogger.LogInfo((object)"Configuration reloaded successfully!"); } catch { TemplateLogger.LogError((object)("There was an issue loading " + ConfigFileName)); return; } _lastReloadTime = now; if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsDedicated()) { TemplateLogger.LogInfo((object)"Updating runtime configurations..."); } } } static discordScreenshotsPlugin() { string configPath = Paths.ConfigPath; char directorySeparatorChar = Path.DirectorySeparatorChar; ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName; TemplateLogger = Logger.CreateLogSource("discordScreenshots"); } } } namespace discordScreenshots.Patches { [HarmonyPatch(typeof(Terminal), "InitTerminal")] public static class DiscordConsoleCommandPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static ConsoleEvent <>9__0_1; public static ConsoleEvent <>9__0_2; internal void <Postfix>b__0_0(ConsoleEventArgs args) { <>c__DisplayClass0_0 CS$<>8__locals0 = new <>c__DisplayClass0_0(); if (args.Length < 2) { Terminal context = args.Context; if (context != null) { context.AddString("Usage: discord <message>"); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString("Example: discord Hello from Valheim!"); } return; } CS$<>8__locals0.message = string.Join(" ", args.Args, 1, args.Args.Length - 1); if (string.IsNullOrWhiteSpace(CS$<>8__locals0.message)) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("Message cannot be empty"); } return; } CS$<>8__locals0.playerName = "Server"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { CS$<>8__locals0.playerName = Player.m_localPlayer.GetPlayerName(); } Terminal context4 = args.Context; if (context4 != null) { context4.AddString("Sending message to Discord..."); } Task.Run(async delegate { try { string formattedMessage = "\ud83d\udcac **" + CS$<>8__locals0.playerName + "**: " + CS$<>8__locals0.message; await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.WebhookURL.Value, formattedMessage, "Valheim Console"); Debug.Log((object)("Discord message sent successfully: " + formattedMessage)); } catch (Exception ex) { Debug.LogError((object)("Failed to send Discord message: " + ex.Message)); } }); } internal void <Postfix>b__0_1(ConsoleEventArgs args) { <>c__DisplayClass0_1 CS$<>8__locals0 = new <>c__DisplayClass0_1(); Terminal context = args.Context; if (context != null) { context.AddString("Testing Discord webhook connection..."); } CS$<>8__locals0.playerName = "Server"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { CS$<>8__locals0.playerName = Player.m_localPlayer.GetPlayerName(); } Task.Run(async delegate { try { await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.WebhookURL.Value, "\ud83e\uddea Discord webhook test from **" + CS$<>8__locals0.playerName + "** - Connection working!", "Valheim Test Bot"); Debug.Log((object)"Discord webhook test message sent successfully!"); } catch (Exception ex) { Debug.LogError((object)("Discord webhook test failed: " + ex.Message)); } }); } internal void <Postfix>b__0_2(ConsoleEventArgs args) { <>c__DisplayClass0_2 CS$<>8__locals0 = new <>c__DisplayClass0_2 { playerName = "Unknown Player" }; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { CS$<>8__locals0.playerName = Player.m_localPlayer.GetPlayerName(); } CS$<>8__locals0.message = null; if (args.Length > 1) { CS$<>8__locals0.message = string.Join(" ", args.Args, 1, args.Args.Length - 1); } if (string.IsNullOrEmpty(CS$<>8__locals0.message)) { CS$<>8__locals0.message = "\ud83d\udcf8 **" + CS$<>8__locals0.playerName + "** took a screenshot!"; } Terminal context = args.Context; if (context != null) { context.AddString("Capturing screenshot..."); } try { CS$<>8__locals0.webhook = new SimpleDiscordWebhook(BepinexConfiguration.WebhookURL.Value, "Valheim Screenshots"); CS$<>8__locals0.filename = $"{CS$<>8__locals0.playerName}_screenshot_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png"; Texture2D val = ScreenCapture.CaptureScreenshotAsTexture(); if ((Object)(object)val == (Object)null) { throw new Exception("Failed to capture screenshot"); } CS$<>8__locals0.pngData = ImageConversion.EncodeToPNG(val); Object.DestroyImmediate((Object)(object)val); if (CS$<>8__locals0.pngData == null || CS$<>8__locals0.pngData.Length == 0) { throw new Exception("Failed to encode screenshot to PNG"); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString("Screenshot captured, uploading to Discord..."); } Task.Run(async delegate { try { await CS$<>8__locals0.webhook.SendFileAsync(CS$<>8__locals0.pngData, CS$<>8__locals0.filename, CS$<>8__locals0.message); Debug.Log((object)("Screenshot uploaded to Discord for " + CS$<>8__locals0.playerName)); } catch (Exception ex2) { Debug.LogError((object)("Failed to upload screenshot: " + ex2.Message)); } }); } catch (Exception ex) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("Error: " + ex.Message); } Debug.LogError((object)("Failed to capture screenshot: " + ex.Message)); } } } [CompilerGenerated] private sealed class <>c__DisplayClass0_0 { public string playerName; public string message; internal async Task <Postfix>b__3() { try { string formattedMessage = "\ud83d\udcac **" + playerName + "**: " + message; await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.WebhookURL.Value, formattedMessage, "Valheim Console"); Debug.Log((object)("Discord message sent successfully: " + formattedMessage)); } catch (Exception ex) { Debug.LogError((object)("Failed to send Discord message: " + ex.Message)); } } } [CompilerGenerated] private sealed class <>c__DisplayClass0_1 { public string playerName; internal async Task <Postfix>b__4() { try { await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.WebhookURL.Value, "\ud83e\uddea Discord webhook test from **" + playerName + "** - Connection working!", "Valheim Test Bot"); Debug.Log((object)"Discord webhook test message sent successfully!"); } catch (Exception ex) { Debug.LogError((object)("Discord webhook test failed: " + ex.Message)); } } } [CompilerGenerated] private sealed class <>c__DisplayClass0_2 { public string message; public string playerName; public SimpleDiscordWebhook webhook; public byte[] pngData; public string filename; internal async Task <Postfix>b__5() { try { await webhook.SendFileAsync(pngData, filename, message); Debug.Log((object)("Screenshot uploaded to Discord for " + playerName)); } catch (Exception ex) { Debug.LogError((object)("Failed to upload screenshot: " + ex.Message)); } } } [HarmonyPostfix] private static void Postfix() { //IL_0032: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { if (args.Length < 2) { Terminal context5 = args.Context; if (context5 != null) { context5.AddString("Usage: discord <message>"); } Terminal context6 = args.Context; if (context6 != null) { context6.AddString("Example: discord Hello from Valheim!"); } } else { string message2 = string.Join(" ", args.Args, 1, args.Args.Length - 1); if (string.IsNullOrWhiteSpace(message2)) { Terminal context7 = args.Context; if (context7 != null) { context7.AddString("Message cannot be empty"); } } else { string playerName3 = "Server"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { playerName3 = Player.m_localPlayer.GetPlayerName(); } Terminal context8 = args.Context; if (context8 != null) { context8.AddString("Sending message to Discord..."); } Task.Run(async delegate { try { string formattedMessage = "\ud83d\udcac **" + playerName3 + "**: " + message2; await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.WebhookURL.Value, formattedMessage, "Valheim Console"); Debug.Log((object)("Discord message sent successfully: " + formattedMessage)); } catch (Exception ex4) { Debug.LogError((object)("Failed to send Discord message: " + ex4.Message)); } }); } } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("discord", "sends a message to Discord webhook. Usage: discord <message>", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__0_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { Terminal context4 = args.Context; if (context4 != null) { context4.AddString("Testing Discord webhook connection..."); } string playerName2 = "Server"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { playerName2 = Player.m_localPlayer.GetPlayerName(); } Task.Run(async delegate { try { await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.WebhookURL.Value, "\ud83e\uddea Discord webhook test from **" + playerName2 + "** - Connection working!", "Valheim Test Bot"); Debug.Log((object)"Discord webhook test message sent successfully!"); } catch (Exception ex3) { Debug.LogError((object)("Discord webhook test failed: " + ex3.Message)); } }); }; <>c.<>9__0_1 = val2; obj2 = (object)val2; } new ConsoleCommand("discordtest", "tests the Discord webhook connection", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>c.<>9__0_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs args) { string playerName = "Unknown Player"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { playerName = Player.m_localPlayer.GetPlayerName(); } string message = null; if (args.Length > 1) { message = string.Join(" ", args.Args, 1, args.Args.Length - 1); } if (string.IsNullOrEmpty(message)) { message = "\ud83d\udcf8 **" + playerName + "** took a screenshot!"; } Terminal context = args.Context; if (context != null) { context.AddString("Capturing screenshot..."); } try { SimpleDiscordWebhook webhook = new SimpleDiscordWebhook(BepinexConfiguration.WebhookURL.Value, "Valheim Screenshots"); string filename = $"{playerName}_screenshot_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png"; Texture2D val4 = ScreenCapture.CaptureScreenshotAsTexture(); if ((Object)(object)val4 == (Object)null) { throw new Exception("Failed to capture screenshot"); } byte[] pngData = ImageConversion.EncodeToPNG(val4); Object.DestroyImmediate((Object)(object)val4); if (pngData == null || pngData.Length == 0) { throw new Exception("Failed to encode screenshot to PNG"); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString("Screenshot captured, uploading to Discord..."); } Task.Run(async delegate { try { await webhook.SendFileAsync(pngData, filename, message); Debug.Log((object)("Screenshot uploaded to Discord for " + playerName)); } catch (Exception ex2) { Debug.LogError((object)("Failed to upload screenshot: " + ex2.Message)); } }); } catch (Exception ex) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("Error: " + ex.Message); } Debug.LogError((object)("Failed to capture screenshot: " + ex.Message)); } }; <>c.<>9__0_2 = val3; obj3 = (object)val3; } new ConsoleCommand("discordscreenshot", "captures and sends a screenshot to Discord", (ConsoleEvent)obj3, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } [HarmonyPatch(typeof(Player), "Update")] public static class HotkeyScreenshotPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && Input.GetKeyDown(BepinexConfiguration.ScreenshotHotkey.Value)) { string text = __instance.GetPlayerName(); if (string.IsNullOrEmpty(text)) { text = "Unknown Player"; } Debug.Log((object)("HotkeyScreenshotPatch: " + text + " pressed screenshot hotkey")); bool num = !string.IsNullOrEmpty(BepinexConfiguration.HotkeyScreenshotWebhookURL.Value); string webhookUrl = (num ? BepinexConfiguration.HotkeyScreenshotWebhookURL.Value : BepinexConfiguration.WebhookURL.Value); string username = (num ? BepinexConfiguration.HotkeyScreenshotWebhookUsername.Value : BepinexConfiguration.WebhookUsername.Value); string avatarUrl = (num ? BepinexConfiguration.HotkeyScreenshotWebhookAvatarURL.Value : BepinexConfiguration.WebhookAvatarURL.Value); string text2 = (num ? BepinexConfiguration.HotkeyScreenshotMessage.Value : "captured this screenshot!"); string message = "\ud83d\udcf8 **" + text + "** " + text2; string filename = $"{text}_screenshot_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png"; SimpleDiscordWebhook.SendQuickScreenshotAsync(webhookUrl, message, username, avatarUrl, filename); } } catch (Exception ex) { Debug.LogError((object)("HotkeyScreenshotPatch: Error: " + ex.Message)); } } } public static class PlayerDeathScreenshotPatch { internal static Texture2D storedDeathScreenshot; internal static string storedPlayerName; internal static DateTime storedDeathTime; internal static IEnumerator CaptureDeathScreenshotCoroutine(string playerName) { yield return (object)new WaitForSeconds(0.1f); storedDeathScreenshot = new SimpleDiscordWebhook(BepinexConfiguration.WebhookURL.Value).CaptureScreenshot(); storedPlayerName = playerName; storedDeathTime = DateTime.Now; Debug.Log((object)("Death screenshot captured and stored for " + playerName)); } } [HarmonyPatch(typeof(Humanoid), "OnRagdollCreated")] public static class PlayerRagdollScreenshotPatch { [HarmonyPostfix] private static void Postfix(Humanoid __instance) { try { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { string text = val.GetPlayerName(); if (string.IsNullOrEmpty(text)) { text = "Unknown Player"; } Debug.Log((object)("PlayerRagdollScreenshotPatch: " + text + " died - capturing screenshot")); ((MonoBehaviour)val).StartCoroutine(PlayerDeathScreenshotPatch.CaptureDeathScreenshotCoroutine(text)); } } catch (Exception ex) { Debug.LogError((object)("PlayerRagdollScreenshotPatch: Error: " + ex.Message)); } } } [HarmonyPatch(typeof(Player), "Awake")] public static class PlayerRespawnScreenshotPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { try { Debug.Log((object)"PlayerRespawnScreenshotPatch: Awake"); Debug.Log((object)"PlayerRespawnScreenshotPatch: Local player confirmed"); if ((Object)(object)PlayerDeathScreenshotPatch.storedDeathScreenshot != (Object)null) { Debug.Log((object)("PlayerRespawnScreenshotPatch: Processing stored death screenshot for " + PlayerDeathScreenshotPatch.storedPlayerName)); ProcessStoredDeathScreenshot(); } else { Debug.Log((object)"PlayerRespawnScreenshotPatch: No stored death screenshot found"); } } catch (Exception ex) { Debug.LogError((object)("PlayerRespawnScreenshotPatch: Error: " + ex.Message)); } } private static void ProcessStoredDeathScreenshot() { try { SimpleDiscordWebhook webhook = new SimpleDiscordWebhook(BepinexConfiguration.WebhookURL.Value, BepinexConfiguration.WebhookUsername.Value, BepinexConfiguration.WebhookAvatarURL.Value); byte[] pngData = webhook.ProcessScreenshot(PlayerDeathScreenshotPatch.storedDeathScreenshot); string deathMessage = "**" + PlayerDeathScreenshotPatch.storedPlayerName + "** " + BepinexConfiguration.GetRandomDeathMessage(); string filename = $"{PlayerDeathScreenshotPatch.storedPlayerName}_death_{PlayerDeathScreenshotPatch.storedDeathTime:yyyy-MM-dd_HH-mm-ss}.png"; Task.Run(async delegate { try { await webhook.SendFileAsync(pngData, filename, deathMessage); Debug.Log((object)("Death screenshot uploaded successfully for " + PlayerDeathScreenshotPatch.storedPlayerName)); } catch (Exception ex2) { Debug.LogError((object)("Error uploading death screenshot: " + ex2.Message)); } }); PlayerDeathScreenshotPatch.storedDeathScreenshot = null; PlayerDeathScreenshotPatch.storedPlayerName = null; } catch (Exception ex) { Debug.LogError((object)("Error processing stored death screenshot: " + ex.Message)); if ((Object)(object)PlayerDeathScreenshotPatch.storedDeathScreenshot != (Object)null) { Object.DestroyImmediate((Object)(object)PlayerDeathScreenshotPatch.storedDeathScreenshot); PlayerDeathScreenshotPatch.storedDeathScreenshot = null; PlayerDeathScreenshotPatch.storedPlayerName = null; } } } } }