C#:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using UnityEngine;
#if RUST
using UnityEngine.Networking;
using System.Collections;
#endif
namespace Oxide.Plugins
{
[Info("Discord Wipe", "MJSU", "2.2.1")]
[Description("Sends a notification to a discord channel when the server wipes or protocol changes")]
internal class DiscordWipe : CovalencePlugin
{
#region Class Fields
[PluginReference] private Plugin RustMapApi, PlaceholderAPI;
private PluginConfig _pluginConfig; //Plugin Config
private StoredData _storedData; //Plugin Data
private const string DefaultUrl = "https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
private const string AdminPermission = "discordwipe.admin";
private const string AttachmentBase = "attachment://";
private const string MapAttachment = AttachmentBase + MapFilename;
private const string MapFilename = "map.jpg";
private const int MaxImageSize = 8 * 1024 * 1024;
private string _protocol;
private string _previousProtocol;
private Action<IPlayer, StringBuilder, bool> _replacer;
private enum DebugEnum {Message, None, Error, Warning, Info}
public enum SendMode {Always, Random}
private enum EncodingMode {Jpg = 1, Png = 2}
#endregion
#region Setup & Loading
private void Init()
{
AddCovalenceCommand(_pluginConfig.Command, nameof(SendWipeCommand));
_storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
_previousProtocol = _storedData.Protocol;
permission.RegisterPermission(AdminPermission, this);
}
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
[LangKeys.NoPermission] = "You do not have permission to use this command",
[LangKeys.SentWipe] = "You have sent a test wipe message",
[LangKeys.SentProtocol] = "You have sent a test protocol message",
[LangKeys.Help] = "Sends test message for plugin\n" +
"{0}{1} wipe - sends a wipe test message\n" +
"{0}{1} protocol - sends a protocol test message\n" +
"{0}{1} - displays this help text again" ,
}, this);
}
protected override void LoadDefaultConfig()
{
PrintWarning("Loading Default Config");
}
protected override void LoadConfig()
{
base.LoadConfig();
Config.Settings.DefaultValueHandling = DefaultValueHandling.Populate;
_pluginConfig = AdditionalConfig(Config.ReadObject<PluginConfig>());
Config.WriteObject(_pluginConfig);
}
private PluginConfig AdditionalConfig(PluginConfig config)
{
config.WipeEmbeds = config.WipeEmbeds ?? new List<DiscordMessageConfig> {
new DiscordMessageConfig
{
Content = "@everyone",
SendMode = SendMode.Always,
Embed = new EmbedConfig
{
Title = "{server.name}",
Description = "The server has wiped!",
Url = string.Empty,
Color = "#de8732",
Image = MapAttachment,
Thumbnail = string.Empty,
Fields = new List<FieldConfig>
{
#if RUST
new FieldConfig
{
Title = "Seed",
Value = "[{world.seed}](https://rustmaps.com/map/{world.size}_{world.seed})",
Inline = true,
Enabled = true
},
new FieldConfig
{
Title = "Size",
Value = "{world.size}M ({world.size!km^2}km^2)",
Inline = true,
Enabled = true
},
new FieldConfig
{
Title = "Protocol",
Value = "{server.protocol.network}",
Inline = true,
Enabled = true
},
#endif
new FieldConfig
{
Title = "Click & Connect",
Value = "steam://connect/{server.address}:{server.port}",
Inline = false,
Enabled = true
}
},
Footer = new FooterConfig
{
IconUrl = string.Empty,
Text = string.Empty,
Enabled = true
},
Enabled = true
}
}};
config.ProtocolEmbeds = config.ProtocolEmbeds ?? new List<DiscordMessageConfig> {
new DiscordMessageConfig
{
Content = "@everyone",
SendMode = SendMode.Always,
Embed = new EmbedConfig
{
Title = "{server.name}",
Description = "The server protocol has changed!",
Url = string.Empty,
Color = "#de8732",
Image = string.Empty,
Thumbnail = string.Empty,
Fields = new List<FieldConfig>
{
new FieldConfig
{
Title = "Protocol",
Value = "{server.protocol.network}",
Inline = true,
Enabled = true
},
new FieldConfig
{
Title = "Previous Protocol",
Value = "{server.protocol.previous}",
Inline = true,
Enabled = true
},
new FieldConfig
{
Title = "Mandatory Client Update",
Value = "This update requires a mandatory client update in order to be able to play on the server",
Inline = false,
Enabled = true
},
new FieldConfig
{
Title = "Click & Connect",
Value = "steam://connect/{server.address}:{server.port}",
Inline = false,
Enabled = true
}
},
Footer = new FooterConfig
{
IconUrl = string.Empty,
Text = string.Empty,
Enabled = true
},
Enabled = true
}
}};
#if RUST
config.ImageSettings = new RustMapImageSettings
{
Name = config.ImageSettings?.Name ?? "Icons",
Scale = config.ImageSettings?.Scale ?? 0.5f,
FileType = config.ImageSettings?.FileType ?? EncodingMode.Jpg
};
#endif
return config;
}
private void OnServerInitialized()
{
bool changed = false;
if (_pluginConfig.WipeEmbed != null)
{
_pluginConfig.WipeEmbeds.Clear();
_pluginConfig.WipeEmbed.SendMode = SendMode.Always;
_pluginConfig.WipeEmbeds.Add(_pluginConfig.WipeEmbed);
_pluginConfig.WipeEmbed = null;
changed = true;
}
if (_pluginConfig.ProtocolEmbed != null)
{
_pluginConfig.ProtocolEmbeds.Clear();
_pluginConfig.ProtocolEmbed.SendMode = SendMode.Always;
_pluginConfig.ProtocolEmbeds.Add(_pluginConfig.ProtocolEmbed);
_pluginConfig.ProtocolEmbed = null;
changed = true;
}
if (changed)
{
Config.WriteObject(_pluginConfig);
}
_protocol = GetProtocol();
if (PlaceholderAPI == null || !PlaceholderAPI.IsLoaded)
{
PrintError("Missing plugin dependency PlaceholderAPI: https://umod.org/plugins/placeholder-api");
return;
}
if(PlaceholderAPI.Version < new VersionNumber(2, 2, 0))
{
PrintError("Placeholder API plugin must be version 2.2.0 or higher");
return;
}
if (IsRustMapApiLoaded())
{
if(RustMapApi.Version < new VersionNumber(1,3,2))
{
PrintError("RustMapApi plugin must be version 1.3.2 or higher");
return;
}
if (!IsRustMapApiReady())
{
return;
}
}
//Delayed so PlaceholderAPI can be ready before we call
timer.In(1f, HandleStartup);
}
private void OnRustMapApiReady()
{
HandleStartup();
}
private void HandleStartup()
{
if (string.IsNullOrEmpty(_storedData.Protocol))
{
Debug(DebugEnum.Info, $"HandleStartup - Protocol is not set setting protocol to: {_protocol}");
_storedData.Protocol = _protocol;
SaveData();
}
else if (_storedData.Protocol != _protocol)
{
Debug(DebugEnum.Info, $"HandleStartup - Protocol has changed {_storedData.Protocol} -> {_protocol}");
if (_pluginConfig.SendProtocolAutomatically)
{
SendProtocol();
}
_storedData.Protocol = _protocol;
SaveData();
Puts("Protocol notification sent");
}
if (_storedData.IsWipe)
{
if (_pluginConfig.SendWipeAutomatically)
{
Debug(DebugEnum.Info, "HandleStartup - IsWipe is set. Sending wipe message.");
SendWipe();
Puts("Wipe notification sent");
}
_storedData.IsWipe = false;
SaveData();
}
}
private void OnNewSave(string filename)
{
_storedData.IsWipe = true;
Debug(DebugEnum.Info, "OnNewSave - Wipe Detected");
SaveData();
}
private void Unload()
{
SaveData();
}
private string GetProtocol()
{
#if RUST
return Rust.Protocol.network.ToString();
#else
return covalence.Server.Protocol;
#endif
}
#endregion
#region Command
private bool SendWipeCommand(IPlayer player, string cmd, string[] args)
{
if (!HasPermission(player, AdminPermission))
{
player.Message(Lang(LangKeys.NoPermission, player));
return true;
}
string commandPrefix = player.IsServer ? "" : "/";
if (args.Length == 0)
{
player.Message(Lang(LangKeys.Help, player, commandPrefix, _pluginConfig.Command));
return true;
}
switch (args[0].ToLower())
{
case "wipe":
SendWipe();
player.Message(Lang(LangKeys.SentWipe, player));
break;
case "protocol":
SendProtocol();
player.Message(Lang(LangKeys.SentProtocol, player));
break;
default:
player.Message(Lang(LangKeys.Help, player,commandPrefix, _pluginConfig.Command));
break;
}
return true;
}
#endregion
#region Message Handling
private void SendWipe()
{
Debug(DebugEnum.Info, "SendWipe - Sending wipe message");
if (string.IsNullOrEmpty(_pluginConfig.WipeWebhook) || _pluginConfig.WipeWebhook == DefaultUrl)
{
Debug(DebugEnum.Info, "SendWipe - Wipe message not sent due to Wipe Webhook being blank or matching the default url");
return;
}
List<DiscordMessageConfig> messages = _pluginConfig.WipeEmbeds
.Where(w => w.SendMode == SendMode.Always)
.ToList();
DiscordMessageConfig random = _pluginConfig.WipeEmbeds
.Where(w => w.SendMode == SendMode.Random)
.OrderBy(w => Guid.NewGuid())
.FirstOrDefault();
if (random != null)
{
messages.Add(random);
}
SendMessage(_pluginConfig.WipeWebhook, messages);
Debug(DebugEnum.Message, "");
}
private void SendProtocol()
{
Debug(DebugEnum.Info, "SendProtocol - Sending protocol message");
if (string.IsNullOrEmpty(_pluginConfig.ProtocolWebhook) || _pluginConfig.ProtocolWebhook == DefaultUrl)
{
Debug(DebugEnum.Info, "SendProtocol - Protocol message not sent due to Wipe Webhook being blank or matching the default url");
return;
}
List<DiscordMessageConfig> messages = _pluginConfig.ProtocolEmbeds
.Where(w => w.SendMode == SendMode.Always)
.ToList();
DiscordMessageConfig random = _pluginConfig.ProtocolEmbeds
.Where(w => w.SendMode == SendMode.Random)
.OrderBy(w => Guid.NewGuid())
.FirstOrDefault();
if (random != null)
{
messages.Add(random);
}
SendMessage(_pluginConfig.ProtocolWebhook, messages);
}
private void SendMessage(string url, List<DiscordMessageConfig> messageConfigs)
{
for (int index = 0; index < messageConfigs.Count; index++)
{
DiscordMessageConfig messageConfig = messageConfigs[index];
DiscordMessage message = ParseMessage(messageConfig);
timer.In(index + 1, () =>
{
#if RUST
List<Attachment> attachments = new List<Attachment>();
AttachMap(attachments, messageConfig);
SendDiscordAttachmentMessage(url, message, attachments);
#else
SendDiscordMessage(url, message);
#endif
});
}
}
#endregion
#if RUST
private void AttachMap(List<Attachment> attachments, DiscordMessageConfig messageConfig)
{
if (IsRustMapApiLoaded() && IsRustMapApiReady() && messageConfig.Embed.Image.StartsWith(AttachmentBase))
{
Debug(DebugEnum.Info, "AttachMap - RustMapApi is ready, attaching map");
List<string> maps = RustMapApi.Call<List<string>>("GetSavedMaps");
string mapName = _pluginConfig.ImageSettings.Name;
if (maps != null)
{
mapName = maps.FirstOrDefault(m => m.Equals(mapName, StringComparison.InvariantCultureIgnoreCase));
if (string.IsNullOrEmpty(mapName))
{
PrintWarning($"Map name not found {_pluginConfig.ImageSettings.Name}. Valid names are {string.Join(", ", maps.ToArray())}");
mapName = "Icons";
}
}
Debug(DebugEnum.Info, $"AttachMap - RustMapApi map name set to: {mapName}");
int resolution = (int) (World.Size * _pluginConfig.ImageSettings.Scale);
AttachmentContentType contentType = _pluginConfig.ImageSettings.FileType == EncodingMode.Jpg ? AttachmentContentType.Jpg : AttachmentContentType.Png;
object response = RustMapApi.Call("CreatePluginImage", this, mapName, resolution, (int)_pluginConfig.ImageSettings.FileType);
if (response is string)
{
PrintError($"An error occurred creating the plugin image: {response}");
return;
}
Hash<string, object> map = response as Hash<string, object>;
byte[] mapData = map?["image"] as byte[];
if (mapData != null)
{
if (mapData.Length >= 8 * 1024 * 1024)
{
PrintError( "Map Image too large. " +
$"Image size is {mapData.Length / 1024.0 / 1024.0:0.00}MB. " +
$"Max size is {MaxImageSize / 1024 / 1024}MB. " +
"Please reduce the \"Image Resolution Scale\"");
}
else
{
attachments.Add(new Attachment(mapData, MapFilename, contentType));
Debug(DebugEnum.Info, "AttachMap - Successfully attached map");
}
}
else
{
Debug(DebugEnum.Warning, "AttachMap - MapData was null!!!");
}
}
}
#endif
#region PlaceholderAPI
private string ParseFields(string json)
{
StringBuilder sb = new StringBuilder(json);
GetReplacer()?.Invoke(null, sb, false);
return sb.ToString();
}
private void OnPluginUnloaded(Plugin plugin)
{
if (plugin?.Name == "PlaceholderAPI")
{
_replacer = null;
}
}
private void OnPlaceholderAPIReady()
{
RegisterPlaceholder("server.protocol.previous", (player, s) => _previousProtocol, "Displays the previous protocol version if it changed during the last restart", double.MaxValue);
}
private void RegisterPlaceholder(string key, Func<IPlayer, string, object> action, string description = null, double ttl = double.NaN)
{
if (IsPlaceholderApiLoaded())
{
PlaceholderAPI.Call("AddPlaceholder", this, key, action, description, ttl);
}
}
private Action<IPlayer, StringBuilder, bool> GetReplacer()
{
if (!IsPlaceholderApiLoaded())
{
return _replacer;
}
return _replacer ?? (_replacer = PlaceholderAPI.Call<Action<IPlayer, StringBuilder, bool>>("GetProcessPlaceholders", 1));
}
private bool IsPlaceholderApiLoaded() => PlaceholderAPI != null && PlaceholderAPI.IsLoaded;
#endregion
#region Helpers
private void Debug(DebugEnum level, string message)
{
if (level > _pluginConfig.DebugLevel)
{
return;
}
switch (level)
{
case DebugEnum.Error:
PrintError(message);
break;
case DebugEnum.Warning:
PrintWarning(message);
break;
default:
Puts($"{level}: {message}");
break;
}
}
private string Lang(string key, IPlayer player = null, params object[] args)
{
try
{
return string.Format(lang.GetMessage(key, this, player?.Id), args);
}
catch(Exception ex)
{
PrintError($"Lang Key '{key}' threw exception\n:{ex.Message}");
throw;
}
}
private bool IsRustMapApiLoaded() => RustMapApi != null && RustMapApi.IsLoaded;
private bool IsRustMapApiReady() => RustMapApi.Call<bool>("IsReady");
private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, _storedData);
private bool HasPermission(IPlayer player, string perm) => permission.UserHasPermission(player.Id, perm);
#endregion
#region Classes
private class PluginConfig
{
[JsonConverter(typeof(StringEnumConverter))]
[DefaultValue(DebugEnum.Warning)]
[JsonProperty(PropertyName = "Debug Level (None, Error, Warning, Info)")]
public DebugEnum DebugLevel { get; set; }
[DefaultValue("dw")]
[JsonProperty(PropertyName = "Command")]
public string Command { get; set; }
#if RUST
[JsonProperty(PropertyName = "Rust Map Image Settings")]
public RustMapImageSettings ImageSettings { get; set; }
#endif
[DefaultValue(true)]
[JsonProperty(PropertyName = "Send wipe message when server wipes")]
public bool SendWipeAutomatically { get; set; }
[DefaultValue(DefaultUrl)]
[JsonProperty(PropertyName = "Wipe Webhook url")]
public string WipeWebhook { get; set; }
[DefaultValue(true)]
[JsonProperty(PropertyName = "Send protocol message when server protocol changes")]
public bool SendProtocolAutomatically { get; set; }
[DefaultValue(DefaultUrl)]
[JsonProperty(PropertyName = "Protocol Webhook url")]
public string ProtocolWebhook { get; set; }
[JsonProperty(PropertyName = "Wipe messages")]
public List<DiscordMessageConfig> WipeEmbeds { get; set; }
[JsonProperty(PropertyName = "Protocol messages")]
public List<DiscordMessageConfig> ProtocolEmbeds { get; set; }
[JsonProperty(PropertyName = "Wipe message")]
public DiscordMessageConfig WipeEmbed { get; set; }
[JsonProperty(PropertyName = "Protocol message")]
public DiscordMessageConfig ProtocolEmbed { get; set; }
public bool ShouldSerializeWipeEmbed()
{
return WipeEmbed != null;
}
public bool ShouldSerializeProtocolEmbed()
{
return ProtocolEmbed != null;
}
}
#if RUST
private class RustMapImageSettings
{
[DefaultValue("Icons")]
[JsonProperty(PropertyName = "Render Name")]
public string Name { get; set; }
[DefaultValue(0.5f)]
[JsonProperty(PropertyName = "Image Resolution Scale")]
public float Scale { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
[DefaultValue(EncodingMode.Jpg)]
[JsonProperty(PropertyName = "File Type (Jpg, Png")]
public EncodingMode FileType { get; set; }
}
#endif
private class StoredData
{
public bool IsWipe { get; set; }
public string Protocol { get; set; }
}
private class LangKeys
{
public const string NoPermission = "NoPermission";
public const string SentWipe = "SentWipe";
public const string SentProtocol = "SentProtocol";
public const string Help = "Help";
}
#endregion
#region Discord Embed
#region Send Embed Methods
/// <summary>
/// Headers when sending an embeded message
/// </summary>
private readonly Dictionary<string, string> _headers = new Dictionary<string, string>()
{
{"Content-Type", "application/json"}
};
/// <summary>
/// Sends the DiscordMessage to the specified webhook url
/// </summary>
/// <param name="url">Webhook url</param>
/// <param name="message">Message being sent</param>
private void SendDiscordMessage(string url, DiscordMessage message)
{
string messageJson = message.ToJson();
Debug(DebugEnum.Info, $"SendDiscordMessage - ToJson \n{messageJson}");
string json = ParseFields( messageJson);
Debug(DebugEnum.Info, $"SendDiscordMessage - ParseFields \n{json}");
webrequest.Enqueue(url, json, SendDiscordMessageCallback, this, RequestMethod.POST, _headers);
}
/// <summary>
/// Callback when sending the embed if any errors occured
/// </summary>
/// <param name="code">HTTP response code</param>
/// <param name="message">Response message</param>
private void SendDiscordMessageCallback(int code, string message)
{
if (code != 204)
{
PrintError(message);
}
}
#if RUST
/// <summary>
/// Sends the DiscordMessage to the specified webhook url with attachments
/// </summary>
/// <param name="url">Webhook url</param>
/// <param name="message">Message being sent</param>
/// <param name="files">Attachments to be added to the DiscordMessage</param>
private void SendDiscordAttachmentMessage(string url, DiscordMessage message, List<Attachment> files)
{
string messageJson = message.ToJson();
Debug(DebugEnum.Info, $"SendDiscordMessage - ToJson \n{messageJson}");
string json = ParseFields( messageJson);
Debug(DebugEnum.Info, $"SendDiscordMessage - ParseFields \n{json}");
List<IMultipartFormSection> formData = new List<IMultipartFormSection>
{
new MultipartFormDataSection("payload_json", json)
};
for (int i = 0; i < files.Count; i++)
{
Attachment attachment = files[i];
formData.Add(new MultipartFormFileSection($"file{i + 1}", attachment.Data, attachment.Filename, attachment.ContentType));
}
InvokeHandler.Instance.StartCoroutine(SendDiscordAttachmentMessageHandler(url, formData));
}
private IEnumerator SendDiscordAttachmentMessageHandler(string url, List<IMultipartFormSection> data)
{
UnityWebRequest www = UnityWebRequest.Post(url, data);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
PrintError($"{www.error} {www.downloadHandler.text}");
}
}
#endif
#endregion
#region Helper Methods
private const string OwnerIcon = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/47/47db946f27bc76d930ac82f1656f7a10707bb67d_full.jpg";
private void AddPluginInfoFooter(Embed embed)
{
embed.AddFooter($"{Title} V{Version} by {Author}", OwnerIcon);
}
private string GetPositionField(Vector3 pos)
{
return $"{pos.x:0.00} {pos.y:0.00} {pos.z:0.00}";
}
#endregion
#region Embed Classes
private class DiscordMessage
{
/// <summary>
/// The name of the user sending the message changing this will change the webhook bots name
/// </summary>
[JsonProperty("username")]
private string Username { get; set; }
/// <summary>
/// The avatar url of the user sending the message changing this will change the webhook bots avatar
/// </summary>
[JsonProperty("avatar_url")]
private string AvatarUrl { get; set; }
/// <summary>
/// String only content to be sent
/// </summary>
[JsonProperty("content")]
private string Content { get; set; }
/// <summary>
/// Embeds to be sent
/// </summary>
[JsonProperty("embeds")]
private List<Embed> Embeds { get; }
public DiscordMessage(string username = null, string avatarUrl = null)
{
Username = username;
AvatarUrl = avatarUrl;
Embeds = new List<Embed>();
}
public DiscordMessage(string content, string username = null, string avatarUrl = null)
{
Content = content;
Username = username;
AvatarUrl = avatarUrl;
Embeds = new List<Embed>();
}
public DiscordMessage(Embed embed, string username = null, string avatarUrl = null)
{
Embeds = new List<Embed> {embed};
Username = username;
AvatarUrl = avatarUrl;
}
/// <summary>
/// Adds a new embed to the list of embed to send
/// </summary>
/// <param name="embed">Embed to add</param>
/// <returns>This</returns>
/// <exception cref="IndexOutOfRangeException">Thrown if more than 10 embeds are added in a send as that is the discord limit</exception>
public DiscordMessage AddEmbed(Embed embed)
{
if (Embeds.Count >= 10)
{
throw new IndexOutOfRangeException("Only 10 embed are allowed per message");
}
Embeds.Add(embed);
return this;
}
/// <summary>
/// Adds string content to the message
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
public DiscordMessage AddContent(string content)
{
Content = content;
return this;
}
/// <summary>
/// Changes the username and avatar image for the bot sending the message
/// </summary>
/// <param name="username">username to change</param>
/// <param name="avatarUrl">avatar img url to change</param>
/// <returns>This</returns>
public DiscordMessage AddSender(string username, string avatarUrl)
{
Username = username;
AvatarUrl = avatarUrl;
return this;
}
/// <summary>
/// Returns message as JSON to be sent in the web request
/// </summary>
/// <returns></returns>
public string ToJson() => JsonConvert.SerializeObject(this, Formatting.None,
new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore});
}
private class Embed
{
/// <summary>
/// Color of the left side bar of the embed message
/// </summary>
[JsonProperty("color")]
private int Color { get; set; }
/// <summary>
/// Fields to be added to the embed message
/// </summary>
[JsonProperty("fields")]
private List<Field> Fields { get; } = new List<Field>();
/// <summary>
/// Title of the embed message
/// </summary>
[JsonProperty("title")]
private string Title { get; set; }
/// <summary>
/// Description of the embed message
/// </summary>
[JsonProperty("description")]
private string Description { get; set; }
/// <summary>
/// Description of the embed message
/// </summary>
[JsonProperty("url")]
private string Url { get; set; }
/// <summary>
/// Image to added to the embed message. Appears at the bottom of the message above the footer
/// </summary>
[JsonProperty("image")]
private Image Image { get; set; }
/// <summary>
/// Thumbnail image added to the embed message. Appears in the top right corner
/// </summary>
[JsonProperty("thumbnail")]
private Image Thumbnail { get; set; }
/// <summary>
/// Video to add to the embed message
/// </summary>
[JsonProperty("video")]
private Video Video { get; set; }
/// <summary>
/// Author to add to the embed message. Appears above the title.
/// </summary>
[JsonProperty("author")]
private AuthorInfo Author { get; set; }
/// <summary>
/// Footer to add to the embed message. Appears below all content.
/// </summary>
[JsonProperty("footer")]
private Footer Footer { get; set; }
/// <summary>
/// Adds a title to the embed message
/// </summary>
/// <param name="title">Title to add</param>
/// <returns>This</returns>
public Embed AddTitle(string title)
{
Title = title;
return this;
}
/// <summary>
/// Adds a description to the embed message
/// </summary>
/// <param name="description">description to add</param>
/// <returns>This</returns>
public Embed AddDescription(string description)
{
Description = description;
return this;
}
/// <summary>
/// Adds a url to the embed message
/// </summary>
/// <param name="url"></param>
/// <returns>This</returns>
public Embed AddUrl(string url)
{
Url = url;
return this;
}
/// <summary>
/// Adds an author to the embed message. The author will appear above the title
/// </summary>
/// <param name="name">Name of the author</param>
/// <param name="iconUrl">Icon Url to use for the author</param>
/// <param name="url">Url to go to when the authors name is clicked on</param>
/// <param name="proxyIconUrl">Backup icon url. Can be left null if you only have one icon url</param>
/// <returns>This</returns>
public Embed AddAuthor(string name, string iconUrl = null, string url = null, string proxyIconUrl = null)
{
Author = new AuthorInfo(name, iconUrl, url, proxyIconUrl);
return this;
}
/// <summary>
/// Adds a footer to the embed message
/// </summary>
/// <param name="text">Text to be added to the footer</param>
/// <param name="iconUrl">Icon url to add in the footer. Appears to the left of the text</param>
/// <param name="proxyIconUrl">Backup icon url. Can be left null if you only have one icon url</param>
/// <returns>This</returns>
public Embed AddFooter(string text, string iconUrl = null, string proxyIconUrl = null)
{
Footer = new Footer(text, iconUrl, proxyIconUrl);
return this;
}
/// <summary>
/// Adds an int based color to the embed. Color appears as a bar on the left side of the message
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public Embed AddColor(int color)
{
if (color < 0x0 || color > 0xFFFFFF)
{
throw new Exception($"Color '{color}' is outside the valid color range");
}
Color = color;
return this;
}
/// <summary>
/// Adds a hex based color. Color appears as a bar on the left side of the message
/// </summary>
/// <param name="color">Color in string hex format</param>
/// <returns>This</returns>
/// <exception cref="Exception">Exception thrown if color is outside of range</exception>
public Embed AddColor(string color)
{
int parsedColor = int.Parse(color.TrimStart('#'), NumberStyles.AllowHexSpecifier);
if (parsedColor < 0x0 || parsedColor > 0xFFFFFF)
{
throw new Exception($"Color '{color}' is outside the valid color range");
}
Color = parsedColor;
return this;
}
/// <summary>
/// Adds a RGB based color. Color appears as a bar on the left side of the message
/// </summary>
/// <param name="red">Red value between 0 - 255</param>
/// <param name="green">Green value between 0 - 255</param>
/// <param name="blue">Blue value between 0 - 255</param>
/// <returns>This</returns>
/// <exception cref="Exception">Thrown if red, green, or blue is outside of range</exception>
public Embed AddColor(int red, int green, int blue)
{
if (red < 0 || red > 255 || green < 0 || green > 255 || green < 0 || green > 255)
{
throw new Exception($"Color Red:{red} Green:{green} Blue:{blue} is outside the valid color range. Must be between 0 - 255");
}
Color = red * 65536 + green * 256 + blue;;
return this;
}
/// <summary>
/// Adds a blank field.
/// If inline it will add a blank column.
/// If not inline will add a blank row
/// </summary>
/// <param name="inline">If the field is inline</param>
/// <returns>This</returns>
public Embed AddBlankField(bool inline)
{
Fields.Add(new Field("", "", inline));
return this;
}
/// <summary>
/// Adds a new field with the name as the title and value as the value.
/// If inline will add a new column. If row will add in a new row.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <param name="inline"></param>
/// <returns></returns>
public Embed AddField(string name, string value, bool inline)
{
Fields.Add(new Field(name, value, inline));
return this;
}
/// <summary>
/// Adds an image to the embed. The url should point to the url of the image.
/// If using attachment image you can make the url: "attachment://{image name}.{image extension}
/// </summary>
/// <param name="url">Url for the image</param>
/// <param name="width">width of the image</param>
/// <param name="height">height of the image</param>
/// <param name="proxyUrl">Backup url for the image</param>
/// <returns></returns>
public Embed AddImage(string url, int? width = null, int? height = null, string proxyUrl = null)
{
Image = new Image(url, width, height, proxyUrl);
return this;
}
/// <summary>
/// Adds a thumbnail in the top right corner of the embed
/// If using attachment image you can make the url: "attachment://{image name}.{image extension}
/// </summary>
/// <param name="url">Url for the image</param>
/// <param name="width">width of the image</param>
/// <param name="height">height of the image</param>
/// <param name="proxyUrl">Backup url for the image</param>
/// <returns></returns>
public Embed AddThumbnail(string url, int? width = null, int? height = null, string proxyUrl = null)
{
Thumbnail = new Image(url, width, height, proxyUrl);
return this;
}
/// <summary>
/// Adds a video to the embed
/// </summary>
/// <param name="url">Url for the video</param>
/// <param name="width">Width of the video</param>
/// <param name="height">Height of the video</param>
/// <returns></returns>
public Embed AddVideo(string url, int? width = null, int? height = null)
{
Video = new Video(url, width, height);
return this;
}
}
/// <summary>
/// Field for and embed message
/// </summary>
private class Field
{
/// <summary>
/// Name of the field
/// </summary>
[JsonProperty("name")]
private string Name { get; }
/// <summary>
/// Value for the field
/// </summary>
[JsonProperty("value")]
private string Value { get; }
/// <summary>
/// If the field should be in the same row or a new row
/// </summary>
[JsonProperty("inline")]
private bool Inline { get; }
public Field(string name, string value, bool inline)
{
Name = name;
Value = value;
Inline = inline;
}
}
/// <summary>
/// Image for an embed message
/// </summary>
private class Image
{
/// <summary>
/// Url for the image
/// </summary>
[JsonProperty("url")]
private string Url { get; }
/// <summary>
/// Width for the image
/// </summary>
[JsonProperty("width")]
private int? Width { get; }
/// <summary>
/// Height for the image
/// </summary>
[JsonProperty("height")]
private int? Height { get; }
/// <summary>
/// Proxy url for the image
/// </summary>
[JsonProperty("proxyURL")]
private string ProxyUrl { get; }
public Image(string url, int? width, int? height, string proxyUrl)
{
Url = url;
Width = width;
Height = height;
ProxyUrl = proxyUrl;
}
}
/// <summary>
/// Video for an embed message
/// </summary>
private class Video
{
/// <summary>
/// Url to the video
/// </summary>
[JsonProperty("url")]
private string Url { get; }
/// <summary>
/// Width of the video
/// </summary>
[JsonProperty("width")]
private int? Width { get; }
/// <summary>
/// Height of the video
/// </summary>
[JsonProperty("height")]
private int? Height { get; }
public Video(string url, int? width, int? height)
{
Url = url;
Width = width;
Height = height;
}
}
/// <summary>
/// Author of an embed message
/// </summary>
private class AuthorInfo
{
/// <summary>
/// Name of the author
/// </summary>
[JsonProperty("name")]
private string Name { get; }
/// <summary>
/// Url to go to when clicking on the authors name
/// </summary>
[JsonProperty("url")]
private string Url { get; }
/// <summary>
/// Icon url for the author
/// </summary>
[JsonProperty("icon_url")]
private string IconUrl { get; }
/// <summary>
/// Proxy icon url for the author
/// </summary>
[JsonProperty("proxy_icon_url")]
private string ProxyIconUrl { get; }
public AuthorInfo(string name, string iconUrl, string url, string proxyIconUrl)
{
Name = name;
Url = url;
IconUrl = iconUrl;
ProxyIconUrl = proxyIconUrl;
}
}
/// <summary>
/// Footer for an embed message
/// </summary>
private class Footer
{
/// <summary>
/// Text for the footer
/// </summary>
[JsonProperty("text")]
private string Text { get; }
// ... [Code preview truncated due to length. Full source in attached file] ...