Editor utility that posts images to Discord via webhook. Builds a multipart/form-data request with optional JSON payload (content, username, avatar, embed) and uploads an image attachment to one or multiple webhook URLs.
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();
// A null message means image-only: send an empty payload so Discord posts just the attachment.
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, wh.ResolveMessage( message ), mimeType ) )
sent++;
}
return sent;
}
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;
if ( message.UseEmbed )
{
var embed = new Dictionary<string, object>
{
["color"] = ColorToInt( message.EmbedColor ),
["image"] = new Dictionary<string, object> { ["url"] = $"attachment://{filename}" }
};
if ( !string.IsNullOrWhiteSpace( message.EmbedTitle ) )
embed["title"] = message.EmbedTitle;
if ( !string.IsNullOrWhiteSpace( message.EmbedDescription ) )
embed["description"] = message.EmbedDescription;
if ( !string.IsNullOrWhiteSpace( message.EmbedFooter ) )
embed["footer"] = new Dictionary<string, object> { ["text"] = message.EmbedFooter };
if ( message.EmbedTimestamp )
embed["timestamp"] = DateTime.UtcNow.ToString( "o" );
payload["embeds"] = new[] { embed };
}
return Json.Serialize( payload );
}
static int ColorToInt( Color c )
{
int r = (int)(MathX.Clamp( c.r, 0f, 1f ) * 255f);
int g = (int)(MathX.Clamp( c.g, 0f, 1f ) * 255f);
int b = (int)(MathX.Clamp( c.b, 0f, 1f ) * 255f);
return (r << 16) | (g << 8) | b;
}
}