Author | SHA1 | Message | Date |
---|---|---|---|
|
c297cd9d4a
|
Introduce message text formatting (#2324) | 3 years ago |
|
5e45b0a317 | initial implementation | 3 years ago |
@@ -133,6 +133,21 @@ namespace Discord | |||||
public const int MaxAuditLogEntriesPerBatch = 100; | public const int MaxAuditLogEntriesPerBatch = 100; | ||||
/// <summary> | /// <summary> | ||||
/// Returns the max number of stickers that can be sent on a message. | |||||
/// </summary> | |||||
public const int MaxStickersPerMessage = 3; | |||||
/// <summary> | |||||
/// Returns the max numbe of files that can be sent on a message. | |||||
/// </summary> | |||||
public const int MaxFilesPerMessage = 10; | |||||
/// <summary> | |||||
/// Returns the max number of embeds that can be sent on a message. | |||||
/// </summary> | |||||
public const int MaxEmbedsPerMessage = 10; | |||||
/// <summary> | |||||
/// Gets or sets how a request should act in the case of an error, by default. | /// Gets or sets how a request should act in the case of an error, by default. | ||||
/// </summary> | /// </summary> | ||||
/// <returns> | /// <returns> | ||||
@@ -165,23 +180,23 @@ namespace Discord | |||||
/// </remarks> | /// </remarks> | ||||
internal bool DisplayInitialLog { get; set; } = true; | internal bool DisplayInitialLog { get; set; } = true; | ||||
/// <summary> | |||||
/// Gets or sets whether or not rate-limits should use the system clock. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// If set to <c>false</c>, we will use the X-RateLimit-Reset-After header | |||||
/// to determine when a rate-limit expires, rather than comparing the | |||||
/// X-RateLimit-Reset timestamp to the system time. | |||||
/// | |||||
/// This should only be changed to false if the system is known to have | |||||
/// a clock that is out of sync. Relying on the Reset-After header will | |||||
/// incur network lag. | |||||
/// | |||||
/// Regardless of this property, we still rely on the system's wall-clock | |||||
/// to determine if a bucket is rate-limited; we do not use any monotonic | |||||
/// clock. Your system will still need a stable clock. | |||||
/// </remarks> | |||||
public bool UseSystemClock { get; set; } = true; | |||||
/// <summary> | |||||
/// Gets or sets whether or not rate-limits should use the system clock. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// If set to <c>false</c>, we will use the X-RateLimit-Reset-After header | |||||
/// to determine when a rate-limit expires, rather than comparing the | |||||
/// X-RateLimit-Reset timestamp to the system time. | |||||
/// | |||||
/// This should only be changed to false if the system is known to have | |||||
/// a clock that is out of sync. Relying on the Reset-After header will | |||||
/// incur network lag. | |||||
/// | |||||
/// Regardless of this property, we still rely on the system's wall-clock | |||||
/// to determine if a bucket is rate-limited; we do not use any monotonic | |||||
/// clock. Your system will still need a stable clock. | |||||
/// </remarks> | |||||
public bool UseSystemClock { get; set; } = true; | |||||
/// <summary> | /// <summary> | ||||
/// Gets or sets whether or not the internal experation check uses the system date | /// Gets or sets whether or not the internal experation check uses the system date | ||||
@@ -38,6 +38,16 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | ||||
/// <summary> | /// <summary> | ||||
/// Sends a message to this message channel. | |||||
/// </summary> | |||||
/// <param name="message">The <see cref="Message"/> created from a <see cref="MessageBuilder"/>.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
Task<IUserMessage> SendMessageAsync(Message message, RequestOptions options = null); | |||||
/// <summary> | |||||
/// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
@@ -92,7 +92,7 @@ namespace Discord | |||||
/// <param name="maxValues">The max values of the placeholder.</param> | /// <param name="maxValues">The max values of the placeholder.</param> | ||||
/// <param name="disabled">Whether or not the menu is disabled.</param> | /// <param name="disabled">Whether or not the menu is disabled.</param> | ||||
/// <param name="row">The row to add the menu to.</param> | /// <param name="row">The row to add the menu to.</param> | ||||
/// <returns></returns> | |||||
/// <returns>The current builder.</returns> | |||||
public ComponentBuilder WithSelectMenu(string customId, List<SelectMenuOptionBuilder> options, | public ComponentBuilder WithSelectMenu(string customId, List<SelectMenuOptionBuilder> options, | ||||
string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0) | string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0) | ||||
{ | { | ||||
@@ -0,0 +1,101 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents a language in which codeblocks can be formatted. | |||||
/// </summary> | |||||
public struct CodeLanguage | |||||
{ | |||||
/// <summary> | |||||
/// Gets the tag of the language. | |||||
/// </summary> | |||||
public string Tag { get; } | |||||
/// <summary> | |||||
/// Gets the name of the language. <see cref="string.Empty"/> if this <see cref="CodeLanguage"/> was constructed with no name provided. | |||||
/// </summary> | |||||
public string Name { get; } = string.Empty; | |||||
/// <summary> | |||||
/// Gets the CSharp language format. | |||||
/// </summary> | |||||
public static readonly CodeLanguage CSharp = new("cs", "csharp"); | |||||
/// <summary> | |||||
/// Gets the Javascript language format. | |||||
/// </summary> | |||||
public static readonly CodeLanguage JavaScript = new("js", "javascript"); | |||||
/// <summary> | |||||
/// Gets the XML language format. | |||||
/// </summary> | |||||
public static readonly CodeLanguage XML = new("xml", "xml"); | |||||
/// <summary> | |||||
/// Gets the HTML language format. | |||||
/// </summary> | |||||
public static readonly CodeLanguage HTML = new("html", "html"); | |||||
/// <summary> | |||||
/// Gets the CSS markdown format. | |||||
/// </summary> | |||||
public static readonly CodeLanguage CSS = new("css", "css"); | |||||
/// <summary> | |||||
/// Gets a language format that represents none. | |||||
/// </summary> | |||||
public static readonly CodeLanguage None = new("", "none"); | |||||
/// <summary> | |||||
/// Creates a new language format with name & tag. | |||||
/// </summary> | |||||
/// <param name="tag">The tag with which markdown will be formatted.</param> | |||||
/// <param name="name">The name of the language.</param> | |||||
public CodeLanguage(string tag, string name) | |||||
{ | |||||
Tag = tag; | |||||
Name = name; | |||||
} | |||||
/// <summary> | |||||
/// Creates a new language format with a tag. | |||||
/// </summary> | |||||
/// <param name="tag">The tag with which markdown will be formatted.</param> | |||||
public CodeLanguage(string tag) | |||||
=> Tag = tag; | |||||
/// <summary> | |||||
/// Gets the tag of the language. | |||||
/// </summary> | |||||
/// <param name="language"></param> | |||||
public static implicit operator string(CodeLanguage language) | |||||
=> language.Tag; | |||||
/// <summary> | |||||
/// Gets a language based on the tag. | |||||
/// </summary> | |||||
/// <param name="tag"></param> | |||||
public static implicit operator CodeLanguage(string tag) | |||||
=> new(tag); | |||||
/// <summary> | |||||
/// Creates markdown format for this language. | |||||
/// </summary> | |||||
/// <param name="input">The input string to format.</param> | |||||
/// <returns>A markdown formatted code-block with this language.</returns> | |||||
public string ToMarkdown(string input) | |||||
=> $"```{Tag}\n{input}\n```"; | |||||
/// <summary> | |||||
/// Gets the tag of the language. | |||||
/// </summary> | |||||
/// <returns><see cref="Tag"/></returns> | |||||
public override string ToString() | |||||
=> $"{Tag}"; | |||||
} | |||||
} |
@@ -0,0 +1,64 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents the format in which a markdown header should be presented. | |||||
/// </summary> | |||||
public readonly struct HeaderFormat | |||||
{ | |||||
public string Format { get; } | |||||
/// <summary> | |||||
/// The biggest header type. | |||||
/// </summary> | |||||
public static readonly HeaderFormat H1 = new("#"); | |||||
/// <summary> | |||||
/// An above-average sized header. | |||||
/// </summary> | |||||
public static readonly HeaderFormat H2 = new("##"); | |||||
/// <summary> | |||||
/// An average-sized header. | |||||
/// </summary> | |||||
public static readonly HeaderFormat H3 = new("###"); | |||||
/// <summary> | |||||
/// A subheader. | |||||
/// </summary> | |||||
public static readonly HeaderFormat H4 = new("####"); | |||||
/// <summary> | |||||
/// A smaller subheader. | |||||
/// </summary> | |||||
public static readonly HeaderFormat H5 = new("#####"); | |||||
/// <summary> | |||||
/// Slightly bigger than regular bold markdown. | |||||
/// </summary> | |||||
public static readonly HeaderFormat H6 = new("######"); | |||||
private HeaderFormat(string format) | |||||
=> Format = format; | |||||
/// <summary> | |||||
/// Formats this header into markdown, appending provided string. | |||||
/// </summary> | |||||
/// <param name="input">The string to turn into a header.</param> | |||||
/// <returns>A markdown formatted header title.</returns> | |||||
public string ToMarkdown(string input) | |||||
=> $"{Format} {input}"; | |||||
/// <summary> | |||||
/// Gets the markdown format for this header. | |||||
/// </summary> | |||||
/// <returns>The markdown format for this header.</returns> | |||||
public override string ToString() | |||||
=> $"{Format}"; | |||||
} | |||||
} |
@@ -0,0 +1,323 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Collections.Immutable; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents a generic message builder that can build <see cref="Message"/>'s. | |||||
/// </summary> | |||||
public class MessageBuilder | |||||
{ | |||||
private readonly List<FileAttachment> _files; | |||||
private List<ISticker> _stickers; | |||||
private List<EmbedBuilder> _embeds; | |||||
/// <summary> | |||||
/// Gets or sets the content of this message | |||||
/// </summary> | |||||
public TextBuilder Content { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets whether or not this message is TTS. | |||||
/// </summary> | |||||
public bool IsTTS { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the allowed mentions of this message. | |||||
/// </summary> | |||||
public AllowedMentions AllowedMentions { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the message reference (reply to) of this message. | |||||
/// </summary> | |||||
public MessageReference MessageReference { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the components of this message. | |||||
/// </summary> | |||||
public ComponentBuilder Components { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the embeds of this message. | |||||
/// </summary> | |||||
public List<EmbedBuilder> Embeds | |||||
{ | |||||
get => _embeds; | |||||
set | |||||
{ | |||||
if (value?.Count > DiscordConfig.MaxEmbedsPerMessage) | |||||
throw new ArgumentOutOfRangeException(nameof(value), $"Embed count must be less than or equal to {DiscordConfig.MaxEmbedsPerMessage}"); | |||||
_embeds = value; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Gets or sets the stickers sent with this message. | |||||
/// </summary> | |||||
public List<ISticker> Stickers | |||||
{ | |||||
get => _stickers; | |||||
set | |||||
{ | |||||
if (value?.Count > DiscordConfig.MaxStickersPerMessage) | |||||
throw new ArgumentOutOfRangeException(nameof(value), $"Sticker count must be less than or equal to {DiscordConfig.MaxStickersPerMessage}"); | |||||
_stickers = value; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Gets or sets the files sent with this message. | |||||
/// </summary> | |||||
public List<FileAttachment> Files | |||||
{ | |||||
get => _files; | |||||
set | |||||
{ | |||||
if(value?.Count > DiscordConfig.MaxFilesPerMessage) | |||||
throw new ArgumentOutOfRangeException(nameof(value), $"File count must be less than or equal to {DiscordConfig.MaxFilesPerMessage}"); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Gets or sets the message flags. | |||||
/// </summary> | |||||
public MessageFlags Flags { get; set; } | |||||
/// <summary> | |||||
/// Creates a new <see cref="MessageBuilder"/> based on the value of <paramref name="content"/>. | |||||
/// </summary> | |||||
/// <param name="content">The message content to create this <see cref="MessageBuilder"/> from.</param> | |||||
public MessageBuilder(string content) | |||||
{ | |||||
Content = new TextBuilder(content); | |||||
} | |||||
public MessageBuilder() | |||||
{ | |||||
_embeds = new(); | |||||
_stickers = new(); | |||||
_files = new(); | |||||
Components = new(); | |||||
} | |||||
/// <summary> | |||||
/// Sets the <see cref="Content"/> of this message. | |||||
/// </summary> | |||||
/// <param name="content">The content of the message.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithContent(TextBuilder builder) | |||||
{ | |||||
Content = builder; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Sets the <see cref="IsTTS"/> of this message. | |||||
/// </summary> | |||||
/// <param name="isTTS">whether or not this message is tts.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithTTS(bool isTTS) | |||||
{ | |||||
IsTTS = isTTS; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Sets the <see cref="Embeds"/> of this message. | |||||
/// </summary> | |||||
/// <param name="embeds">The embeds to be put in this message.</param> | |||||
/// <returns>The current builder.</returns> | |||||
/// <exception cref="ArgumentOutOfRangeException">A message can only contain a maximum of <see cref="DiscordConfig.MaxEmbedsPerMessage"/> embeds.</exception> | |||||
public virtual MessageBuilder WithEmbeds(params EmbedBuilder[] embeds) | |||||
{ | |||||
Embeds = new(embeds); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds an embed builder to the current <see cref="Embeds"/>. | |||||
/// </summary> | |||||
/// <param name="embed">The embed builder to add</param> | |||||
/// <returns>The current builder.</returns> | |||||
/// <exception cref="ArgumentOutOfRangeException">A message can only contain a maximum of <see cref="DiscordConfig.MaxEmbedsPerMessage"/> embeds.</exception> | |||||
public virtual MessageBuilder AddEmbed(EmbedBuilder embed) | |||||
{ | |||||
if (_embeds?.Count >= DiscordConfig.MaxEmbedsPerMessage) | |||||
throw new ArgumentOutOfRangeException(nameof(embed.Length), $"A message can only contain a maximum of {DiscordConfig.MaxEmbedsPerMessage} embeds"); | |||||
_embeds ??= new(); | |||||
_embeds.Add(embed); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Sets the <see cref="AllowedMentions"/> for this message. | |||||
/// </summary> | |||||
/// <param name="allowedMentions">The allowed mentions for this message.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithAllowedMentions(AllowedMentions allowedMentions) | |||||
{ | |||||
AllowedMentions = allowedMentions; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Sets the <see cref="MessageReference"/> for this message. | |||||
/// </summary> | |||||
/// <param name="reference">The message reference (reply-to) for this message.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithMessageReference(MessageReference reference) | |||||
{ | |||||
MessageReference = reference; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Sets the <see cref="MessageReference"/> for this current message. | |||||
/// </summary> | |||||
/// <param name="message">The message to set as a reference.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithMessageReference(IMessage message) | |||||
{ | |||||
if (message != null) | |||||
MessageReference = new MessageReference(message.Id, message.Channel?.Id, ((IGuildChannel)message.Channel)?.GuildId); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Sets the <see cref="Components"/> for this message. | |||||
/// </summary> | |||||
/// <param name="builder">The component builder to set.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithComponentBuilder(ComponentBuilder builder) | |||||
{ | |||||
Components = builder; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a button to the current <see cref="Components"/>. | |||||
/// </summary> | |||||
/// <param name="button">The button builder to add.</param> | |||||
/// <param name="row">The optional row to place the button on.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithButton(ButtonBuilder button, int row = 0) | |||||
{ | |||||
Components ??= new(); | |||||
Components.WithButton(button, row); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a button to the current <see cref="Components"/>. | |||||
/// </summary> | |||||
/// <param name="label">The label text for the newly added button.</param> | |||||
/// <param name="style">The style of this newly added button.</param> | |||||
/// <param name="emote">A <see cref="IEmote"/> to be used with this button.</param> | |||||
/// <param name="customId">The custom id of the newly added button.</param> | |||||
/// <param name="url">A URL to be used only if the <see cref="ButtonStyle"/> is a Link.</param> | |||||
/// <param name="disabled">Whether or not the newly created button is disabled.</param> | |||||
/// <param name="row">The row the button should be placed on.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithButton( | |||||
string label = null, | |||||
string customId = null, | |||||
ButtonStyle style = ButtonStyle.Primary, | |||||
IEmote emote = null, | |||||
string url = null, | |||||
bool disabled = false, | |||||
int row = 0) | |||||
{ | |||||
Components ??= new(); | |||||
Components.WithButton(label, customId, style, emote, url, disabled, row); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a select menu to the current <see cref="Components"/>. | |||||
/// </summary> | |||||
/// <param name="menu">The select menu builder to add.</param> | |||||
/// <param name="row">The optional row to place the select menu on.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0) | |||||
{ | |||||
Components ??= new(); | |||||
Components.WithSelectMenu(menu, row); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a select menu to the current <see cref="Components"/>. | |||||
/// </summary> | |||||
/// <param name="customId">The custom id of the menu.</param> | |||||
/// <param name="options">The options of the menu.</param> | |||||
/// <param name="placeholder">The placeholder of the menu.</param> | |||||
/// <param name="minValues">The min values of the placeholder.</param> | |||||
/// <param name="maxValues">The max values of the placeholder.</param> | |||||
/// <param name="disabled">Whether or not the menu is disabled.</param> | |||||
/// <param name="row">The row to add the menu to.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithSelectMenu(string customId, List<SelectMenuOptionBuilder> options, | |||||
string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0) | |||||
{ | |||||
Components ??= new(); | |||||
Components.WithSelectMenu(customId, options, placeholder, minValues, maxValues, disabled, row); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Sets the <see cref="Files"/> of this message. | |||||
/// </summary> | |||||
/// <param name="files">The file collection to set.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder WithFiles(IEnumerable<FileAttachment> files) | |||||
{ | |||||
Files = new List<FileAttachment>(files); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a file to the current collection <see cref="Files"/>. | |||||
/// </summary> | |||||
/// <param name="file">The file to add.</param> | |||||
/// <returns>The current builder.</returns> | |||||
public virtual MessageBuilder AddFile(FileAttachment file) | |||||
{ | |||||
Files.Add(file); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Builds this message builder to a <see cref="Message"/> that can be sent across the discord api. | |||||
/// </summary> | |||||
/// <returns>A <see cref="Message"/> that can be sent to the discord api.</returns> | |||||
public Message Build() | |||||
{ | |||||
var embeds = _embeds != null && _embeds.Count > 0 | |||||
? _embeds.Select(x => x.Build()).ToImmutableArray() | |||||
: ImmutableArray<Embed>.Empty; | |||||
return new Message( | |||||
Content.Build(), | |||||
IsTTS, | |||||
embeds, | |||||
AllowedMentions, | |||||
MessageReference, | |||||
Components?.Build(), | |||||
_stickers, | |||||
Files?.ToImmutableArray() ?? ImmutableArray<FileAttachment>.Empty, | |||||
Flags | |||||
); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,125 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents a builder for multi-line text. | |||||
/// </summary> | |||||
public class MultiLineBuilder | |||||
{ | |||||
/// <summary> | |||||
/// The underlying list of lines this builder uses to construct multiline text. | |||||
/// </summary> | |||||
public List<string> Lines { get; set; } | |||||
/// <summary> | |||||
/// Creates a new instance of <see cref="MultiLineBuilder"/>. | |||||
/// </summary> | |||||
public MultiLineBuilder() | |||||
{ | |||||
Lines = new(); | |||||
} | |||||
/// <summary> | |||||
/// Creates a new instance of <see cref="MultiLineBuilder"/> with a pre-defined capacity. | |||||
/// </summary> | |||||
/// <param name="capacity"></param> | |||||
public MultiLineBuilder(int capacity) | |||||
{ | |||||
Lines = new(capacity); | |||||
} | |||||
/// <summary> | |||||
/// Creates a new instance of <see cref="MultiLineBuilder"/> with a number of lines pre-defined. | |||||
/// </summary> | |||||
/// <param name="entries">The range of lines to add to this builder.</param> | |||||
public MultiLineBuilder(params string[] entries) | |||||
{ | |||||
Lines = new(entries); | |||||
} | |||||
/// <summary> | |||||
/// Adds a line to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to add to this line.</param> | |||||
/// <returns>The same instance with a line appended.</returns> | |||||
public MultiLineBuilder AddLine(string text) | |||||
{ | |||||
Lines.Add(text); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a range of lines to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The range of text to add.</param> | |||||
/// <returns>The same instance with a range of lines appended.</returns> | |||||
public MultiLineBuilder AddLines(IEnumerable<string> text) | |||||
{ | |||||
if (!text.Any()) | |||||
throw new ArgumentException("The passed range does not contain any values", nameof(text)); | |||||
Lines.AddRange(text); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Removes a (or more) line(s) from the builder. | |||||
/// </summary> | |||||
/// <param name="predicate">The predicate to remove lines with.</param> | |||||
/// <returns>The same instance with all lines matching <paramref name="predicate"/> removed.</returns> | |||||
public MultiLineBuilder RemoveLine(Predicate<string> predicate) | |||||
{ | |||||
if (predicate is null) | |||||
throw new ArgumentNullException(nameof(predicate)); | |||||
Lines.RemoveAll(x => predicate(x)); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Removes a line from the builder. | |||||
/// </summary> | |||||
/// <param name="index">The index to remove a line at.</param> | |||||
/// <returns></returns> | |||||
public MultiLineBuilder RemoveLine(int index) | |||||
{ | |||||
Lines.RemoveAt(index); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Gets the line at a specific index. | |||||
/// </summary> | |||||
/// <param name="index">The index to get a line for.</param> | |||||
/// <returns>The line at defined <paramref name="index"/>.</returns> | |||||
public string this[int index] | |||||
{ | |||||
get | |||||
{ | |||||
return Lines[index]; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Builds the builder into multiline text. | |||||
/// </summary> | |||||
/// <returns>A string representing the lines added in this builder.</returns> | |||||
public string Build() | |||||
=> string.Join(Environment.NewLine, Lines); | |||||
/// <summary> | |||||
/// Creates a string from the lines currently present in <see cref="Lines"/>. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method has the same behavior as <see cref="Build"/>. | |||||
/// </remarks> | |||||
/// <returns>A string representing the lines added in this builder.</returns> | |||||
public override string ToString() | |||||
=> string.Join(Environment.NewLine, Lines); | |||||
} | |||||
} |
@@ -0,0 +1,493 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents a builder to build Discord messages with markdown with. | |||||
/// </summary> | |||||
public class TextBuilder | |||||
{ | |||||
private readonly StringBuilder _builder; | |||||
private bool _lineStart = false; | |||||
/// <summary> | |||||
/// Creates a new instance of <see cref="TextBuilder"/>. | |||||
/// </summary> | |||||
public TextBuilder() | |||||
{ | |||||
_builder = new(); | |||||
} | |||||
/// <summary> | |||||
/// Creates a new instance of <see cref="TextBuilder"/> with a starting string appended. | |||||
/// </summary> | |||||
/// <param name="startingString">The string to start the builder with.</param> | |||||
public TextBuilder(string startingString) | |||||
{ | |||||
_builder = new(startingString); | |||||
} | |||||
/// <summary> | |||||
/// Creates a new instance of <see cref="TextBuilder"/> with a capacity and (optionally) max capacity defined. | |||||
/// </summary> | |||||
/// <param name="capacity">The init capacity of the underlying <see cref="StringBuilder"/>.</param> | |||||
/// <param name="maxCapacity">The maximum capacity of the underlying <see cref="StringBuilder"/>.</param> | |||||
public TextBuilder(int capacity, int? maxCapacity = null) | |||||
{ | |||||
if (maxCapacity is not null) | |||||
_builder = new(capacity, maxCapacity.Value); | |||||
else | |||||
_builder = new(capacity); | |||||
} | |||||
/// <summary> | |||||
/// Adds a header to the builder. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// [Note] Headers are only supported in forums, which are not released publically yet. | |||||
/// </remarks> | |||||
/// <param name="text">The text to be present in the header.</param> | |||||
/// <param name="format">The header format.</param> | |||||
/// <param name="skipLine">If the builder should skip a line when creating the next parameter.</param> | |||||
/// <returns>The same instance with a header appended. This method will append a new line below the header.</returns> | |||||
public TextBuilder AddHeader(string text, HeaderFormat format, bool skipLine = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
if (skipLine) | |||||
_builder.AppendLine(); | |||||
_builder.AppendLine(text.ToHeader(format)); | |||||
_lineStart = true; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds bold text to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with bold text appended.</returns> | |||||
public TextBuilder AddBoldText(string text, bool inline = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
Construct(Format.Bold(text), inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds bold text to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with bold text appended.</returns> | |||||
public TextBuilder AddBoldText(MultiLineBuilder builder, bool inline = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddBoldText(text, inline); | |||||
} | |||||
/// <summary> | |||||
/// Adds italic text to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with italic appended.</returns> | |||||
public TextBuilder AddItalicText(string text, bool inline = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
Construct(Discord.Format.Italics(text), inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds italic text to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with italic text appended.</returns> | |||||
public TextBuilder AddItalicText(MultiLineBuilder builder, bool inline = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddItalicText(text, inline); | |||||
} | |||||
/// <summary> | |||||
/// Adds plain text to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with plain text appended.</returns> | |||||
public TextBuilder AddPlainText(string text, bool inline = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
Construct(text, inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds plain text to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with plain text appended.</returns> | |||||
public TextBuilder AddPlainText(MultiLineBuilder builder, bool inline = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddPlainText(text, inline); | |||||
} | |||||
/// <summary> | |||||
/// Adds underlined text to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with underlined text appended.</returns> | |||||
public TextBuilder AddUnderlinedText(string text, bool inline = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
Construct(text.ToUnderline(), inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds underlined text to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with underlined text appended.</returns> | |||||
public TextBuilder AddUnderlinedText(MultiLineBuilder builder, bool inline = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddUnderlinedText(text, inline); | |||||
} | |||||
/// <summary> | |||||
/// Adds a timestamp to the builder. | |||||
/// </summary> | |||||
/// <param name="dateTime">The time for which this timestamp should be created.</param> | |||||
/// <param name="style">The style of the stamp.</param> | |||||
/// <param name="inline">If the stamp should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with a timestamp appended.</returns> | |||||
public TextBuilder AddTimestamp(DateTime dateTime, TimestampTagStyles style, bool inline = true) | |||||
{ | |||||
Construct(TimestampTag.FromDateTime(dateTime, style).ToString(), inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds strikethrough text to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with striked through text appended.</returns> | |||||
public TextBuilder AddStrikeThroughText(string text, bool inline = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
Construct(text.ToStrikethrough(), inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds strikethrough text to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with striked through text appended.</returns> | |||||
public TextBuilder AddStrikeThroughText(MultiLineBuilder builder, bool inline = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddStrikeThroughText(text, inline); | |||||
} | |||||
/// <summary> | |||||
/// Adds a spoiler to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with a spoiler appended.</returns> | |||||
public TextBuilder AddSpoiler(string text, bool inline = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
Construct(text.ToSpoiler(), inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a spoiler to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with a spoiler appended.</returns> | |||||
public TextBuilder AddSpoiler(MultiLineBuilder builder, bool inline = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddSpoiler(text, inline); | |||||
} | |||||
/// <summary> | |||||
/// Adds a quote to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="skipLine">If the builder should skip a line when creating the next parameter.</param> | |||||
/// <returns>The same instance with a quote appended. This method will append a new line below the quote.</returns> | |||||
public TextBuilder AddQuote(string text, bool skipLine = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
if (skipLine) | |||||
_builder.AppendLine(); | |||||
_builder.AppendLine(text.ToQuote()); | |||||
_lineStart = true; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a quote to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with a quote appended.</returns> | |||||
public TextBuilder AddQuote(MultiLineBuilder builder, bool inline = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddQuote(text, inline); | |||||
} | |||||
/// <summary> | |||||
/// Adds a block quote to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="skipLine">If the builder should skip a line when creating the next parameter.</param> | |||||
/// <returns>The same instance with a block quote appended. This method will append a new line below the quote.</returns> | |||||
public TextBuilder AddBlockQuote(string text, bool skipLine = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
if (skipLine) | |||||
_builder.AppendLine(); | |||||
_builder.AppendLine(text.ToBlockQuote()); | |||||
_lineStart = true; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a block quote to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="skipLine">If the builder should skip a line when creating the next parameter.</param> | |||||
/// <returns>The same instance with a block quote appended. This method will append a new line below the quote.</returns> | |||||
public TextBuilder AddBlockQuote(MultiLineBuilder builder, bool skipLine = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddBlockQuote(text, skipLine); | |||||
} | |||||
/// <summary> | |||||
/// Adds code marked text to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with code marked text appended.</returns> | |||||
public TextBuilder AddCode(string text, bool inline = false) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
Construct(text.ToCode(), inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds code marked text to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="inline">If the text should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with code marked text appended.</returns> | |||||
public TextBuilder AddCode(MultiLineBuilder builder, bool inline = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddCode(text, inline); | |||||
} | |||||
/// <summary> | |||||
/// Adds a code block to the builder. | |||||
/// </summary> | |||||
/// <param name="text">The text to be present in the markdown.</param> | |||||
/// <param name="lang">The language in which this code should be presented.</param> | |||||
/// <param name="skipLine">If the builder should skip a line when creating the next parameter.</param> | |||||
/// <returns>The same instance with a code block appended. This method will append a new line below the block.</returns> | |||||
public TextBuilder AddCodeBlock(string text, CodeLanguage? lang = null, bool skipLine = true) | |||||
{ | |||||
if (string.IsNullOrEmpty(text)) | |||||
throw new ArgumentException("Value cannot be null or empty.", nameof(text)); | |||||
lang ??= CodeLanguage.None; | |||||
if (skipLine) | |||||
_builder.AppendLine(); | |||||
_builder.AppendLine(text.ToCodeBlock(lang)); | |||||
_lineStart = true; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a code block to the builder. | |||||
/// </summary> | |||||
/// <param name="builder">A builder for multiline text.</param> | |||||
/// <param name="lang">The language in which this code should be presented.</param> | |||||
/// <param name="skipLine">If the builder should skip a line when creating the next parameter.</param> | |||||
/// <returns>The same instance with a code block appended. This method will append a new line below the quote.</returns> | |||||
public TextBuilder AddCodeBlock(MultiLineBuilder builder, CodeLanguage? lang = null, bool skipLine = true) | |||||
{ | |||||
if (builder is null) | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
var text = builder.Build(); | |||||
return AddCodeBlock(text, lang, skipLine); | |||||
} | |||||
/// <summary> | |||||
/// Adds an emote to the builder. | |||||
/// </summary> | |||||
/// <param name="emote">The emote to add.</param> | |||||
/// <param name="inline">If the emote should be appended in the same line or if it should append to a new line.</param> | |||||
/// <returns>The same instance with an emote appended.</returns> | |||||
public TextBuilder AddEmote(IEmote emote, bool inline = false) | |||||
{ | |||||
if (emote is null) | |||||
throw new ArgumentNullException(nameof(emote)); | |||||
var str = emote switch | |||||
{ | |||||
Emote ee => ee.ToString(), | |||||
Emoji ei => ei.ToString(), | |||||
_ => null | |||||
}; | |||||
Construct(str, inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a range of emotes to the builder. | |||||
/// </summary> | |||||
/// <param name="seperator">The seperator to join the emotes with.</param> | |||||
/// <param name="inline">If the emotes should be appended in the same line or if it should append to a new line.</param> | |||||
/// <param name="emotes">The range of emotes to add.</param> | |||||
/// <returns>The same instance with a range of emotes appended.</returns> | |||||
public TextBuilder AddEmotes(string seperator, bool inline = false, params IEmote[] emotes) | |||||
{ | |||||
if (!emotes.Any()) | |||||
throw new ArgumentException("No values were found in the passed selection", nameof(emotes)); | |||||
var str = string.Join(seperator, emotes.Select(x => | |||||
{ | |||||
return x switch | |||||
{ | |||||
Emote emote => emote.ToString(), | |||||
Emoji emoji => emoji.ToString(), | |||||
_ => throw new ArgumentNullException(nameof(emotes)), | |||||
}; | |||||
})); | |||||
Construct(str, inline); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Starts the next query to the builder on a new line. | |||||
/// </summary> | |||||
/// <returns>The same instance with an empty line appended.</returns> | |||||
public TextBuilder AddNewline() | |||||
{ | |||||
_builder.AppendLine(); | |||||
_lineStart = true; | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Builds a Discord message string from this instance. | |||||
/// </summary> | |||||
/// <returns>The string to send to Discord.</returns> | |||||
public string Build() | |||||
=> _builder.ToString(); | |||||
private void Construct(string text, bool inline) | |||||
{ | |||||
if (_builder.Length + text.Length > DiscordConfig.MaxMessageSize) | |||||
throw new ArgumentOutOfRangeException(nameof(text), $"Maximum message length of {DiscordConfig.MaxMessageSize} has been reached."); | |||||
if (inline) | |||||
{ | |||||
if (!_lineStart) | |||||
text = " " + text; | |||||
else | |||||
_lineStart = false; | |||||
_builder.Append(text); // add a space to define | |||||
} | |||||
else | |||||
{ | |||||
if (_lineStart) | |||||
_lineStart = false; | |||||
_builder.AppendLine(); | |||||
_builder.Append(text); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Builds the underlying <see cref="StringBuilder"/> to a string. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method has the same functionality as <see cref="Build"/>. | |||||
/// </remarks> | |||||
/// <returns>The string to send to Discord.</returns> | |||||
public override string ToString() | |||||
=> _builder.ToString(); | |||||
} | |||||
} |
@@ -0,0 +1,77 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents a message created by a <see cref="MessageBuilder"/> that can be sent to a channel. | |||||
/// </summary> | |||||
public sealed class Message | |||||
{ | |||||
/// <summary> | |||||
/// Gets the content of the message. | |||||
/// </summary> | |||||
public string Content { get; internal set; } | |||||
/// <summary> | |||||
/// Gets whether or not this message should be read by a text-to-speech engine. | |||||
/// </summary> | |||||
public bool IsTTS { get; internal set; } | |||||
/// <summary> | |||||
/// Gets a collection of embeds sent along with this message. | |||||
/// </summary> | |||||
public IReadOnlyCollection<Embed> Embeds { get; internal set; } | |||||
/// <summary> | |||||
/// Gets the allowed mentions for this message. | |||||
/// </summary> | |||||
public AllowedMentions AllowedMentions { get; internal set; } | |||||
/// <summary> | |||||
/// Gets the message reference (reply to) for this message. | |||||
/// </summary> | |||||
public MessageReference MessageReference { get; internal set; } | |||||
/// <summary> | |||||
/// Gets the components of this message. | |||||
/// </summary> | |||||
public MessageComponent Components { get; internal set; } | |||||
/// <summary> | |||||
/// Gets a collection of sticker ids that will be sent with this message. | |||||
/// </summary> | |||||
public IReadOnlyCollection<ISticker> Stickers { get; internal set; } | |||||
/// <summary> | |||||
/// Gets a collection of files sent with this message. | |||||
/// </summary> | |||||
public IReadOnlyCollection<FileAttachment> Attachments { get; internal set; } | |||||
/// <summary> | |||||
/// Gets the message flags for this message. | |||||
/// </summary> | |||||
public MessageFlags Flags { get; internal set; } | |||||
public bool HasFiles | |||||
=> Attachments?.Any() ?? false; | |||||
internal Message(string content, bool istts, IReadOnlyCollection<Embed> embeds, AllowedMentions allowedMentions, | |||||
MessageReference messagereference, MessageComponent components, IReadOnlyCollection<ISticker> stickers, | |||||
IReadOnlyCollection<FileAttachment> attachments, MessageFlags flags) | |||||
{ | |||||
Content = content; | |||||
IsTTS = istts; | |||||
Embeds = embeds; | |||||
AllowedMentions = allowedMentions; | |||||
MessageReference = messagereference; | |||||
Components = components; | |||||
Stickers = stickers; | |||||
Attachments = attachments; | |||||
Flags = flags; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,101 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
internal static class MarkdownExtensions | |||||
{ | |||||
public static string ToBold(this string text, int index = 0, int? count = null) //=> $"**{text}**"; | |||||
{ | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"**{text.Substring(index, (index + length))}**", index, length); | |||||
} | |||||
public static string ToItalic(this string text, int index = 0, int? count = null) //=> $"*{text}*"; | |||||
{ | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"*{text.Substring(index, (index + length))}*", index, length); | |||||
} | |||||
public static string ToUnderline(this string text, int index = 0, int? count = null) //=> $"__{text}__"; | |||||
{ | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"__{text.Substring(index, (index + length))}__", index, length); | |||||
} | |||||
public static string ToStrikethrough(this string text, int index = 0, int? count = null) //=> $"~~{text}~~"; | |||||
{ | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"~~{text.Substring(index, (index + length))}~~", index, length); | |||||
} | |||||
public static string ToSpoiler(this string text, int index = 0, int? count = null) //=> $"||{text}||"; | |||||
{ | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"||{text.Substring(index, (index + length))}||", index, length); | |||||
} | |||||
public static string ToQuote(this string text, int index = 0, int? count = null) //=> $"> {text}"; | |||||
{ | |||||
if (index is 0 && count is null) | |||||
text = text.Replace(Environment.NewLine, $"{Environment.NewLine}> "); | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"{Environment.NewLine}> {text.Substring(index, (index + length))}{Environment.NewLine}", index, length); | |||||
} | |||||
public static string ToBlockQuote(this string text, int index = 0, int? count = null) //=> $">>> {text}"; | |||||
{ | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"{Environment.NewLine}>>> {text.Substring(index, (index + length))}{Environment.NewLine}", index, length); | |||||
} | |||||
public static string ToCode(this string text, int index = 0, int? count = null) //=> $"`{text}`"; | |||||
{ | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"`{text.Substring(index, (index + length))}`", index, length); | |||||
} | |||||
public static string ToCodeBlock(this string text, CodeLanguage? lang = null, int index = 0, int? count = null) | |||||
{ | |||||
lang ??= CodeLanguage.None; | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"```{lang.Value}{Environment.NewLine}{text.Substring(index, (index + length))}{Environment.NewLine}```", index, length); | |||||
} | |||||
public static string ToHyperLink(this string text, string url, int index = 0, int? count = null) | |||||
{ | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"[{text.Substring(index, (index + length))}]({url})", index, length); | |||||
} | |||||
public static string ToHeader(this string text, HeaderFormat format, int index = 0, int? count = null) | |||||
{ | |||||
var length = count ?? (text.Length - index); | |||||
return text.Format($"{Environment.NewLine}{format.Format} {text.Substring(index, (index + length))} {Environment.NewLine}", index, length); | |||||
} | |||||
public static string WithTimestamp(this string text, DateTime dateTime, TimestampTagStyles style, int index = 0) | |||||
=> text.Insert(index, TimestampTag.FromDateTime(dateTime, style).ToString()); | |||||
private static string Format(this string text, string format, int index, int length) | |||||
{ | |||||
return text.Insert(index, format).Remove(index + format.Length, length); | |||||
} | |||||
} | |||||
} |
@@ -93,9 +93,29 @@ namespace Discord | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
public static async Task<IUserMessage> ReplyAsync(this IUserMessage msg, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
public static Task<IUserMessage> ReplyAsync(this IUserMessage msg, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
{ | { | ||||
return await msg.Channel.SendMessageAsync(text, isTTS, embed, options, allowedMentions, new MessageReference(messageId: msg.Id), components, stickers, embeds).ConfigureAwait(false); | |||||
return msg.Channel.SendMessageAsync(text, isTTS, embed, options, allowedMentions, new MessageReference(messageId: msg.Id), components, stickers, embeds); | |||||
} | |||||
/// <summary> | |||||
/// Sends an inline reply that references a message. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// Any feild set with the <see cref="MessageBuilder.WithMessageReference(IMessage)"/> will be | |||||
/// overwritten by this function. | |||||
/// </remarks> | |||||
/// <param name="msg">The message that is being replied on.</param> | |||||
/// <param name="message">The <see cref="Message"/> created from a <see cref="MessageBuilder"/>.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
public static Task<IUserMessage> ReplyAsync(this IUserMessage msg, Message message, RequestOptions options = null) | |||||
{ | |||||
message.MessageReference = new MessageReference(messageId: msg.Id); | |||||
return msg.Channel.SendMessageAsync(message, options); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -50,6 +50,21 @@ namespace Discord | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// Sends a message via DM. | |||||
/// </summary> | |||||
/// <param name="user">The user to send the DM to.</param> | |||||
/// <param name="message">The <see cref="Message"/> created from a <see cref="MessageBuilder"/>.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
public static async Task<IUserMessage> SendMessageAsync(this IUser user, Message message, RequestOptions options = null) | |||||
{ | |||||
return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(message, options).ConfigureAwait(false); | |||||
} | |||||
/// <summary> | |||||
/// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
@@ -10,22 +10,44 @@ namespace Discord | |||||
private static readonly string[] SensitiveCharacters = { | private static readonly string[] SensitiveCharacters = { | ||||
"\\", "*", "_", "~", "`", ".", ":", "/", ">", "|" }; | "\\", "*", "_", "~", "`", ".", ":", "/", ">", "|" }; | ||||
/// <summary> Returns a markdown-formatted string with bold formatting. </summary> | |||||
/// <summary> | |||||
/// Returns a markdown-formatted string with bold formatting. | |||||
/// </summary> | |||||
public static string Bold(string text) => $"**{text}**"; | public static string Bold(string text) => $"**{text}**"; | ||||
/// <summary> Returns a markdown-formatted string with italics formatting. </summary> | |||||
/// <summary> | |||||
/// Returns a markdown-formatted string with italics formatting. | |||||
/// </summary> | |||||
public static string Italics(string text) => $"*{text}*"; | public static string Italics(string text) => $"*{text}*"; | ||||
/// <summary> Returns a markdown-formatted string with underline formatting. </summary> | |||||
/// <summary> | |||||
/// Returns a markdown-formatted string with underline formatting. | |||||
/// </summary> | |||||
public static string Underline(string text) => $"__{text}__"; | public static string Underline(string text) => $"__{text}__"; | ||||
/// <summary> Returns a markdown-formatted string with strike-through formatting. </summary> | |||||
/// <summary> | |||||
/// Returns a markdown-formatted string with strike-through formatting. | |||||
/// </summary> | |||||
public static string Strikethrough(string text) => $"~~{text}~~"; | public static string Strikethrough(string text) => $"~~{text}~~"; | ||||
/// <summary> Returns a string with spoiler formatting. </summary> | |||||
/// <summary> | |||||
/// Returns a string with spoiler formatting. | |||||
/// </summary> | |||||
public static string Spoiler(string text) => $"||{text}||"; | public static string Spoiler(string text) => $"||{text}||"; | ||||
/// <summary> Returns a markdown-formatted URL. Only works in <see cref="EmbedBuilder"/> descriptions and fields. </summary> | |||||
/// <summary> | |||||
/// Returns a markdown-formatted URL. Only works in <see cref="EmbedBuilder"/> descriptions and fields. | |||||
/// </summary> | |||||
public static string Url(string text, string url) => $"[{text}]({url})"; | public static string Url(string text, string url) => $"[{text}]({url})"; | ||||
/// <summary> Escapes a URL so that a preview is not generated. </summary> | |||||
/// <summary> | |||||
/// Escapes a URL so that a preview is not generated. | |||||
/// </summary> | |||||
public static string EscapeUrl(string url) => $"<{url}>"; | public static string EscapeUrl(string url) => $"<{url}>"; | ||||
/// <summary> Returns a markdown-formatted string with codeblock formatting. </summary> | |||||
/// <summary> | |||||
/// Returns a markdown-formatted string with codeblock formatting. | |||||
/// </summary> | |||||
public static string Code(string text, string language = null) | public static string Code(string text, string language = null) | ||||
{ | { | ||||
if (language != null || text.Contains("\n")) | if (language != null || text.Contains("\n")) | ||||
@@ -34,7 +56,9 @@ namespace Discord | |||||
return $"`{text}`"; | return $"`{text}`"; | ||||
} | } | ||||
/// <summary> Sanitizes the string, safely escaping any Markdown sequences. </summary> | |||||
/// <summary> | |||||
/// Sanitizes the string, safely escaping any Markdown sequences. | |||||
/// </summary> | |||||
public static string Sanitize(string text) | public static string Sanitize(string text) | ||||
{ | { | ||||
foreach (string unsafeChar in SensitiveCharacters) | foreach (string unsafeChar in SensitiveCharacters) | ||||
@@ -265,6 +265,20 @@ namespace Discord.Rest | |||||
return builder.ToImmutable(); | return builder.ToImmutable(); | ||||
} | } | ||||
public static Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, Message message, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotNull(message, nameof(message)); | |||||
if (message.HasFiles) | |||||
{ | |||||
return SendFilesAsync(channel, client, message.Attachments, message.Content, message.IsTTS, null, message.AllowedMentions, | |||||
message.MessageReference, message.Components, message.Stickers?.ToArray(), options, message.Embeds?.ToArray(), message.Flags); | |||||
} | |||||
else | |||||
return SendMessageAsync(channel, client, message.Content, message.IsTTS, null, message.AllowedMentions, message.MessageReference, | |||||
message.Components, message.Stickers?.ToArray(), options, message.Embeds?.ToArray(), message.Flags); | |||||
} | |||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | /// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | ||||
public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, | public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
@@ -11,7 +11,10 @@ namespace Discord.Rest | |||||
{ | { | ||||
/// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | /// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | ||||
new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | ||||
/// <inheritdoc cref="IMessageChannel.SendMessageAsync(Message, RequestOptions)"/> | |||||
new Task<RestUserMessage> SendMessageAsync(Message message, RequestOptions options = null); | |||||
/// <inheritdoc cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | /// <inheritdoc cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | ||||
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | ||||
@@ -92,6 +92,10 @@ namespace Discord.Rest | |||||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | ||||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | ||||
/// <inheritdoc/> | |||||
public Task<RestUserMessage> SendMessageAsync(Message message, RequestOptions options = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, message, options); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | /// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | ||||
@@ -271,6 +275,10 @@ namespace Discord.Rest | |||||
AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | ||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(Message message, RequestOptions options) | |||||
=> await SendMessageAsync(message, options).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
@@ -102,6 +102,10 @@ namespace Discord.Rest | |||||
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | ||||
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); | => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); | ||||
/// <inheritdoc/> | |||||
public Task<RestUserMessage> SendMessageAsync(Message message, RequestOptions options = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, message, options); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | /// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | ||||
@@ -255,7 +259,11 @@ namespace Discord.Rest | |||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, | => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, | ||||
stickers, embeds, flags).ConfigureAwait(false); | stickers, embeds, flags).ConfigureAwait(false); | ||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(Message message, RequestOptions options) | |||||
=> await SendMessageAsync(message, options).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
#region IAudioChannel | #region IAudioChannel | ||||
@@ -101,6 +101,10 @@ namespace Discord.Rest | |||||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | ||||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | ||||
/// <inheritdoc/> | |||||
public Task<RestUserMessage> SendMessageAsync(Message message, RequestOptions options = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, message, options); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | /// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | ||||
@@ -386,6 +390,10 @@ namespace Discord.Rest | |||||
AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | ||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(Message message, RequestOptions options) | |||||
=> await SendMessageAsync(message, options).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
#region IGuildChannel | #region IGuildChannel | ||||
@@ -352,7 +352,7 @@ namespace Discord.Rest | |||||
#endregion | #endregion | ||||
#region Responses | #region Responses | ||||
public static async Task<Message> ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action<MessageProperties> func, | |||||
public static async Task<API.Message> ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action<MessageProperties> func, | |||||
RequestOptions options = null) | RequestOptions options = null) | ||||
{ | { | ||||
var args = new MessageProperties(); | var args = new MessageProperties(); | ||||
@@ -394,7 +394,7 @@ namespace Discord.Rest | |||||
} | } | ||||
public static async Task DeleteFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null) | public static async Task DeleteFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null) | ||||
=> await client.ApiClient.DeleteInteractionFollowupMessageAsync(message.Id, message.Token, options); | => await client.ApiClient.DeleteInteractionFollowupMessageAsync(message.Id, message.Token, options); | ||||
public static async Task<Message> ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action<MessageProperties> func, | |||||
public static async Task<API.Message> ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action<MessageProperties> func, | |||||
RequestOptions options = null) | RequestOptions options = null) | ||||
{ | { | ||||
var args = new MessageProperties(); | var args = new MessageProperties(); | ||||
@@ -137,6 +137,10 @@ namespace Discord.WebSocket | |||||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | ||||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | ||||
/// <inheritdoc/> | |||||
public Task<RestUserMessage> SendMessageAsync(Message message, RequestOptions options = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, message, options); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | /// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | ||||
@@ -310,6 +314,10 @@ namespace Discord.WebSocket | |||||
AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | ||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(Message message, RequestOptions options) | |||||
=> await SendMessageAsync(message, options).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
#region IChannel | #region IChannel | ||||
@@ -176,6 +176,10 @@ namespace Discord.WebSocket | |||||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | ||||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | ||||
/// <inheritdoc/> | |||||
public Task<RestUserMessage> SendMessageAsync(Message message, RequestOptions options = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, message, options); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | /// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | ||||
@@ -382,6 +386,10 @@ namespace Discord.WebSocket | |||||
AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | ||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(Message message, RequestOptions options) | |||||
=> await SendMessageAsync(message, options).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
#region IAudioChannel | #region IAudioChannel | ||||
@@ -210,6 +210,10 @@ namespace Discord.WebSocket | |||||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | ||||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | ||||
/// <inheritdoc/> | |||||
public Task<RestUserMessage> SendMessageAsync(Message message, RequestOptions options = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, message, options); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | /// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | ||||
@@ -448,7 +452,10 @@ namespace Discord.WebSocket | |||||
AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | ||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ISticker[] stickers, Embed[] embeds, MessageFlags flags) | ||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(Message message, RequestOptions options) | |||||
=> await SendMessageAsync(message, options).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
#region INestedChannel | #region INestedChannel | ||||
@@ -88,5 +88,6 @@ namespace Discord | |||||
public Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | public Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | ||||
public Task<IUserMessage> SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | public Task<IUserMessage> SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | ||||
public Task<IUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | public Task<IUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | ||||
public Task<IUserMessage> SendMessageAsync(Message message, RequestOptions options = null) => throw new NotImplementedException(); | |||||
} | } | ||||
} | } |
@@ -115,5 +115,6 @@ namespace Discord | |||||
public Task<IUserMessage> SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | public Task<IUserMessage> SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | ||||
public Task<IUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | public Task<IUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | ||||
public Task<IUserMessage> SendMessageAsync(Message message, RequestOptions options = null) => throw new NotImplementedException(); | |||||
} | } | ||||
} | } |
@@ -216,5 +216,6 @@ namespace Discord | |||||
public Task<IThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | public Task<IThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException(); | ||||
public Task<IInviteMetadata> CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException(); | public Task<IInviteMetadata> CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException(); | ||||
public Task<IThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null) => throw new NotImplementedException(); | public Task<IThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null) => throw new NotImplementedException(); | ||||
public Task<IUserMessage> SendMessageAsync(Message message, RequestOptions options = null) => throw new NotImplementedException(); | |||||
} | } | ||||
} | } |