Editor/Discord/DiscordWebhook.cs

Helper that posts images or text to Discord webhooks from the Editor. It builds multipart form-data with image bytes and optional message payload, and posts JSON text payloads for simple messages.

NetworkingHttp Calls
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Sandbox;

namespace Editor.SuperShot;

public static class DiscordWebhook
{
	public static async Task<bool> PostImage( string webhookUrl, byte[] imageBytes, string filename, DiscordMessage message, string mimeType = "image/png" )
	{
		if ( string.IsNullOrWhiteSpace( webhookUrl ) )
		{
			Log.Warning( "[Supershot] No Discord webhook URL configured." );
			return false;
		}

		if ( imageBytes is null || imageBytes.Length == 0 )
		{
			Log.Warning( "[Supershot] Nothing to post to Discord." );
			return false;
		}

		try
		{
			using var form = new MultipartFormDataContent();

			var payload = message is null ? "{}" : BuildPayload( message, filename );
			form.Add( new StringContent( payload, Encoding.UTF8, "application/json" ), "payload_json" );

			var file = new ByteArrayContent( imageBytes );
			file.Headers.ContentType = new MediaTypeHeaderValue( mimeType );
			form.Add( file, "files[0]", filename );

			await Http.RequestStringAsync( webhookUrl, "POST", form );
			Log.Info( $"[Supershot] Posted {filename} to Discord." );
			return true;
		}
		catch ( Exception e )
		{
			Log.Error( $"[Supershot] Discord post failed: {e.Message}" );
			return false;
		}
	}

	public static async Task<int> PostImageToMany( IEnumerable<DiscordWebhookConfig> webhooks, byte[] imageBytes, string filename, DiscordMessage message, string mimeType = "image/png" )
	{
		int sent = 0;
		foreach ( var wh in webhooks )
		{
			if ( wh is null || !wh.IsConfigured )
				continue;

			if ( await PostImage( wh.Url, imageBytes, filename, message, mimeType ) )
				sent++;
		}
		return sent;
	}

	public static async Task<bool> PostText( string webhookUrl, string content )
	{
		if ( string.IsNullOrWhiteSpace( webhookUrl ) )
		{
			Log.Warning( "[Supershot] No Discord webhook URL configured." );
			return false;
		}

		if ( string.IsNullOrWhiteSpace( content ) )
			content = "Supershot Discord test message.";

		try
		{
			var payload = Json.Serialize( new Dictionary<string, object>
			{
				["content"] = content
			} );

			await Http.RequestStringAsync( webhookUrl, "POST", new StringContent( payload, Encoding.UTF8, "application/json" ) );
			return true;
		}
		catch ( Exception e )
		{
			Log.Error( $"[Supershot] Discord test failed: {e.Message}" );
			return false;
		}
	}

	static string BuildPayload( DiscordMessage message, string filename )
	{
		var payload = new Dictionary<string, object>();

		if ( !string.IsNullOrWhiteSpace( message.Content ) )
			payload["content"] = message.Content;

		if ( !string.IsNullOrWhiteSpace( message.Username ) )
			payload["username"] = message.Username;

		if ( !string.IsNullOrWhiteSpace( message.AvatarUrl ) )
			payload["avatar_url"] = message.AvatarUrl;

		return Json.Serialize( payload );
	}
}