using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; using Microsoft.Extensions.Logging; namespace CS2_SimpleAdmin.Managers; public class DiscordManager(string webhookUrl) { /// /// Sends a plain text message asynchronously to the configured Discord webhook URL. /// /// The text message to send to Discord. /// A task representing the asynchronous operation. [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public async Task SendMessageAsync(string message) { var client = CS2_SimpleAdmin.HttpClient; var payload = new { content = message }; var options = new JsonSerializerOptions { WriteIndented = false }; var json = JsonSerializer.Serialize(payload, options); var content = new StringContent(json, Encoding.UTF8, "application/json"); try { var response = await client.PostAsync(webhookUrl, content); if (!response.IsSuccessStatusCode) { CS2_SimpleAdmin._logger?.LogError( $"Failed to send discord message. Status Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"); } } catch (HttpRequestException e) { CS2_SimpleAdmin._logger?.LogError($"Error sending discord message: {e.Message}"); } } /// /// Sends an embed message asynchronously to the configured Discord webhook URL. /// /// The embed object containing rich content to send. /// A task representing the asynchronous operation. [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public async Task SendEmbedAsync(Embed embed) { var httpClient = CS2_SimpleAdmin.HttpClient; var payload = new { embeds = new[] { new { color = embed.Color, title = !string.IsNullOrEmpty(embed.Title) ? embed.Title : null, description = !string.IsNullOrEmpty(embed.Description) ? embed.Description : null, thumbnail = !string.IsNullOrEmpty(embed.ThumbnailUrl) ? new { url = embed.ThumbnailUrl } : null, image = !string.IsNullOrEmpty(embed.ImageUrl) ? new { url = embed.ImageUrl } : null, footer = !string.IsNullOrEmpty(embed.Footer?.Text) ? new { text = embed.Footer.Text, icon_url = embed.Footer.IconUrl } : null, timestamp = embed.Timestamp, fields = embed.Fields.Count > 0 ? embed.Fields.Select(field => new { name = field.Name, value = field.Value, inline = field.Inline }).ToArray() : null } } }; var options = new JsonSerializerOptions { WriteIndented = false }; var jsonPayload = JsonSerializer.Serialize(payload, options); var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); var response = await httpClient.PostAsync(webhookUrl, content); if (!response.IsSuccessStatusCode) { var errorMessage = await response.Content.ReadAsStringAsync(); CS2_SimpleAdmin._logger?.LogError($"Failed to send embed: {response.StatusCode} - {errorMessage}"); } } /// /// Converts a hexadecimal color string (e.g. "#FF0000") to its integer representation. /// /// The hexadecimal color string, optionally starting with '#'. /// An integer representing the color. public static int ColorFromHex(string hex) { if (hex.StartsWith($"#")) { hex = hex[1..]; } return int.Parse(hex, System.Globalization.NumberStyles.HexNumber); } } /// /// Represents a Discord embed message containing rich content such as title, description, fields, and images. /// public class Embed { public int Color { get; init; } public string? Title { get; init; } public string? Description { get; init; } public string? ImageUrl { get; init; } public string? ThumbnailUrl { get; init; } public Footer? Footer { get; init; } public string? Timestamp { get; init; } public List Fields { get; } = []; /// /// Adds a field to the embed message. /// /// The name of the field. /// The value or content of the field. /// Whether the field should be displayed inline with other fields. public void AddField(string name, string value, bool inline) { var field = new EmbedField { Name = name, Value = value, Inline = inline }; Fields.Add(field); } } /// /// Represents the footer section of a Discord embed message, including optional text and icon URL. /// public class Footer { public string? Text { get; init; } public string? IconUrl { get; set; } } /// /// Represents a field inside a Discord embed message. /// public class EmbedField { public string? Name { get; init; } public string? Value { get; init; } public bool Inline { get; init; } }