Reapply "[Feature] Components V2 (#3065)" (#3100) (#3104)

This reverts commit d27bb49b2d.
This commit is contained in:
Mihail Gribkov
2025-04-27 00:31:08 +03:00
committed by GitHub
parent 25230f31cd
commit 8686102d87
102 changed files with 2852 additions and 692 deletions

View File

@@ -132,7 +132,7 @@ namespace Discord
/// A task that represents an asynchronous send operation for delivering the message.
/// </returns>
Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null,
MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Responds to this interaction with a file attachment.
@@ -154,7 +154,8 @@ namespace Discord
/// </returns>
#if NETCOREAPP3_0_OR_GREATER
async Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
using (var file = new FileAttachment(fileStream, fileName))
{
@@ -163,7 +164,8 @@ namespace Discord
}
#else
Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null
, MessageFlags flags = MessageFlags.None);
#endif
/// <summary>
/// Responds to this interaction with a file attachment.
@@ -185,7 +187,8 @@ namespace Discord
/// </returns>
#if NETCOREAPP3_0_OR_GREATER
async Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
using (var file = new FileAttachment(filePath, fileName))
{
@@ -194,7 +197,8 @@ namespace Discord
}
#else
Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null,
MessageFlags flags = MessageFlags.None);
#endif
/// <summary>
/// Responds to this interaction with a file attachment.
@@ -215,11 +219,13 @@ namespace Discord
/// </returns>
#if NETCOREAPP3_0_OR_GREATER
Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> RespondWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
=> RespondWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
#else
Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null,
MessageFlags flags = MessageFlags.None);
#endif
/// <summary>
/// Responds to this interaction with a collection of file attachments.
@@ -239,7 +245,9 @@ namespace Discord
/// contains the sent message.
/// </returns>
Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null,
MessageFlags flags = MessageFlags.None);
/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
@@ -257,7 +265,9 @@ namespace Discord
/// contains the sent message.
/// </returns>
Task<IUserMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null,
MessageFlags flags = MessageFlags.None);
/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
@@ -278,17 +288,19 @@ namespace Discord
/// </returns>
#if NETCOREAPP3_0_OR_GREATER
async Task<IUserMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
{
using (var file = new FileAttachment(fileStream, fileName))
{
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
}
#else
Task<IUserMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null,
MessageFlags flags = MessageFlags.None);
#endif
/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
@@ -309,16 +321,17 @@ namespace Discord
/// </returns>
#if NETCOREAPP3_0_OR_GREATER
async Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
using (var file = new FileAttachment(filePath, fileName))
{
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
}
#else
Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
#endif
/// <summary>
/// Sends a followup message for this interaction.
@@ -339,11 +352,11 @@ namespace Discord
/// </returns>
#if NETCOREAPP3_0_OR_GREATER
Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
#else
Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
#endif
/// <summary>
/// Sends a followup message for this interaction.
@@ -363,7 +376,7 @@ namespace Discord
/// contains the sent message.
/// </returns>
Task<IUserMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Gets the original response for this interaction.
/// </summary>

View File

@@ -1,27 +1,27 @@
using System.Collections.Generic;
namespace Discord
namespace Discord;
/// <summary>
/// Represents a <see cref="IMessageComponent"/> Row for child components to live in.
/// </summary>
public class ActionRowComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.ActionRow;
/// <inheritdoc/>
public int? Id { get; internal set; }
/// <summary>
/// Represents a <see cref="IMessageComponent"/> Row for child components to live in.
/// Gets the child components in this row.
/// </summary>
public class ActionRowComponent : IMessageComponent
public IReadOnlyCollection<IMessageComponent> Components { get; internal set; }
internal ActionRowComponent() { }
internal ActionRowComponent(IReadOnlyCollection<IMessageComponent> components)
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.ActionRow;
/// <summary>
/// Gets the child components in this row.
/// </summary>
public IReadOnlyCollection<IMessageComponent> Components { get; internal set; }
internal ActionRowComponent() { }
internal ActionRowComponent(List<IMessageComponent> components)
{
Components = components;
}
string IMessageComponent.CustomId => null;
Components = components;
}
}

View File

@@ -8,8 +8,12 @@ namespace Discord;
/// <summary>
/// Represents a class used to build Action rows.
/// </summary>
public class ActionRowBuilder
public class ActionRowBuilder : IMessageComponentBuilder, IInteractableComponentContainer
{
public ComponentType Type => ComponentType.ActionRow;
public int? Id { get; set; }
/// <summary>
/// The max amount of child components this row can hold.
/// </summary>
@@ -20,7 +24,7 @@ public class ActionRowBuilder
/// </summary>
/// <exception cref="ArgumentNullException" accessor="set"><see cref="Components"/> cannot be null.</exception>
/// <exception cref="ArgumentException" accessor="set"><see cref="Components"/> count exceeds <see cref="MaxChildCount"/>.</exception>
public List<IMessageComponent> Components
public List<IMessageComponentBuilder> Components
{
get => _components;
set
@@ -37,7 +41,21 @@ public class ActionRowBuilder
}
}
private List<IMessageComponent> _components = new List<IMessageComponent>();
public ActionRowBuilder AddComponents(params IMessageComponentBuilder[] components)
{
foreach (var component in components)
AddComponent(component);
return this;
}
public ActionRowBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components)
{
Components = components.ToList();
return this;
}
private List<IMessageComponentBuilder> _components = new ();
/// <summary>
/// Adds a list of components to the current row.
@@ -45,7 +63,7 @@ public class ActionRowBuilder
/// <param name="components">The list of components to add.</param>
/// <inheritdoc cref="Components"/>
/// <returns>The current builder.</returns>
public ActionRowBuilder WithComponents(List<IMessageComponent> components)
public ActionRowBuilder WithComponents(List<IMessageComponentBuilder> components)
{
Components = components;
return this;
@@ -57,7 +75,7 @@ public class ActionRowBuilder
/// <param name="component">The component to add.</param>
/// <exception cref="InvalidOperationException">Components count reached <see cref="MaxChildCount"/></exception>
/// <returns>The current builder.</returns>
public ActionRowBuilder AddComponent(IMessageComponent component)
public ActionRowBuilder AddComponent(IMessageComponentBuilder component)
{
if (Components.Count >= MaxChildCount)
throw new InvalidOperationException($"Components count reached {MaxChildCount}");
@@ -103,13 +121,11 @@ public class ActionRowBuilder
{
if (menu.Options is not null && menu.Options.Distinct().Count() != menu.Options.Count)
throw new InvalidOperationException("Please make sure that there is no duplicates values.");
var builtMenu = menu.Build();
if (Components.Count != 0)
throw new InvalidOperationException($"A Select Menu cannot exist in a pre-occupied ActionRow.");
AddComponent(builtMenu);
AddComponent(menu);
return this;
}
@@ -152,15 +168,13 @@ public class ActionRowBuilder
/// <returns>The current builder.</returns>
public ActionRowBuilder WithButton(ButtonBuilder button)
{
var builtButton = button.Build();
if (Components.Count >= 5)
throw new InvalidOperationException($"Components count reached {MaxChildCount}");
if (Components.Any(x => x.Type.IsSelectType()))
throw new InvalidOperationException($"A button cannot be added to a row with a SelectMenu");
AddComponent(builtButton);
AddComponent(button);
return this;
}
@@ -171,10 +185,11 @@ public class ActionRowBuilder
/// <returns>A <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/></returns>
public ActionRowComponent Build()
{
return new ActionRowComponent(_components);
return new ActionRowComponent(_components.Select(x => x.Build()).ToList());
}
IMessageComponent IMessageComponentBuilder.Build() => Build();
internal bool CanTakeComponent(IMessageComponent component)
internal bool CanTakeComponent(IMessageComponentBuilder component)
{
switch (component.Type)
{
@@ -195,4 +210,11 @@ public class ActionRowBuilder
return false;
}
}
IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component);
IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components);
IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components);
}

View File

@@ -7,8 +7,10 @@ namespace Discord;
/// <summary>
/// Represents a class used to build <see cref="ButtonComponent"/>'s.
/// </summary>
public class ButtonBuilder
public class ButtonBuilder : IInteractableComponentBuilder
{
public ComponentType Type => ComponentType.Button;
/// <summary>
/// The max length of a <see cref="ButtonComponent.Label"/>.
/// </summary>
@@ -82,6 +84,8 @@ public class ButtonBuilder
/// </remarks>
public ulong? SkuId { get; set; }
public int? Id { get; set; }
private string _label;
private string _customId;
@@ -100,7 +104,7 @@ public class ButtonBuilder
/// <param name="emote">The emote of this button.</param>
/// <param name="isDisabled">Disabled this button or not.</param>
/// <param name="skuId">The sku id of this button.</param>
public ButtonBuilder(string label = null, string customId = null, ButtonStyle style = ButtonStyle.Primary, string url = null, IEmote emote = null, bool isDisabled = false, ulong? skuId = null)
public ButtonBuilder(string label = null, string customId = null, ButtonStyle style = ButtonStyle.Primary, string url = null, IEmote emote = null, bool isDisabled = false, ulong? skuId = null, int? id = null)
{
CustomId = customId;
Style = style;
@@ -109,6 +113,7 @@ public class ButtonBuilder
IsDisabled = isDisabled;
Emote = emote;
SkuId = skuId;
Id = id;
}
/// <summary>
@@ -123,6 +128,7 @@ public class ButtonBuilder
IsDisabled = button.IsDisabled;
Emote = button.Emote;
SkuId = button.SkuId;
Id = button.Id;
}
/// <summary>
@@ -323,6 +329,8 @@ public class ButtonBuilder
break;
}
return new ButtonComponent(Style, Label, Emote, CustomId, Url, IsDisabled, SkuId);
return new ButtonComponent(Style, Label, Emote, CustomId, Url, IsDisabled, SkuId, Id);
}
IMessageComponent IMessageComponentBuilder.Build() => Build();
}

View File

@@ -98,7 +98,7 @@ public class ComponentBuilder
/// <returns>The current builder.</returns>
public ComponentBuilder RemoveComponent(string customId)
{
this.ActionRows.ForEach(ar => ar.Components.RemoveAll(c => c.CustomId == customId));
this.ActionRows.ForEach(ar => ar.Components.RemoveAll(c => c is IInteractableComponent i && i.CustomId == customId));
return this;
}
@@ -158,20 +158,15 @@ public class ComponentBuilder
Preconditions.LessThan(row, MaxActionRowCount, nameof(row));
if (menu.Options is not null && menu.Options.Distinct().Count() != menu.Options.Count)
throw new InvalidOperationException("Please make sure that there is no duplicates values.");
var builtMenu = menu.Build();
if (_actionRows == null)
{
_actionRows = new List<ActionRowBuilder>
{
new ActionRowBuilder().AddComponent(builtMenu)
};
_actionRows = [new ActionRowBuilder().AddComponent(menu)];
}
else
{
if (_actionRows.Count == row)
_actionRows.Add(new ActionRowBuilder().AddComponent(builtMenu));
_actionRows.Add(new ActionRowBuilder().AddComponent(menu));
else
{
ActionRowBuilder actionRow;
@@ -183,12 +178,12 @@ public class ComponentBuilder
_actionRows.Add(actionRow);
}
if (actionRow.CanTakeComponent(builtMenu))
actionRow.AddComponent(builtMenu);
if (actionRow.CanTakeComponent(menu))
actionRow.AddComponent(menu);
else if (row < MaxActionRowCount)
WithSelectMenu(menu, row + 1);
else
throw new InvalidOperationException($"There is no more row to add a {nameof(builtMenu)}");
throw new InvalidOperationException($"There is no more row to add a {nameof(menu)}");
}
}
@@ -243,19 +238,17 @@ public class ComponentBuilder
{
Preconditions.LessThan(row, MaxActionRowCount, nameof(row));
var builtButton = button.Build();
if (_actionRows == null)
{
_actionRows = new List<ActionRowBuilder>
{
new ActionRowBuilder().AddComponent(builtButton)
new ActionRowBuilder().AddComponent(button)
};
}
else
{
if (_actionRows.Count == row)
_actionRows.Add(new ActionRowBuilder().AddComponent(builtButton));
_actionRows.Add(new ActionRowBuilder().AddComponent(button));
else
{
ActionRowBuilder actionRow;
@@ -267,8 +260,8 @@ public class ComponentBuilder
_actionRows.Add(actionRow);
}
if (actionRow.CanTakeComponent(builtButton))
actionRow.AddComponent(builtButton);
if (actionRow.CanTakeComponent(button))
actionRow.AddComponent(button);
else if (row < MaxActionRowCount)
WithButton(button, row + 1);
else
@@ -326,7 +319,7 @@ public class ComponentBuilder
_actionRows.RemoveAt(i);
return _actionRows != null
? new MessageComponent(_actionRows.Select(x => x.Build()).ToList())
? new MessageComponent(_actionRows.Select(x => x.Build()).OfType<IMessageComponent>().ToList())
: MessageComponent.Empty;
}
}

View File

@@ -0,0 +1,17 @@
namespace Discord;
public static class ComponentBuilderExtensions
{
/// <summary>
/// Sets the custom id for the component.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public static BuilderT WithId<BuilderT>(this BuilderT builder, int? id)
where BuilderT : IMessageComponentBuilder
{
builder.Id = id;
return builder;
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Discord;
public class ComponentBuilderV2 : IStaticComponentContainer
{
/// <summary>
/// Gets the maximum number of components that can be added to this container.
/// </summary>
public const int MaxComponents = 10;
private List<IMessageComponentBuilder> _components = new();
/// <inheritdoc/>
public List<IMessageComponentBuilder> Components
{
get => _components;
set
{
_components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null.");
}
}
/// <summary>
/// Initializes a new instance of <see cref="ComponentBuilderV2"/>.
/// </summary>
public ComponentBuilderV2() { }
/// <inheritdoc cref="IComponentContainer.AddComponent"/>
public ComponentBuilderV2 AddComponent(IMessageComponentBuilder component)
{
Components.Add(component);
return this;
}
/// <inheritdoc cref="IComponentContainer.AddComponents"/>
public ComponentBuilderV2 AddComponents(params IMessageComponentBuilder[] components)
{
foreach (var component in components)
Components.Add(component);
return this;
}
/// <inheritdoc cref="IComponentContainer.WithComponents"/>
public ComponentBuilderV2 WithComponents(IEnumerable<IMessageComponentBuilder> components)
{
Components = components.ToList();
return this;
}
/// <inheritdoc cref="IMessageComponentBuilder.Build" />
public MessageComponent Build()
{
if (_components.Count is 0 or >MaxComponents)
throw new InvalidOperationException($"The number of components must be between 1 and {MaxComponents}.");
if (_components.Any(x =>
x is not ActionRowBuilder
and not SectionBuilder
and not TextDisplayBuilder
and not MediaGalleryBuilder
and not FileComponentBuilder
and not SeparatorBuilder
and not ContainerBuilder))
throw new InvalidOperationException($"Only the following components can be at the top level: {nameof(ActionRowBuilder)}, {nameof(TextDisplayBuilder)}, {nameof(SectionBuilder)}, {nameof(MediaGalleryBuilder)}, {nameof(SeparatorBuilder)}, or {nameof(FileComponentBuilder)} components.");
return new MessageComponent(Components.Select(x => x.Build()).ToList());
}
/// <inheritdoc/>
IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component);
/// <inheritdoc/>
IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components);
/// <inheritdoc/>
IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components);
}

View File

@@ -0,0 +1,315 @@
using System.Collections.Generic;
using System.Linq;
namespace Discord;
public static class ComponentContainerExtensions
{
/// <summary>
/// Adds a <see cref="TextDisplayBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithTextDisplay<BuilderT>(this BuilderT container, TextDisplayBuilder textDisplay)
where BuilderT : class, IStaticComponentContainer
{
container.AddComponent(textDisplay);
return container;
}
/// <summary>
/// Adds a <see cref="TextDisplayBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithTextDisplay<BuilderT>(this BuilderT container,
string content,
int? id = null)
where BuilderT : class, IStaticComponentContainer
=> container.WithTextDisplay(new TextDisplayBuilder()
.WithContent(content)
.WithId(id));
/// <summary>
/// Adds a <see cref="SectionBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithSection<BuilderT>(this BuilderT container, SectionBuilder section)
where BuilderT : class, IStaticComponentContainer
{
container.AddComponent(section);
return container;
}
/// <summary>
/// Adds a <see cref="SectionBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithSection<BuilderT>(this BuilderT container,
IEnumerable<TextDisplayBuilder> components,
IMessageComponentBuilder accessory,
bool isSpoiler = false,
int? id = null)
where BuilderT : class, IStaticComponentContainer
=> container.WithSection(new SectionBuilder()
.WithComponents(components)
.WithAccessory(accessory)
.WithId(id));
/// <summary>
/// Adds a <see cref="MediaGalleryBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, MediaGalleryBuilder mediaGallery)
where BuilderT : class, IStaticComponentContainer
{
container.AddComponent(mediaGallery);
return container;
}
/// <summary>
/// Adds a <see cref="MediaGalleryBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container,
IEnumerable<MediaGalleryItemProperties> items,
int? id = null) where BuilderT : class, IStaticComponentContainer
=> container.WithMediaGallery(new MediaGalleryBuilder()
.WithItems(items)
.WithId(id));
/// <summary>
/// Adds a <see cref="MediaGalleryBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container,
IEnumerable<string> urls,
int? id = null)
where BuilderT : class, IStaticComponentContainer
=> container.WithMediaGallery(new MediaGalleryBuilder()
.WithItems(urls.Select(x => new MediaGalleryItemProperties(new UnfurledMediaItemProperties(x))))
.WithId(id));
/// <summary>
/// Adds a <see cref="SeparatorBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithSeparator<BuilderT>(this BuilderT container, SeparatorBuilder separator)
where BuilderT : class, IStaticComponentContainer
{
container.AddComponent(separator);
return container;
}
/// <summary>
/// Adds a <see cref="SeparatorBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithSeparator<BuilderT>(this BuilderT container,
SeparatorSpacingSize spacing = SeparatorSpacingSize.Small,
bool isDivider = true,
int? id = null)
where BuilderT : class, IStaticComponentContainer
=> container.WithSeparator(new SeparatorBuilder()
.WithSpacing(spacing)
.WithIsDivider(isDivider)
.WithId(id));
/// <summary>
/// Adds a <see cref="FileComponentBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithFile<BuilderT>(this BuilderT container, FileComponentBuilder file)
where BuilderT : class, IStaticComponentContainer
{
container.AddComponent(file);
return container;
}
/// <summary>
/// Adds a <see cref="FileComponentBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithFile<BuilderT>(this BuilderT container,
UnfurledMediaItemProperties file,
bool isSpoiler = false,
int? id = null)
where BuilderT : class, IStaticComponentContainer
=> container.WithFile(new FileComponentBuilder()
.WithFile(file)
.WithIsSpoiler(isSpoiler)
.WithId(id));
/// <summary>
/// Adds a <see cref="ContainerBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithContainer<BuilderT>(this BuilderT container, ContainerBuilder containerComponent)
where BuilderT : class, IStaticComponentContainer
{
container.AddComponent(containerComponent);
return container;
}
/// <summary>
/// Adds a <see cref="ContainerBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithContainer<BuilderT>(this BuilderT container,
IEnumerable<IMessageComponentBuilder> components,
Color? accentColor = null,
bool isSpoiler = false,
int? id = null)
where BuilderT : class, IStaticComponentContainer
=> container.WithContainer(new ContainerBuilder()
.WithComponents(components)
.WithAccentColor(accentColor)
.WithSpoiler(isSpoiler)
.WithId(id));
/// <summary>
/// Adds a <see cref="ContainerBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithContainer<BuilderT>(this BuilderT container,
params IMessageComponentBuilder[] components)
where BuilderT : class, IStaticComponentContainer
=> container.WithContainer(new ContainerBuilder()
.WithComponents(components));
/// <summary>
/// Adds a <see cref="ButtonBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithButton<BuilderT>(this BuilderT container, ButtonBuilder button)
where BuilderT : class, IInteractableComponentContainer
{
container.AddComponent(button);
return container;
}
/// <summary>
/// Adds a <see cref="ButtonBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithButton<BuilderT>(this BuilderT container,
string label = null,
string customId = null,
ButtonStyle style = ButtonStyle.Primary,
IEmote emote = null,
string url = null,
bool disabled = false,
ulong? skuId = null,
int? id = null)
where BuilderT : class, IInteractableComponentContainer
=> container.WithButton(new ButtonBuilder()
.WithLabel(label)
.WithStyle(style)
.WithEmote(emote)
.WithCustomId(customId)
.WithUrl(url)
.WithDisabled(disabled)
.WithSkuId(skuId)
.WithId(id));
/// <summary>
/// Adds a <see cref="SelectMenuBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithSelectMenu<BuilderT>(this BuilderT container, SelectMenuBuilder selectMenu)
where BuilderT : class, IInteractableComponentContainer
{
container.AddComponent(selectMenu);
return container;
}
/// <summary>
/// Adds a <see cref="SelectMenuBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithSelectMenu<BuilderT>(this BuilderT container,
string customId,
List<SelectMenuOptionBuilder> options = null,
string placeholder = null,
int minValues = 1,
int maxValues = 1,
bool disabled = false,
int row = 0,
ComponentType type = ComponentType.SelectMenu,
ChannelType[] channelTypes = null,
SelectMenuDefaultValue[] defaultValues = null,
int? id = null)
where BuilderT : class, IInteractableComponentContainer
=> container.WithSelectMenu(new SelectMenuBuilder()
.WithCustomId(customId)
.WithOptions(options)
.WithPlaceholder(placeholder)
.WithMaxValues(maxValues)
.WithMinValues(minValues)
.WithDisabled(disabled)
.WithType(type)
.WithChannelTypes(channelTypes)
.WithDefaultValues(defaultValues)
.WithId(id));
/// <summary>
/// Adds a <see cref="ActionRowBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithActionRow<BuilderT>(this BuilderT container, ActionRowBuilder actionRow)
where BuilderT : class, IStaticComponentContainer
{
container.AddComponent(actionRow);
return container;
}
/// <summary>
/// Adds a <see cref="ActionRowBuilder"/> to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
public static BuilderT WithActionRow<BuilderT>(this BuilderT container,
IEnumerable<IMessageComponentBuilder> components,
int? id = null)
where BuilderT : class, IStaticComponentContainer
=> container.WithActionRow(new ActionRowBuilder()
.WithComponents(components)
.WithId(id));
}

View File

@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Discord;
public class ContainerBuilder : IMessageComponentBuilder, IStaticComponentContainer
{
/// <summary>
/// The maximum number of components allowed in a container.
/// </summary>
public const int MaxComponents = 10;
/// <inheritdoc />
public ComponentType Type => ComponentType.Container;
/// <inheritdoc />
public int? Id { get; set; }
private List<IMessageComponentBuilder> _components = new();
/// <inheritdoc/>
public List<IMessageComponentBuilder> Components
{
get => _components;
set => _components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null.");
}
/// <summary>
/// Gets or sets the accent color of this container.
/// </summary>
public uint? AccentColor { get; set; }
/// <summary>
/// Gets or sets whether this container is a spoiler.
/// </summary>
public bool? IsSpoiler { get; set; }
/// <summary>
/// Sets the accent color of this container.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public ContainerBuilder WithAccentColor(Color? color)
{
AccentColor = color?.RawValue;
return this;
}
/// <summary>
/// Sets whether this container is a spoiler.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public ContainerBuilder WithSpoiler(bool isSpoiler)
{
IsSpoiler = isSpoiler;
return this;
}
/// <inheritdoc cref="IComponentContainer.AddComponent"/>
public ContainerBuilder AddComponent(IMessageComponentBuilder component)
{
Components.Add(component);
return this;
}
/// <inheritdoc cref="IComponentContainer.AddComponents"/>
public ContainerBuilder AddComponents(params IMessageComponentBuilder[] components)
{
foreach (var component in components)
Components.Add(component);
return this;
}
/// <inheritdoc cref="IComponentContainer.WithComponents"/>
public ContainerBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components)
{
Components = components.ToList();
return this;
}
/// <inheritdoc cref="IMessageComponentBuilder.Build"/>
public ContainerComponent Build()
{
if (_components.Count is 0 or > MaxComponents)
throw new InvalidOperationException($"A container must have between 1 and {MaxComponents} components.");
if (_components.Any(x => x
is not ActionRowBuilder
and not TextDisplayBuilder
and not SectionBuilder
and not MediaGalleryBuilder
and not SeparatorBuilder
and not FileComponentBuilder))
throw new InvalidOperationException($"A container can only contain {nameof(ActionRowBuilder)}, {nameof(TextDisplayBuilder)}, {nameof(SectionBuilder)}, {nameof(MediaGalleryBuilder)}, {nameof(SeparatorBuilder)}, or {nameof(FileComponentBuilder)} components.");
return new(Components.ConvertAll(x => x.Build()).ToImmutableArray(), AccentColor, IsSpoiler, Id);
}
/// <inheritdoc />
IMessageComponent IMessageComponentBuilder.Build() => Build();
/// <inheritdoc />
IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component);
/// <inheritdoc />
IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components);
/// <inheritdoc />
IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components);
}

View File

@@ -0,0 +1,79 @@
using System;
namespace Discord;
public class FileComponentBuilder : IMessageComponentBuilder
{
/// <inheritdoc />
public ComponentType Type => ComponentType.File;
/// <inheritdoc />
public int? Id { get; set; }
/// <summary>
/// Gets or sets the file for the component.
/// </summary>
/// <remarks>
/// Only attachment URLs are supported.
/// </remarks>
public UnfurledMediaItemProperties File { get; set; }
/// <summary>
/// Gets or sets whether the file is a spoiler.
/// </summary>
public bool? IsSpoiler { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="FileComponentBuilder"/>.
/// </summary>
public FileComponentBuilder() {}
/// <summary>
/// Initializes a new instance of the <see cref="FileComponentBuilder"/>.
/// </summary>
public FileComponentBuilder(UnfurledMediaItemProperties media, bool isSpoiler = false, int? id = null)
{
File = media;
Id = id;
IsSpoiler = isSpoiler;
}
/// <summary>
/// Sets the file for the component.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public FileComponentBuilder WithFile(UnfurledMediaItemProperties file)
{
File = file;
return this;
}
/// <summary>
/// Sets whether the file is a spoiler.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public FileComponentBuilder WithIsSpoiler(bool? isSpoiler)
{
IsSpoiler = isSpoiler;
return this;
}
/// <inheritdoc cref="IMessageComponentBuilder.Build" />
public FileComponent Build()
{
if (string.IsNullOrWhiteSpace(File.Url))
throw new InvalidOperationException("File URL must be set.");
if (!File.Url.StartsWith("attachment://"))
throw new InvalidOperationException("File URL must be an attachment URL.");
return new(new UnfurledMediaItem(File.Url), IsSpoiler, Id);
}
/// <inheritdoc />
IMessageComponent IMessageComponentBuilder.Build() => Build();
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
namespace Discord;
/// <summary>
/// Represents a container with child components.
/// </summary>
public interface IComponentContainer
{
/// <summary>
/// Gets the components in the container.
/// </summary>
List<IMessageComponentBuilder> Components { get; }
/// <summary>
/// Adds a component to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
IComponentContainer AddComponent(IMessageComponentBuilder component);
/// <summary>
/// Adds components to the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
IComponentContainer AddComponents(params IMessageComponentBuilder[] components);
/// <summary>
/// Sets the components in the container.
/// </summary>
/// <returns>
/// The current container.
/// </returns>
IComponentContainer WithComponents(IEnumerable<IMessageComponentBuilder> components);
}

View File

@@ -0,0 +1,12 @@
namespace Discord;
/// <summary>
/// Represents a builder for an interactable component.
/// </summary>
public interface IInteractableComponentBuilder : IMessageComponentBuilder
{
/// <summary>
/// Gets or sets the custom id for the component.
/// </summary>
string CustomId { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace Discord;
/// <summary>
/// Represents a container for interactable components.
/// </summary>
public interface IInteractableComponentContainer : IComponentContainer
{
}

View File

@@ -0,0 +1,19 @@
namespace Discord;
public interface IMessageComponentBuilder
{
/// <summary>
/// Gets the type of the component.
/// </summary>
ComponentType Type { get; }
/// <summary>
/// Gets or sets the id for the component. An autoincremented id will be assigned if not set.
/// </summary>
int? Id { get; set; }
/// <summary>
/// Runs validation checks and builds the component.
/// </summary>
IMessageComponent Build();
}

View File

@@ -0,0 +1,9 @@
namespace Discord;
/// <summary>
/// Represents a container for static components.
/// </summary>
public interface IStaticComponentContainer : IComponentContainer
{
}

View File

@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Discord;
public class MediaGalleryBuilder : IMessageComponentBuilder
{
/// <summary>
/// Gets the maximum number of items that can be added to a media gallery.
/// </summary>
public const int MaxItems = 10;
/// <inheritdoc/>
public ComponentType Type => ComponentType.MediaGallery;
/// <inheritdoc/>
public int? Id { get; set; }
private List<MediaGalleryItemProperties> _items = new();
/// <summary>
/// Initializes a new instance of the <see cref="MediaGalleryBuilder"/>.
/// </summary>
public MediaGalleryBuilder() { }
/// <summary>
/// Initializes a new instance of the <see cref="MediaGalleryBuilder"/>.
/// </summary>
public MediaGalleryBuilder(IEnumerable<MediaGalleryItemProperties> items, int? id = null)
{
Items = items.ToList();
Id = id;
}
/// <summary>
/// Gets or sets the items in this media gallery.
/// </summary>
public List<MediaGalleryItemProperties> Items
{
get => _items;
set => _items = value;
}
/// <summary>
/// Adds a new item to the media gallery.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public MediaGalleryBuilder AddItem(MediaGalleryItemProperties item)
{
_items.Add(item);
return this;
}
/// <summary>
/// Adds a new item to the media gallery.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public MediaGalleryBuilder AddItem(string url, string description = null, bool isSpoiler = false)
{
_items.Add(new MediaGalleryItemProperties(new UnfurledMediaItemProperties(url), description, isSpoiler));
return this;
}
/// <summary>
/// Adds a list of items to the media gallery.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public MediaGalleryBuilder AddItems(params IEnumerable<MediaGalleryItemProperties> items)
{
foreach (var item in items)
_items.Add(item);
return this;
}
/// <summary>
/// Sets the items in the media gallery.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public MediaGalleryBuilder WithItems(IEnumerable<MediaGalleryItemProperties> items)
{
_items = items.ToList();
return this;
}
/// <inheritdoc cref="IMessageComponentBuilder.Build"/>
public MediaGalleryComponent Build()
{
if (_items.Any(x => (x.Description?.Length ?? 0) > MediaGalleryItemProperties.MaxDescriptionLength))
throw new ArgumentException($"{nameof(MediaGalleryItemProperties)} description length cannot exceed {MediaGalleryItemProperties.MaxDescriptionLength} characters.");
if (_items.Any(x => !(x.Media.Url?.StartsWith("http://") ?? false)
&& !(x.Media.Url?.StartsWith("https://") ?? false)
&& !(x.Media.Url?.StartsWith("attachment://") ?? false)))
throw new ArgumentException($"{nameof(MediaGalleryItemProperties)} description must be a valid URL or attachment.");
if (_items.Count is 0 or > MaxItems)
throw new ArgumentOutOfRangeException(nameof(Items), $"Media gallery items count must be in range [1, {MaxItems}]");
return new(_items.Select(x => new MediaGalleryItem(new UnfurledMediaItem(x.Media.Url), x.Description, x.IsSpoiler)).ToImmutableArray(), Id);
}
/// <inheritdoc/>
IMessageComponent IMessageComponentBuilder.Build() => Build();
}

View File

@@ -0,0 +1,39 @@
namespace Discord;
public struct MediaGalleryItemProperties
{
/// <summary>
/// The maximum length of the description.
/// </summary>
public const int MaxDescriptionLength = 256;
/// <summary>
/// Gets or sets the media item to display.
/// </summary>
public UnfurledMediaItemProperties Media { get; set; }
/// <summary>
/// Gets or sets the description of the media item.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets whether the media item is a spoiler.
/// </summary>
public bool IsSpoiler { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MediaGalleryItemProperties"/>.
/// </summary>
public MediaGalleryItemProperties() { }
/// <summary>
/// Initializes a new instance of the <see cref="MediaGalleryItemProperties"/>.
/// </summary>
public MediaGalleryItemProperties(UnfurledMediaItemProperties media, string description = null, bool isSpoiler = false)
{
Media = media;
Description = description;
IsSpoiler = isSpoiler;
}
}

View File

@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Discord;
public class SectionBuilder : IMessageComponentBuilder, IStaticComponentContainer
{
/// <summary>
/// Gets the maximum number of components allowed in this container.
/// </summary>
public const int MaxComponents = 3;
/// <inheritdoc/>
public ComponentType Type => ComponentType.Section;
/// <inheritdoc/>
public int? Id { get; set; }
/// <summary>
/// Gets or sets the accessory component.
/// </summary>
/// <remarks>
/// Only supports <see cref="ButtonBuilder"/> and <see cref="ThumbnailBuilder"/> currently.
/// </remarks>
public IMessageComponentBuilder Accessory { get; set; }
private List<IMessageComponentBuilder> _components = new();
/// <inheritdoc/>
/// <remarks>
/// Only <see cref="TextDisplayBuilder"/> is supported.
/// </remarks>
public List<IMessageComponentBuilder> Components
{
get => _components;
set => _components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null.");
}
/// <inheritdoc cref="IComponentContainer.AddComponent"/>
/// <remarks>
/// Only <see cref="TextDisplayBuilder"/> is supported.
/// </remarks>
public SectionBuilder AddComponent(IMessageComponentBuilder component)
{
Components.Add(component);
return this;
}
/// <inheritdoc cref="IComponentContainer.AddComponents"/>
/// <remarks>
/// Only <see cref="TextDisplayBuilder"/> is supported.
/// </remarks>
public SectionBuilder AddComponents(params IMessageComponentBuilder[] components)
{
foreach (var component in components)
AddComponent(component);
return this;
}
/// <inheritdoc cref="IComponentContainer.WithComponents"/>
/// <remarks>
/// Only <see cref="TextDisplayBuilder"/> is supported.
/// </remarks>
public SectionBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components)
{
Components = components.ToList();
return this;
}
/// <summary>
/// Sets the accessory component.
/// </summary>
public SectionBuilder WithAccessory(IMessageComponentBuilder accessory)
{
Accessory = accessory;
return this;
}
/// <inheritdoc cref="IMessageComponentBuilder.Build"/>
public SectionComponent Build()
{
if (_components.Count is 0 or > MaxComponents)
throw new InvalidOperationException($"Section component can only contain {MaxComponents} child components!");
if (_components.Any(x => x is not TextDisplayBuilder))
throw new InvalidOperationException($"Section component can only contain {nameof(TextDisplayBuilder)}!");
if (Accessory is null)
throw new ArgumentNullException(nameof(Accessory), "A section must have an accessory");
if (Accessory is not ButtonBuilder and not ThumbnailBuilder)
throw new InvalidOperationException($"Accessory component can only be {nameof(ButtonBuilder)} or {nameof(ThumbnailBuilder)}!");
return new(Id, Components.Select(x => x.Build()).ToImmutableArray(), Accessory?.Build());
}
/// <inheritdoc/>
IMessageComponent IMessageComponentBuilder.Build() => Build();
/// <inheritdoc/>
IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component);
/// <inheritdoc/>
IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components);
/// <inheritdoc/>
IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components.ToList());
}

View File

@@ -8,7 +8,7 @@ namespace Discord;
/// <summary>
/// Represents a class used to build <see cref="SelectMenuComponent"/>'s.
/// </summary>
public class SelectMenuBuilder
public class SelectMenuBuilder : IInteractableComponentBuilder
{
/// <summary>
/// The max length of a <see cref="SelectMenuComponent.Placeholder"/>.
@@ -143,13 +143,16 @@ public class SelectMenuBuilder
}
}
private List<SelectMenuOptionBuilder> _options = new List<SelectMenuOptionBuilder>();
/// <inheritdoc/>
public int? Id { get; set; }
private List<SelectMenuOptionBuilder> _options = [];
private int _minValues = 1;
private int _maxValues = 1;
private string _placeholder;
private string _customId;
private ComponentType _type = ComponentType.SelectMenu;
private List<SelectMenuDefaultValue> _defaultValues = new();
private List<SelectMenuDefaultValue> _defaultValues = [];
/// <summary>
/// Creates a new instance of a <see cref="SelectMenuBuilder"/>.
@@ -170,6 +173,7 @@ public class SelectMenuBuilder
.Select(x => new SelectMenuOptionBuilder(x.Label, x.Value, x.Description, x.Emote, x.IsDefault))
.ToList();
DefaultValues = selectMenu.DefaultValues?.ToList();
Id = selectMenu.Id;
}
/// <summary>
@@ -184,7 +188,7 @@ public class SelectMenuBuilder
/// <param name="type">The <see cref="ComponentType"/> of this select menu.</param>
/// <param name="channelTypes">The types of channels this menu can select (only valid on <see cref="ComponentType.ChannelSelect"/>s)</param>
public SelectMenuBuilder(string customId, List<SelectMenuOptionBuilder> options = null, string placeholder = null, int maxValues = 1, int minValues = 1,
bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List<ChannelType> channelTypes = null, List<SelectMenuDefaultValue> defaultValues = null)
bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List<ChannelType> channelTypes = null, List<SelectMenuDefaultValue> defaultValues = null, int? id = null)
{
CustomId = customId;
Options = options;
@@ -195,6 +199,7 @@ public class SelectMenuBuilder
Type = type;
ChannelTypes = channelTypes ?? new();
DefaultValues = defaultValues ?? new();
Id = id;
}
/// <summary>
@@ -408,6 +413,9 @@ public class SelectMenuBuilder
{
var options = Options?.Select(x => x.Build()).ToList();
return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled, Type, ChannelTypes, DefaultValues);
return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled, Type, Id, ChannelTypes, DefaultValues);
}
/// <inheritdoc/>
IMessageComponent IMessageComponentBuilder.Build() => Build();
}

View File

@@ -0,0 +1,53 @@
namespace Discord;
public class SeparatorBuilder : IMessageComponentBuilder
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.Separator;
/// <summary>
/// Gets or sets whether the component is a divider.
/// </summary>
public bool? IsDivider { get; set; }
/// <summary>
/// Gets or sets the spacing of the separator.
/// </summary>
public SeparatorSpacingSize? Spacing { get; set; }
/// <inheritdoc/>
public int? Id { get; set; }
/// <summary>
/// Sets whether the component is a divider.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public SeparatorBuilder WithIsDivider(bool? isDivider)
{
IsDivider = isDivider;
return this;
}
/// <summary>
/// Sets the spacing of the separator.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public SeparatorBuilder WithSpacing(SeparatorSpacingSize? spacing)
{
Spacing = spacing;
return this;
}
/// <inheritdoc cref="IMessageComponentBuilder.Build"/>
public SeparatorComponent Build()
{
return new(IsDivider, Spacing, Id);
}
/// <inheritdoc/>
IMessageComponent IMessageComponentBuilder.Build() => Build();
}

View File

@@ -0,0 +1,60 @@
using System;
namespace Discord;
public class TextDisplayBuilder : IMessageComponentBuilder
{
/// <summary>
/// The maximum length of the content.
/// </summary>
public const int MaxContentLength = 4096;
/// <inheritdoc/>
public ComponentType Type => ComponentType.ActionRow;
/// <inheritdoc/>
public int? Id { get; set; }
/// <summary>
/// Gets or sets the content of the text display.
/// </summary>
public string Content { get; set; }
/// <summary>
/// Initializes a new <see cref="TextDisplayBuilder"/>.
/// </summary>
public TextDisplayBuilder() { }
/// <summary>
/// Initializes a new <see cref="TextDisplayBuilder"/> with the specified content.
/// </summary>
public TextDisplayBuilder(string content, int? id = null)
{
Content = content;
Id = id;
}
/// <summary>
/// Sets the content of the text display.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public TextDisplayBuilder WithContent(string content)
{
Content = content;
return this;
}
/// <inheritdoc cref="IMessageComponentBuilder.Build"/>
public TextDisplayComponent Build()
{
if (Content.Length > MaxContentLength)
throw new ArgumentException($"Content length must be less than or equal to {MaxContentLength}.", nameof(Content));
return new(Content, Id);
}
/// <inheritdoc/>
IMessageComponent IMessageComponentBuilder.Build() => Build();
}

View File

@@ -6,8 +6,10 @@ namespace Discord;
/// <summary>
/// Represents a builder for creating a <see cref="TextInputComponent"/>.
/// </summary>
public class TextInputBuilder
public class TextInputBuilder : IInteractableComponentBuilder
{
public ComponentType Type => ComponentType.TextInput;
/// <summary>
/// The max length of a <see cref="TextInputComponent.Placeholder"/>.
/// </summary>
@@ -103,6 +105,8 @@ public class TextInputBuilder
/// </summary>
public bool? Required { get; set; }
public int? Id { get; set; }
/// <summary>
/// Gets or sets the default value of the text input.
/// </summary>
@@ -144,7 +148,7 @@ public class TextInputBuilder
/// <param name="maxLength">The text input's maximum length.</param>
/// <param name="required">The text input's required value.</param>
public TextInputBuilder(string label, string customId, TextInputStyle style = TextInputStyle.Short, string placeholder = null,
int? minLength = null, int? maxLength = null, bool? required = null, string value = null)
int? minLength = null, int? maxLength = null, bool? required = null, string value = null, int? id = null)
{
Label = label;
Style = style;
@@ -154,6 +158,7 @@ public class TextInputBuilder
MaxLength = maxLength;
Required = required;
Value = value;
Id = id;
}
/// <summary>
@@ -261,6 +266,8 @@ public class TextInputBuilder
if (Style is TextInputStyle.Short && Value?.Any(x => x == '\n') is true)
throw new ArgumentException($"Value must not contain new line characters when style is {TextInputStyle.Short}.", nameof(Value));
return new TextInputComponent(CustomId, Label, Placeholder, MinLength, MaxLength, Style, Required, Value);
return new TextInputComponent(CustomId, Label, Placeholder, MinLength, MaxLength, Style, Required, Value, Id);
}
IMessageComponent IMessageComponentBuilder.Build() => Build();
}

View File

@@ -0,0 +1,95 @@
using System;
namespace Discord;
public class ThumbnailBuilder : IMessageComponentBuilder
{
/// <summary>
/// Gets the maximum length of the description.
/// </summary>
public const int MaxDescriptionLength = 1024;
/// <inheritdoc/>
public ComponentType Type => ComponentType.Thumbnail;
/// <inheritdoc/>
public int? Id { get; set; }
/// <summary>
/// Gets or sets the media of the thumbnail.
/// </summary>
public UnfurledMediaItemProperties Media { get; set; }
/// <summary>
/// Gets or sets the description of the thumbnail.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets whether the thumbnail is a spoiler.
/// </summary>
public bool IsSpoiler { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ThumbnailBuilder"/>.
/// </summary>
public ThumbnailBuilder() { }
/// <summary>
/// Initializes a new instance of the <see cref="ThumbnailBuilder"/>.
/// </summary>
public ThumbnailBuilder(UnfurledMediaItemProperties media, string description = null, bool isSpoiler = false)
{
Media = media;
Description = description;
IsSpoiler = isSpoiler;
}
/// <summary>
/// Sets the media of the thumbnail.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public ThumbnailBuilder WithMedia(UnfurledMediaItemProperties media)
{
Media = media;
return this;
}
/// <summary>
/// Sets the description of the thumbnail.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public ThumbnailBuilder WithDescription(string description)
{
Description = description;
return this;
}
/// <summary>
/// Sets whether the thumbnail is a spoiler.
/// </summary>
/// <returns>
/// The current builder.
/// </returns>
public ThumbnailBuilder WithSpoiler(bool isSpoiler)
{
IsSpoiler = isSpoiler;
return this;
}
/// <inheritdoc cref="IMessageComponentBuilder.Build"/>
public ThumbnailComponent Build()
{
if (Description is not null && Description.Length > MaxDescriptionLength)
throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description));
return new(Id, new UnfurledMediaItem(Media.Url), Description, IsSpoiler);
}
/// <inheritdoc/>
IMessageComponent IMessageComponentBuilder.Build() => Build();
}

View File

@@ -0,0 +1,24 @@
namespace Discord;
public struct UnfurledMediaItemProperties
{
/// <summary>
/// Gets or sets the URL of the media item.
/// </summary>
public string Url { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="UnfurledMediaItemProperties"/>.
/// </summary>
public UnfurledMediaItemProperties() {}
/// <summary>
/// Initializes a new instance of the <see cref="UnfurledMediaItemProperties"/>.
/// </summary>
public UnfurledMediaItemProperties(string url)
{
Url = url;
}
public static implicit operator UnfurledMediaItemProperties(string url) => new(url);
}

View File

@@ -3,11 +3,14 @@ namespace Discord;
/// <summary>
/// Represents a <see cref="IMessageComponent"/> Button.
/// </summary>
public class ButtonComponent : IMessageComponent
public class ButtonComponent : IInteractableComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.Button;
/// <inheritdoc/>
public int? Id { get; }
/// <summary>
/// Gets the <see cref="ButtonStyle"/> of this button, example buttons with each style can be found <see href="https://discord.com/assets/7bb017ce52cfd6575e21c058feb3883b.png">Here</see>.
/// </summary>
@@ -56,9 +59,10 @@ public class ButtonComponent : IMessageComponent
public ButtonBuilder ToBuilder()
=> new (Label, CustomId, Style, Url, Emote, IsDisabled);
internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool isDisabled, ulong? skuId)
internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool isDisabled, ulong? skuId, int? id)
{
Style = style;
Id = id;
Label = label;
Emote = emote;
CustomId = customId;

View File

@@ -44,5 +44,19 @@ namespace Discord
/// A select menu for picking from channels.
/// </summary>
ChannelSelect = 8,
Section = 9,
TextDisplay = 10,
Thumbnail = 11,
MediaGallery = 12,
File = 13,
Separator = 14,
Container = 17,
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
namespace Discord;
/// <summary>
/// Represents a container component.
/// </summary>
public class ContainerComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.Container;
/// <inheritdoc/>
public int? Id { get; }
/// <summary>
/// Gets the components in this container.
/// </summary>
public IReadOnlyCollection<IMessageComponent> Components { get; }
/// <summary>
/// Gets the accent color of this container.
/// </summary>
public uint? AccentColor { get; }
/// <summary>
/// Gets whether this container is a spoiler.
/// </summary>
public bool? IsSpoiler { get; }
internal ContainerComponent(IReadOnlyCollection<IMessageComponent> components, uint? accentColor, bool? isSpoiler, int? id = null)
{
Components = components;
AccentColor = accentColor;
IsSpoiler = isSpoiler;
Id = id;
}
}

View File

@@ -0,0 +1,30 @@
namespace Discord;
/// <summary>
/// Represents a file component.
/// </summary>
public class FileComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.File;
/// <inheritdoc/>
public int? Id { get; }
/// <summary>
/// Gets the file of this component.
/// </summary>
public UnfurledMediaItem File { get; }
/// <summary>
/// Gets whether this file is a spoiler.
/// </summary>
public bool? IsSpoiler { get; }
internal FileComponent(UnfurledMediaItem file, bool? isSpoiler, int? id = null)
{
File = file;
IsSpoiler = isSpoiler;
Id = id;
}
}

View File

@@ -0,0 +1,12 @@
namespace Discord;
/// <summary>
/// Represents a message component that can be interacted with.
/// </summary>
public interface IInteractableComponent : IMessageComponent
{
/// <summary>
/// Gets the custom id of the component if possible; otherwise <see langword="null"/>.
/// </summary>
string CustomId { get; }
}

View File

@@ -1,18 +1,14 @@
namespace Discord
namespace Discord;
/// <summary>
/// Represents a message component on a message.
/// </summary>
public interface IMessageComponent
{
/// <summary>
/// Represents a message component on a message.
/// Gets the <see cref="ComponentType"/> of this Message Component.
/// </summary>
public interface IMessageComponent
{
/// <summary>
/// Gets the <see cref="ComponentType"/> of this Message Component.
/// </summary>
ComponentType Type { get; }
ComponentType Type { get; }
/// <summary>
/// Gets the custom id of the component if possible; otherwise <see langword="null"/>.
/// </summary>
string CustomId { get; }
}
int? Id { get; }
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace Discord;
/// <summary>
/// Represents a media gallery component.
/// </summary>
public class MediaGalleryComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.MediaGallery;
/// <inheritdoc/>
public int? Id { get; }
/// <summary>
/// Gets the items in this media gallery.
/// </summary>
public IReadOnlyCollection<MediaGalleryItem> Items { get; }
internal MediaGalleryComponent(IReadOnlyCollection<MediaGalleryItem> items, int? id)
{
Items = items;
Id = id;
}
}

View File

@@ -0,0 +1,29 @@
namespace Discord;
/// <summary>
/// Represents a media gallery item.
/// </summary>
public readonly struct MediaGalleryItem
{
/// <summary>
/// Gets the media for this item.
/// </summary>
public UnfurledMediaItem Media { get; }
/// <summary>
/// Gets the description for this item.
/// </summary>
public string Description { get; }
/// <summary>
/// Gets whether this item is a spoiler.
/// </summary>
public bool IsSpoiler { get; }
internal MediaGalleryItem(UnfurledMediaItem media, string description, bool? isSpoiler)
{
Media = media;
Description = description;
IsSpoiler = isSpoiler ?? false;
}
}

View File

@@ -1,26 +1,25 @@
using System.Collections.Generic;
namespace Discord
namespace Discord;
/// <summary>
/// Represents a component object used to send components with messages.
/// </summary>
public class MessageComponent
{
/// <summary>
/// Represents a component object used to send components with messages.
/// Gets the components to be used in a message.
/// </summary>
public class MessageComponent
public IReadOnlyCollection<IMessageComponent> Components { get; }
internal MessageComponent(List<IMessageComponent> components)
{
/// <summary>
/// Gets the components to be used in a message.
/// </summary>
public IReadOnlyCollection<ActionRowComponent> Components { get; }
internal MessageComponent(List<ActionRowComponent> components)
{
Components = components;
}
/// <summary>
/// Returns a empty <see cref="MessageComponent"/>.
/// </summary>
internal static MessageComponent Empty
=> new MessageComponent(new List<ActionRowComponent>());
Components = components;
}
/// <summary>
/// Returns a empty <see cref="MessageComponent"/>.
/// </summary>
internal static MessageComponent Empty
=> new([]);
}

View File

@@ -0,0 +1,41 @@
namespace Discord;
/// <summary>
/// Represents a media item that has been unfurled and resolved.
/// </summary>
public class ResolvedUnfurledMediaItem : UnfurledMediaItem
{
/// <summary>
/// Gets the proxy URL for this media item.
/// </summary>
public string ProxyUrl { get; }
/// <summary>
/// Gets the height of this media item.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the width of this media item.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the content type of this media item.
/// </summary>
public string ContentType { get;}
/// <summary>
/// Gets the loading state of this media item.
/// </summary>
public UnfurledMediaItemLoadingState LoadingState { get; }
internal ResolvedUnfurledMediaItem(string url, string proxyUrl, int height, int width, string contentType, UnfurledMediaItemLoadingState loadingState) : base(url)
{
ProxyUrl = proxyUrl;
Height = height;
Width = width;
ContentType = contentType;
LoadingState = loadingState;
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
namespace Discord;
/// <summary>
/// Represents a section component.
/// </summary>
public class SectionComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.Section;
/// <inheritdoc/>
public int? Id { get; }
/// <summary>
/// Gets the components in this section.
/// </summary>
public IReadOnlyCollection<IMessageComponent> Components { get; }
/// <summary>
/// Gets the accessory of this section.
/// </summary>
public IMessageComponent Accessory { get; }
internal SectionComponent(int? id, IReadOnlyCollection<IMessageComponent> components, IMessageComponent accessory)
{
Id = id;
Components = components;
Accessory = accessory;
}
}

View File

@@ -7,11 +7,14 @@ namespace Discord
/// <summary>
/// Represents a select menu component defined at <see href="https://discord.com/developers/docs/interactions/message-components#select-menu-object"/>
/// </summary>
public class SelectMenuComponent : IMessageComponent
public class SelectMenuComponent : IInteractableComponent
{
/// <inheritdoc/>
public ComponentType Type { get; }
/// <inheritdoc/>
public int? Id { get; }
/// <inheritdoc/>
public string CustomId { get; }
@@ -67,7 +70,7 @@ namespace Discord
DefaultValues.ToList());
internal SelectMenuComponent(string customId, List<SelectMenuOption> options, string placeholder, int minValues, int maxValues,
bool disabled, ComponentType type, IEnumerable<ChannelType> channelTypes = null, IEnumerable<SelectMenuDefaultValue> defaultValues = null)
bool disabled, ComponentType type, int? id, IEnumerable<ChannelType> channelTypes = null, IEnumerable<SelectMenuDefaultValue> defaultValues = null)
{
CustomId = customId;
Options = options;
@@ -76,8 +79,9 @@ namespace Discord
MaxValues = maxValues;
IsDisabled = disabled;
Type = type;
ChannelTypes = channelTypes?.ToArray() ?? Array.Empty<ChannelType>();
DefaultValues = defaultValues?.ToArray() ?? Array.Empty<SelectMenuDefaultValue>();
Id = id;
ChannelTypes = channelTypes?.ToArray() ?? [];
DefaultValues = defaultValues?.ToArray() ?? [];
}
}
}

View File

@@ -0,0 +1,30 @@
namespace Discord;
/// <summary>
/// Represents a separator component.
/// </summary>
public class SeparatorComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.Separator;
/// <inheritdoc/>
public int? Id { get; }
/// <summary>
/// Gets whether this component is a divider.
/// </summary>
public bool? IsDivider { get; }
/// <summary>
/// Gets the spacing of this component.
/// </summary>
public SeparatorSpacingSize? Spacing { get; }
internal SeparatorComponent(bool? isDivider, SeparatorSpacingSize? spacing, int? id = null)
{
IsDivider = isDivider;
Spacing = spacing;
Id = id;
}
}

View File

@@ -0,0 +1,17 @@
namespace Discord;
/// <summary>
/// Represents the spacing of a separator component.
/// </summary>
public enum SeparatorSpacingSize
{
/// <summary>
/// The separator has a small spacing.
/// </summary>
Small = 1,
/// <summary>
/// The separator has a large spacing.
/// </summary>
Large = 2
}

View File

@@ -0,0 +1,24 @@
namespace Discord;
/// <summary>
/// Represents a text display component.
/// </summary>
public class TextDisplayComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.TextDisplay;
/// <inheritdoc/>
public int? Id { get; }
/// <summary>
/// Gets the content of this component.
/// </summary>
public string Content { get; }
internal TextDisplayComponent(string content, int? id = null)
{
Id = id;
Content = content;
}
}

View File

@@ -3,7 +3,7 @@ namespace Discord
/// <summary>
/// Represents a <see cref="IMessageComponent"/> text input.
/// </summary>
public class TextInputComponent : IMessageComponent
public class TextInputComponent : IInteractableComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.TextInput;
@@ -11,6 +11,9 @@ namespace Discord
/// <inheritdoc/>
public string CustomId { get; }
/// <inheritdoc/>
public int? Id { get; }
/// <summary>
/// Gets the label of the component; this is the text shown above it.
/// </summary>
@@ -47,7 +50,7 @@ namespace Discord
public string Value { get; }
internal TextInputComponent(string customId, string label, string placeholder, int? minLength, int? maxLength,
TextInputStyle style, bool? required, string value)
TextInputStyle style, bool? required, string value, int? id)
{
CustomId = customId;
Label = label;
@@ -57,6 +60,7 @@ namespace Discord
Style = style;
Required = required;
Value = value;
Id = id;
}
}
}

View File

@@ -6,6 +6,7 @@ namespace Discord
/// Intended for short, single-line text.
/// </summary>
Short = 1,
/// <summary>
/// Intended for longer or multiline text.
/// </summary>

View File

@@ -0,0 +1,36 @@
namespace Discord;
/// <summary>
/// Represents a thumbnail component.
/// </summary>
public class ThumbnailComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.Thumbnail;
/// <inheritdoc/>
public int? Id { get; }
/// <summary>
/// Gets the media of the component.
/// </summary>
public UnfurledMediaItem Media { get; }
/// <summary>
/// Gets the description of the component.
/// </summary>
public string Description { get; }
/// <summary>
/// Gets whether the component is a spoiler.
/// </summary>
public bool IsSpoiler { get; }
internal ThumbnailComponent(int? id, UnfurledMediaItem media, string description, bool? isSpoiler)
{
Id = id;
Media = media;
Description = description;
IsSpoiler = isSpoiler ?? false;
}
}

View File

@@ -0,0 +1,17 @@
namespace Discord;
/// <summary>
/// Represents a media item that has been unfurled.
/// </summary>
public class UnfurledMediaItem
{
/// <summary>
/// Gets the URL of this media item.
/// </summary>
public string Url { get; }
internal UnfurledMediaItem(string url)
{
Url = url;
}
}

View File

@@ -0,0 +1,24 @@
namespace Discord;
public enum UnfurledMediaItemLoadingState
{
/// <summary>
/// The state of the media item is unknown.
/// </summary>
Unknown = 0,
/// <summary>
/// The media item is currently loading.
/// </summary>
Loading = 1,
/// <summary>
/// The media item was successfully loaded.
/// </summary>
LoadingSuccess = 2,
/// <summary>
/// The media item was not found.
/// </summary>
LoadingNotFound = 3
}

View File

@@ -1,19 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// Represents a modal interaction.
/// </summary>
public class Modal : IMessageComponent
public class Modal
{
/// <inheritdoc/>
public ComponentType Type => throw new NotSupportedException("Modals do not have a component type.");
/// <summary>
/// Gets the title of the modal.
/// </summary>

View File

@@ -115,21 +115,20 @@ namespace Discord
}
/// <summary>
/// Gets a <typeparamref name="TMessageComponent"/> by the specified <paramref name="customId"/>.
/// Gets a <typeparamref name="TMessageComponentBuilder"/> by the specified <paramref name="customId"/>.
/// </summary>
/// <typeparam name="TMessageComponent">The type of the component to get.</typeparam>
/// <param name="customId">The <see cref="IMessageComponent.CustomId"/> of the component to get.</param>
/// <typeparam name="TMessageComponentBuilder">The type of the component to get.</typeparam>
/// <param name="customId">The <see cref="IInteractableComponentBuilder.CustomId"/> of the component to get.</param>
/// <returns>
/// The component of type <typeparamref name="TMessageComponent"/> that was found, <see langword="null"/> otherwise.
/// The component of type <typeparamref name="TMessageComponentBuilder"/> that was found, <see langword="null"/> otherwise.
/// </returns>
public TMessageComponent GetComponent<TMessageComponent>(string customId)
where TMessageComponent : class, IMessageComponent
public TMessageComponentBuilder GetComponent<TMessageComponentBuilder>(string customId)
where TMessageComponentBuilder : class, IInteractableComponentBuilder
{
Preconditions.NotNull(customId, nameof(customId));
return Components.ActionRows
?.SelectMany(r => r.Components.OfType<TMessageComponent>())
.FirstOrDefault(c => c?.CustomId == customId);
return Components.ActionRows?.SelectMany(r => r.Components.OfType<TMessageComponentBuilder>())
.FirstOrDefault(c => c.CustomId == customId);
}
/// <summary>
@@ -145,7 +144,7 @@ namespace Discord
{
Preconditions.NotNull(customId, nameof(customId));
var component = GetComponent<TextInputComponent>(customId) ?? throw new ArgumentException($"There is no component of type {nameof(TextInputComponent)} with the specified custom ID in this modal builder.", nameof(customId));
var component = GetComponent<TextInputBuilder>(customId) ?? throw new ArgumentException($"There is no component of type {nameof(TextInputComponent)} with the specified custom ID in this modal builder.", nameof(customId));
var row = Components.ActionRows.First(r => r.Components.Contains(component));
var builder = new TextInputBuilder
@@ -163,7 +162,7 @@ namespace Discord
updateTextInput(builder);
row.Components.Remove(component);
row.AddComponent(builder.Build());
row.AddComponent(builder);
return this;
}
@@ -183,13 +182,13 @@ namespace Discord
/// <summary>
/// Removes a component from this builder by the specified <paramref name="customId"/>.
/// </summary>
/// <param name="customId">The <see cref="IMessageComponent.CustomId"/> of the component to remove.</param>
/// <param name="customId">The <see cref="IInteractableComponent.CustomId"/> of the component to remove.</param>
/// <returns>The current builder.</returns>
public ModalBuilder RemoveComponent(string customId)
{
Preconditions.NotNull(customId, nameof(customId));
Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c.CustomId == customId));
Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c is IInteractableComponentBuilder ic && ic.CustomId == customId));
return this;
}
@@ -230,7 +229,7 @@ namespace Discord
public class ModalComponentBuilder
{
/// <summary>
/// The max length of a <see cref="IMessageComponent.CustomId"/>.
/// The max length of a <see cref="IInteractableComponent.CustomId"/>.
/// </summary>
public const int MaxCustomIdLength = 100;
@@ -318,19 +317,17 @@ namespace Discord
{
Preconditions.LessThan(row, MaxActionRowCount, nameof(row));
var builtButton = text.Build();
if (_actionRows == null)
{
_actionRows = new List<ActionRowBuilder>
{
new ActionRowBuilder().AddComponent(builtButton)
new ActionRowBuilder().AddComponent(text)
};
}
else
{
if (_actionRows.Count == row)
_actionRows.Add(new ActionRowBuilder().AddComponent(builtButton));
_actionRows.Add(new ActionRowBuilder().AddComponent(text));
else
{
ActionRowBuilder actionRow;
@@ -342,8 +339,8 @@ namespace Discord
_actionRows.Add(actionRow);
}
if (actionRow.CanTakeComponent(builtButton))
actionRow.AddComponent(builtButton);
if (actionRow.CanTakeComponent(text))
actionRow.AddComponent(text);
else if (row < MaxActionRowCount)
WithTextInput(text, row + 1);
else

View File

@@ -123,5 +123,16 @@ namespace Discord
_isDisposed = true;
}
}
/// <summary>
/// Gets the url formatted with <c>attachment://</c> protocol.
/// </summary>
/// <returns>
/// The formatted url.
/// </returns>
public string GetAttachmentUrl()
{
return $"attachment://{FileName}";
}
}
}

View File

@@ -57,5 +57,10 @@ namespace Discord
/// This message is a voice message.
/// </summary>
VoiceMessage = 1 << 13,
/// <summary>
/// This message is using v2 components.
/// </summary>
ComponentsV2 = 1 << 15,
}
}

View File

@@ -401,5 +401,11 @@ namespace Discord
}
#endregion
public static void ValidateMessageFlags(MessageFlags flags)
{
if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification and not MessageFlags.ComponentsV2 and not MessageFlags.Ephemeral)
throw new ArgumentException("The only valid MessageFlags are Ephemeral, SuppressEmbeds, SuppressNotification, ComponentsV2 and None.", nameof(flags));
}
}
}

View File

@@ -44,55 +44,55 @@ namespace Discord.Interactions
protected virtual Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
=> Context.Interaction.DeferAsync(ephemeral, options);
/// <inheritdoc cref="IDiscordInteraction.RespondAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.RespondAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null)
=> Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> Context.Interaction.RespondWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.RespondWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> Context.Interaction.RespondWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.RespondWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> Context.Interaction.RespondWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.RespondWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IDiscordInteraction.RespondWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.RespondWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> Context.Interaction.RespondWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.RespondWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task<IUserMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null)
=> Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task<IUserMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> Context.Interaction.FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> Context.Interaction.FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> Context.Interaction.FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/>
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/>
protected virtual Task<IUserMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> Context.Interaction.FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> Context.Interaction.FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags, PollProperties)"/>
protected virtual Task<IUserMessage> ReplyAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null,

View File

@@ -43,8 +43,8 @@ namespace Discord.Interactions
/// A Task representing the operation of creating the interaction response.
/// </returns>
/// <exception cref="InvalidOperationException">Thrown if the interaction isn't a type of <see cref="RestInteraction"/>.</exception>
protected override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null)
=> HandleInteractionAsync(x => x.Respond(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll));
protected override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> HandleInteractionAsync(x => x.Respond(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags));
/// <summary>
/// Responds to the interaction with a modal.

View File

@@ -8,6 +8,9 @@ namespace Discord.API
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("components")]
public IMessageComponent[] Components { get; set; }
@@ -29,9 +32,10 @@ namespace Discord.API
_ => null
};
}).ToArray();
Id = c.Id ?? Optional<int>.Unspecified;
}
[JsonIgnore]
string IMessageComponent.CustomId => null;
int? IMessageComponent.Id => Id.ToNullable();
}
}

View File

@@ -2,11 +2,14 @@ using Newtonsoft.Json;
namespace Discord.API
{
internal class ButtonComponent : IMessageComponent
internal class ButtonComponent : IInteractableComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("style")]
public ButtonStyle Style { get; set; }
@@ -39,6 +42,7 @@ namespace Discord.API
Url = c.Url;
Disabled = c.IsDisabled;
SkuId = c.SkuId ?? Optional<ulong>.Unspecified;
Id = c.Id ?? Optional<int>.Unspecified;
if (c.Emote != null)
{
@@ -62,6 +66,9 @@ namespace Discord.API
}
[JsonIgnore]
string IMessageComponent.CustomId => CustomId.GetValueOrDefault();
string IInteractableComponent.CustomId => CustomId.GetValueOrDefault();
[JsonIgnore]
int? IMessageComponent.Id => Id.ToNullable();
}
}

View File

@@ -0,0 +1,36 @@
using Discord.Rest;
using Newtonsoft.Json;
using System.Linq;
namespace Discord.API;
internal class ContainerComponent : IMessageComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("accent_color")]
public Optional<uint?> AccentColor { get; set; }
[JsonProperty("spoiler")]
public Optional<bool> IsSpoiler { get; set; }
[JsonProperty("components")]
public IMessageComponent[] Components { get; set; }
public ContainerComponent() { }
public ContainerComponent(Discord.ContainerComponent component)
{
Type = component.Type;
Id = component.Id ?? Optional<int>.Unspecified;
AccentColor = component.AccentColor ?? Optional<uint?>.Unspecified;
IsSpoiler = component.IsSpoiler ?? Optional<bool>.Unspecified;
Components = component.Components.Select(x => x.ToModel()).ToArray();
}
int? IMessageComponent.Id => Id.ToNullable();
}

View File

@@ -0,0 +1,30 @@
using Discord.Rest;
using Newtonsoft.Json;
namespace Discord.API;
internal class FileComponent : IMessageComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("file")]
public UnfurledMediaItem File { get; set; }
[JsonProperty("spoiler")]
public Optional<bool> IsSpoiler { get; set; }
public FileComponent() { }
public FileComponent(Discord.FileComponent component)
{
Type = component.Type;
Id = component.Id ?? Optional<int>.Unspecified;
File = component.File.ToModel();
IsSpoiler = component.IsSpoiler ?? Optional<bool>.Unspecified;
}
int? IMessageComponent.Id => Id.ToNullable();
}

View File

@@ -17,7 +17,7 @@ internal class ForumThreadMessage
public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; }
public Optional<IMessageComponent[]> Components { get; set; }
[JsonProperty("sticker_ids")]
public Optional<ulong[]> Stickers { get; set; }

View File

@@ -21,7 +21,7 @@ namespace Discord.API
public Optional<MessageFlags> Flags { get; set; }
[JsonProperty("components")]
public Optional<ActionRowComponent[]> Components { get; set; }
public Optional<IMessageComponent[]> Components { get; set; }
[JsonProperty("choices")]
public Optional<ApplicationCommandOptionChoice[]> Choices { get; set; }

View File

@@ -0,0 +1,33 @@
using Discord.Rest;
using Newtonsoft.Json;
using System.Linq;
namespace Discord.API;
internal class MediaGalleryComponent : IMessageComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("items")]
public MediaGalleryItem[] Items { get; set; }
public MediaGalleryComponent() { }
public MediaGalleryComponent(Discord.MediaGalleryComponent component)
{
Type = component.Type;
Id = component.Id ?? Optional<int>.Unspecified;
Items = component.Items.Select(x => new MediaGalleryItem
{
Description = x.Description,
IsSpoiler = x.IsSpoiler,
Media = x.Media.ToModel()
}).ToArray();
}
int? IMessageComponent.Id => Id.ToNullable();
}

View File

@@ -0,0 +1,15 @@
using Newtonsoft.Json;
namespace Discord.API;
internal class MediaGalleryItem
{
[JsonProperty("media")]
public UnfurledMediaItem Media { get; set; }
[JsonProperty("description")]
public Optional<string> Description { get; set; }
[JsonProperty("spoiler")]
public Optional<bool> IsSpoiler { get; set; }
}

View File

@@ -0,0 +1,32 @@
using Discord.Rest;
using Newtonsoft.Json;
using System.Linq;
namespace Discord.API;
internal class SectionComponent : IMessageComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("components")]
public IMessageComponent[] Components { get; set; }
[JsonProperty("accessory")]
public IMessageComponent Accessory { get; set; }
public SectionComponent() { }
public SectionComponent(Discord.SectionComponent component)
{
Type = component.Type;
Id = component.Id ?? Optional<int>.Unspecified;
Components = component.Components.Select(x => x.ToModel()).ToArray();
Accessory = component.Accessory.ToModel();
}
int? IMessageComponent.Id => Id.ToNullable();
}

View File

@@ -3,11 +3,14 @@ using System.Linq;
namespace Discord.API
{
internal class SelectMenuComponent : IMessageComponent
internal class SelectMenuComponent : IInteractableComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("custom_id")]
public string CustomId { get; set; }
@@ -52,5 +55,8 @@ namespace Discord.API
ChannelTypes = component.ChannelTypes.ToArray();
DefaultValues = component.DefaultValues.Select(x => new SelectMenuDefaultValue {Id = x.Id, Type = x.Type}).ToArray();
}
[JsonIgnore]
int? IMessageComponent.Id => Id.ToNullable();
}
}

View File

@@ -0,0 +1,30 @@
using Newtonsoft.Json;
namespace Discord.API;
internal class SeparatorComponent : IMessageComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("divider")]
public Optional<bool> IsDivider { get; set; }
[JsonProperty("spacing")]
public Optional<SeparatorSpacingSize> Spacing { get; set; }
public SeparatorComponent() { }
public SeparatorComponent(Discord.SeparatorComponent component)
{
Type = component.Type;
Id = component.Id ?? Optional<int>.Unspecified;
IsDivider = component.IsDivider ?? Optional<bool>.Unspecified;
Spacing = component.Spacing ?? Optional<SeparatorSpacingSize>.Unspecified;
}
int? IMessageComponent.Id => Id.ToNullable();
}

View File

@@ -0,0 +1,26 @@
using Newtonsoft.Json;
namespace Discord.API;
internal class TextDisplayComponent : IMessageComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("content")]
public string Content { get; set; }
public TextDisplayComponent() { }
public TextDisplayComponent(Discord.TextDisplayComponent component)
{
Type = component.Type;
Id = component.Id ?? Optional<int>.Unspecified;
Content = component.Content;
}
int? IMessageComponent.Id => Id.ToNullable();
}

View File

@@ -2,11 +2,14 @@ using Newtonsoft.Json;
namespace Discord.API
{
internal class TextInputComponent : IMessageComponent
internal class TextInputComponent : IInteractableComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("style")]
public TextInputStyle Style { get; set; }
@@ -44,6 +47,10 @@ namespace Discord.API
MaxLength = component.MaxLength ?? Optional<int>.Unspecified;
Required = component.Required ?? Optional<bool>.Unspecified;
Value = component.Value ?? Optional<string>.Unspecified;
Id = component.Id ?? Optional<int>.Unspecified;
}
[JsonIgnore]
int? IMessageComponent.Id => Id.ToNullable();
}
}

View File

@@ -0,0 +1,35 @@
using Discord.Rest;
using Newtonsoft.Json;
namespace Discord.API;
internal class ThumbnailComponent : IMessageComponent
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
[JsonProperty("id")]
public Optional<int> Id { get; set; }
[JsonProperty("media")]
public UnfurledMediaItem Media { get; set; }
[JsonProperty("description")]
public Optional<string> Description { get; set; }
[JsonProperty("spoiler")]
public Optional<bool> IsSpoiler { get; set; }
public ThumbnailComponent() { }
public ThumbnailComponent(Discord.ThumbnailComponent component)
{
Type = component.Type;
Id = component.Id ?? Optional<int>.Unspecified;
Media = component.Media.ToModel();
Description = component.Description;
IsSpoiler = component.IsSpoiler;
}
int? IMessageComponent.Id => Id.ToNullable();
}

View File

@@ -0,0 +1,24 @@
using Newtonsoft.Json;
namespace Discord.API;
internal class UnfurledMediaItem
{
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("proxy_url")]
public Optional<string> ProxyUrl { get; set; }
[JsonProperty("height")]
public Optional<int?> Height { get; set; }
[JsonProperty("width")]
public Optional<int?> Width { get; set; }
[JsonProperty("content_type")]
public Optional<string> ContentType { get; set; }
[JsonProperty("loading_state")]
public Optional<UnfurledMediaItemLoadingState> LoadingState { get; set; }
}

View File

@@ -24,7 +24,7 @@ namespace Discord.API.Rest
public Optional<MessageReference> MessageReference { get; set; }
[JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; }
public Optional<IMessageComponent[]> Components { get; set; }
[JsonProperty("sticker_ids")]
public Optional<ulong[]> Stickers { get; set; }

View File

@@ -24,7 +24,7 @@ namespace Discord.API.Rest
public Optional<string> Content { get; set; }
public Optional<Embed[]> Embeds { get; set; }
public Optional<AllowedMentions> AllowedMentions { get; set; }
public Optional<ActionRowComponent[]> MessageComponent { get; set; }
public Optional<IMessageComponent[]> MessageComponent { get; set; }
public Optional<MessageFlags?> Flags { get; set; }
public Optional<ulong[]> Stickers { get; set; }
public Optional<ulong[]> TagIds { get; set; }

View File

@@ -1,28 +1,22 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Discord.API.Rest
namespace Discord.API.Rest;
internal class CreatePostParams
{
internal class CreatePostParams
{
// thread
[JsonProperty("name")]
public string Title { get; set; }
// thread
[JsonProperty("name")]
public string Title { get; set; }
[JsonProperty("auto_archive_duration")]
public ThreadArchiveDuration ArchiveDuration { get; set; }
[JsonProperty("auto_archive_duration")]
public ThreadArchiveDuration ArchiveDuration { get; set; }
[JsonProperty("rate_limit_per_user")]
public Optional<int?> Slowmode { get; set; }
[JsonProperty("rate_limit_per_user")]
public Optional<int?> Slowmode { get; set; }
[JsonProperty("message")]
public ForumThreadMessage Message { get; set; }
[JsonProperty("message")]
public ForumThreadMessage Message { get; set; }
[JsonProperty("applied_tags")]
public Optional<ulong[]> Tags { get; set; }
}
[JsonProperty("applied_tags")]
public Optional<ulong[]> Tags { get; set; }
}

View File

@@ -3,6 +3,7 @@ using Discord.Net.Rest;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Discord.API.Rest
@@ -37,7 +38,7 @@ namespace Discord.API.Rest
public Optional<MessageFlags> Flags { get; set; }
[JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; }
public Optional<IMessageComponent[]> Components { get; set; }
[JsonProperty("file")]
public Optional<MultipartFile> File { get; set; }
@@ -55,6 +56,7 @@ namespace Discord.API.Rest
{
var d = new Dictionary<string, object>();
var extraFlags = MessageFlags.None;
if (File.IsSpecified)
{
d["file"] = File.Value;
@@ -77,14 +79,21 @@ namespace Discord.API.Rest
payload["embeds"] = Embeds.Value;
if (AllowedMentions.IsSpecified)
payload["allowed_mentions"] = AllowedMentions.Value;
if (Components.IsSpecified)
{
payload["components"] = Components.Value;
if (Components.Value.Any(x => x.Type is not ComponentType.ActionRow))
extraFlags |= MessageFlags.ComponentsV2;
}
payload["flags"] = Flags.GetValueOrDefault(MessageFlags.None) | extraFlags;
if (ThreadName.IsSpecified)
payload["thread_name"] = ThreadName.Value;
if (AppliedTags.IsSpecified)
payload["applied_tags"] = AppliedTags.Value;
if (Flags.IsSpecified)
payload["flags"] = Flags.Value;
if (Poll.IsSpecified)
payload["poll"] = Poll.Value;

View File

@@ -14,7 +14,7 @@ namespace Discord.API.Rest
public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("components")]
public Optional<ActionRowComponent[]> Components { get; set; }
public Optional<IMessageComponent[]> Components { get; set; }
[JsonProperty("flags")]
public Optional<MessageFlags?> Flags { get; set; }

View File

@@ -1,19 +1,21 @@
using Newtonsoft.Json;
namespace Discord.API.Rest
namespace Discord.API.Rest;
internal class ModifyMessageParams
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class ModifyMessageParams
{
[JsonProperty("content")]
public Optional<string> Content { get; set; }
[JsonProperty("embeds")]
public Optional<API.Embed[]> Embeds { get; set; }
[JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; }
[JsonProperty("flags")]
public Optional<MessageFlags?> Flags { get; set; }
[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }
}
[JsonProperty("content")]
public Optional<string> Content { get; set; }
[JsonProperty("embeds")]
public Optional<Embed[]> Embeds { get; set; }
[JsonProperty("components")]
public Optional<IMessageComponent[]> Components { get; set; }
[JsonProperty("flags")]
public Optional<MessageFlags?> Flags { get; set; }
[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }
}

View File

@@ -1,17 +1,18 @@
using Newtonsoft.Json;
namespace Discord.API.Rest
namespace Discord.API.Rest;
internal class ModifyWebhookMessageParams
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class ModifyWebhookMessageParams
{
[JsonProperty("content")]
public Optional<string> Content { get; set; }
[JsonProperty("embeds")]
public Optional<Embed[]> Embeds { get; set; }
[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; }
}
[JsonProperty("content")]
public Optional<string> Content { get; set; }
[JsonProperty("embeds")]
public Optional<Embed[]> Embeds { get; set; }
[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("components")]
public Optional<IMessageComponent[]> Components { get; set; }
}

View File

@@ -21,7 +21,7 @@ namespace Discord.API.Rest
public Optional<Embed[]> Embeds { get; set; }
public Optional<AllowedMentions> AllowedMentions { get; set; }
public Optional<MessageReference> MessageReference { get; set; }
public Optional<ActionRowComponent[]> MessageComponent { get; set; }
public Optional<IMessageComponent[]> MessageComponent { get; set; }
public Optional<MessageFlags?> Flags { get; set; }
public Optional<ulong[]> Stickers { get; set; }
public Optional<CreatePollParams> Poll { get; set; }

View File

@@ -21,7 +21,7 @@ namespace Discord.API.Rest
public Optional<bool> IsTTS { get; set; }
public Optional<Embed[]> Embeds { get; set; }
public Optional<AllowedMentions> AllowedMentions { get; set; }
public Optional<ActionRowComponent[]> MessageComponents { get; set; }
public Optional<IMessageComponent[]> MessageComponents { get; set; }
public Optional<MessageFlags> Flags { get; set; }
public Optional<CreatePollParams> Poll { get; set; }
@@ -44,8 +44,10 @@ namespace Discord.API.Rest
{
var d = new Dictionary<string, object>();
var extraFlags = MessageFlags.None;
if (Files.Any(x => x.Waveform is not null && x.DurationSeconds is not null))
Flags = Flags.GetValueOrDefault(MessageFlags.None) | MessageFlags.VoiceMessage;
extraFlags |= MessageFlags.VoiceMessage;
var payload = new Dictionary<string, object>();
payload["type"] = Type;
@@ -55,20 +57,26 @@ namespace Discord.API.Rest
data["content"] = Content.Value;
if (IsTTS.IsSpecified)
data["tts"] = IsTTS.Value;
if (MessageComponents.IsSpecified)
data["components"] = MessageComponents.Value;
if (Embeds.IsSpecified)
data["embeds"] = Embeds.Value;
if (AllowedMentions.IsSpecified)
data["allowed_mentions"] = AllowedMentions.Value;
if (Flags.IsSpecified)
data["flags"] = Flags.Value;
if (MessageComponents.IsSpecified)
{
data["components"] = MessageComponents.Value;
if (MessageComponents.Value.Any(x => x.Type is not ComponentType.ActionRow))
extraFlags |= MessageFlags.ComponentsV2;
}
data["flags"] = Flags.GetValueOrDefault(MessageFlags.None) | extraFlags;
if (Poll.IsSpecified)
data["poll"] = Poll.Value;
List<object> attachments = new();
List<object> attachments = [];
for (int n = 0; n != Files.Length; n++)
for (var n = 0; n != Files.Length; n++)
{
var attachment = Files[n];

View File

@@ -22,7 +22,7 @@ namespace Discord.API.Rest
public Optional<string> AvatarUrl { get; set; }
public Optional<Embed[]> Embeds { get; set; }
public Optional<AllowedMentions> AllowedMentions { get; set; }
public Optional<ActionRowComponent[]> MessageComponents { get; set; }
public Optional<IMessageComponent[]> MessageComponents { get; set; }
public Optional<MessageFlags> Flags { get; set; }
public Optional<string> ThreadName { get; set; }
public Optional<ulong[]> AppliedTags { get; set; }
@@ -37,8 +37,10 @@ namespace Discord.API.Rest
{
var d = new Dictionary<string, object>();
var extraFlags = MessageFlags.None;
if (Files.Any(x => x.Waveform is not null && x.DurationSeconds is not null))
Flags = Flags.GetValueOrDefault(MessageFlags.None) | MessageFlags.VoiceMessage;
extraFlags |= MessageFlags.VoiceMessage;
var payload = new Dictionary<string, object>();
if (Content.IsSpecified)
@@ -51,14 +53,20 @@ namespace Discord.API.Rest
payload["username"] = Username.Value;
if (AvatarUrl.IsSpecified)
payload["avatar_url"] = AvatarUrl.Value;
if (MessageComponents.IsSpecified)
payload["components"] = MessageComponents.Value;
if (Embeds.IsSpecified)
payload["embeds"] = Embeds.Value;
if (AllowedMentions.IsSpecified)
payload["allowed_mentions"] = AllowedMentions.Value;
if (Flags.IsSpecified)
payload["flags"] = Flags.Value;
if (MessageComponents.IsSpecified)
{
payload["components"] = MessageComponents.Value;
if (MessageComponents.Value.Any(x => x.Type is not ComponentType.ActionRow))
extraFlags |= MessageFlags.ComponentsV2;
}
payload["flags"] = Flags.GetValueOrDefault(MessageFlags.None) | extraFlags;
if (ThreadName.IsSpecified)
payload["thread_name"] = ThreadName.Value;
if (AppliedTags.IsSpecified)

View File

@@ -2779,6 +2779,8 @@ namespace Discord.API
if (threadId.HasValue)
querys.Add($"thread_id={threadId}");
querys.Add("with_components=true");
return $"{string.Join("&", querys)}";
}

View File

@@ -308,10 +308,10 @@ namespace Discord.Rest
Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed.");
}
if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification)
throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds, SuppressNotification and none.", nameof(flags));
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
Preconditions.ValidateMessageFlags(flags);
var args = new CreateMessageParams
{
@@ -320,7 +320,7 @@ namespace Discord.Rest
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
AllowedMentions = allowedMentions?.ToModel(),
MessageReference = messageReference?.ToModel(),
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified,
Flags = flags,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
@@ -429,8 +429,10 @@ namespace Discord.Rest
}
}
if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification)
throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds, SuppressNotification and none.", nameof(flags));
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
Preconditions.ValidateMessageFlags(flags);
if (stickers != null)
{
@@ -444,7 +446,7 @@ namespace Discord.Rest
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified,
MessageComponent = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
MessageComponent = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified,
Flags = flags,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified

View File

@@ -163,7 +163,7 @@ namespace Discord.Rest
Content = text,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
Flags = flags,
Components = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
Components = components?.Components?.Any() ?? false ? components.Components.Select(x => x.ToModel()).ToArray() : Optional<IMessageComponent[]>.Unspecified,
Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified,
},
Tags = tagIds
@@ -224,7 +224,7 @@ namespace Discord.Rest
Content = text,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
Flags = flags,
MessageComponent = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
MessageComponent = components?.Components?.Any() ?? false ? components.Components.Select(x => x.ToModel()).ToArray() : Optional<IMessageComponent[]>.Unspecified,
Slowmode = slowmode,
Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified,
Title = title,

View File

@@ -1,12 +1,11 @@
using Discord.API.Rest;
using Discord.Net.Rest;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Model = Discord.API.Interaction;
namespace Discord.Rest
@@ -81,7 +80,8 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -89,7 +89,7 @@ namespace Discord.Rest
if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck)
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -123,8 +123,12 @@ namespace Discord.Rest
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
TTS = isTTS,
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified,
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
}
};
@@ -152,12 +156,13 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -172,13 +177,15 @@ namespace Discord.Rest
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
};
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
}
@@ -194,7 +201,8 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -203,7 +211,7 @@ namespace Discord.Rest
Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null");
using (var file = new FileAttachment(fileStream, fileName))
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
/// <inheritdoc/>
@@ -218,7 +226,8 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist");
@@ -226,7 +235,7 @@ namespace Discord.Rest
Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null");
using (var file = new FileAttachment(File.OpenRead(filePath), fileName))
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
/// <inheritdoc/>
@@ -240,9 +249,10 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
return FollowupWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
}
/// <inheritdoc/>
@@ -256,12 +266,13 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -289,23 +300,22 @@ namespace Discord.Rest
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}
var flags = MessageFlags.None;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
};
var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray())
{
Flags = flags,
Content = text,
IsTTS = isTTS,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
{
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
Content = text,
IsTTS = isTTS,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
}

View File

@@ -430,8 +430,8 @@ namespace Discord.Rest
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Components = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? []
: Optional<IMessageComponent[]>.Unspecified,
};
return client.ApiClient.ModifyInteractionFollowupMessageAsync(apiArgs, message.Id, message.Token, options);
@@ -478,8 +478,7 @@ namespace Discord.Rest
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Components = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] : Optional<IMessageComponent[]>.Unspecified,
Flags = args.Flags
};
@@ -495,8 +494,7 @@ namespace Discord.Rest
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
MessageComponents = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] : Optional<IMessageComponent[]>.Unspecified
};
return client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options);

View File

@@ -77,7 +77,8 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -85,7 +86,7 @@ namespace Discord.Rest
if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck)
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -119,14 +120,16 @@ namespace Discord.Rest
AllowedMentions = allowedMentions?.ToModel(),
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
TTS = isTTS,
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
}
};
if (ephemeral)
response.Data.Value.Flags = MessageFlags.Ephemeral;
lock (_lock)
{
if (HasResponded)
@@ -208,8 +211,8 @@ namespace Discord.Rest
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? []
: Optional<IMessageComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
}
};
@@ -227,8 +230,8 @@ namespace Discord.Rest
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
MessageComponents = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? []
: Optional<IMessageComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
};
@@ -256,12 +259,13 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -276,14 +280,15 @@ namespace Discord.Rest
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = ephemeral ? MessageFlags.Ephemeral : MessageFlags.None,
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
}
@@ -299,7 +304,8 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -308,7 +314,7 @@ namespace Discord.Rest
Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null");
using (var file = new FileAttachment(fileStream, fileName))
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
/// <inheritdoc/>
@@ -323,7 +329,8 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist");
@@ -331,7 +338,7 @@ namespace Discord.Rest
Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null");
using (var file = new FileAttachment(File.OpenRead(filePath), fileName))
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
/// <inheritdoc/>
@@ -345,9 +352,10 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
return FollowupWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
}
/// <inheritdoc/>
@@ -361,12 +369,13 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -396,19 +405,18 @@ namespace Discord.Rest
}
}
var flags = MessageFlags.None;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray())
{
Flags = flags,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
Content = text,
IsTTS = isTTS,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);

View File

@@ -93,7 +93,7 @@ namespace Discord.Rest
}
}
internal RestMessageComponentData(IMessageComponent component, BaseDiscordClient discord, IGuild guild)
internal RestMessageComponentData(IInteractableComponent component, BaseDiscordClient discord, IGuild guild)
{
CustomId = component.CustomId;
Type = component.Type;

View File

@@ -3,6 +3,7 @@ using Discord.Rest;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -137,12 +138,13 @@ namespace Discord.Rest
MessageComponent component = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -157,13 +159,15 @@ namespace Discord.Rest
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ??Optional<API.ActionRowComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified
Components = component?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
};
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
}
@@ -194,12 +198,13 @@ namespace Discord.Rest
MessageComponent component = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -216,14 +221,16 @@ namespace Discord.Rest
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Components = component?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified,
Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
};
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
}
@@ -254,12 +261,13 @@ namespace Discord.Rest
MessageComponent component = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -279,14 +287,16 @@ namespace Discord.Rest
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Components = component?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
File = fileStream != null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified,
Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified
Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
};
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;
return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
}
@@ -315,7 +325,8 @@ namespace Discord.Rest
MessageComponent component = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -323,7 +334,7 @@ namespace Discord.Rest
if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck)
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -357,8 +368,12 @@ namespace Discord.Rest
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
TTS = isTTS,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified,
Components = component?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified
}
};
@@ -390,12 +405,13 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -425,19 +441,18 @@ namespace Discord.Rest
}
}
var flags = MessageFlags.None;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray())
{
Flags = flags,
Flags = ephemeral
? flags | MessageFlags.Ephemeral
: flags == MessageFlags.None
? Optional<MessageFlags>.Unspecified
: flags,
Content = text,
IsTTS = isTTS,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified
};
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);
@@ -454,9 +469,10 @@ namespace Discord.Rest
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
return FollowupWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
}
/// <inheritdoc/>
@@ -541,8 +557,8 @@ namespace Discord.Rest
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? []
: Optional<IMessageComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
}
};
@@ -551,7 +567,7 @@ namespace Discord.Rest
}
else
{
var attachments = args.Attachments.Value?.ToArray() ?? Array.Empty<FileAttachment>();
var attachments = args.Attachments.Value?.ToArray() ?? [];
var response = new API.Rest.UploadInteractionFileParams(attachments)
{
@@ -560,8 +576,8 @@ namespace Discord.Rest
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
MessageComponents = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? []
: Optional<IMessageComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
};

View File

@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DataModel = Discord.API.MessageComponentInteractionData;
using InterationModel = Discord.API.Interaction;
using Model = Discord.API.ModalInteractionData;
namespace Discord.Rest
@@ -26,7 +23,7 @@ namespace Discord.Rest
{
CustomId = model.CustomId;
Components = model.Components
.SelectMany(x => x.Components)
.SelectMany(x => x.Components.OfType<IInteractableComponent>())
.Select(x => new RestMessageComponentData(x, discord, guild))
.ToArray();
}

View File

@@ -342,7 +342,7 @@ namespace Discord.Rest
/// <inheritdoc/>
public abstract string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Sends a followup message for this interaction.
@@ -361,7 +361,7 @@ namespace Discord.Rest
/// contains the sent message.
/// </returns>
public abstract Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Sends a followup message for this interaction.
@@ -382,7 +382,7 @@ namespace Discord.Rest
/// contains the sent message.
/// </returns>
public abstract Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Sends a followup message for this interaction.
@@ -403,7 +403,7 @@ namespace Discord.Rest
/// contains the sent message.
/// </returns>
public abstract Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Sends a followup message for this interaction.
@@ -423,7 +423,7 @@ namespace Discord.Rest
/// contains the sent message.
/// </returns>
public abstract Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Sends a followup message for this interaction.
@@ -443,7 +443,7 @@ namespace Discord.Rest
/// contains the sent message.
/// </returns>
public abstract Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <inheritdoc/>
public Task DeleteOriginalResponseAsync(RequestOptions options = null)
@@ -462,7 +462,7 @@ namespace Discord.Rest
/// <inheritdoc/>
Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions,
MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> Task.FromResult(Respond(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll));
/// <inheritdoc/>
Task IDiscordInteraction.DeferAsync(bool ephemeral, RequestOptions options)
@@ -470,45 +470,48 @@ namespace Discord.Rest
/// <inheritdoc/>
Task IDiscordInteraction.RespondWithModalAsync(Modal modal, RequestOptions options)
=> Task.FromResult(RespondWithModal(modal, options));
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions,
MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options)
=> await GetOriginalResponseAsync(options).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options)
=> await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral,
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral,
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS,
bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text, Embed[] embeds, bool isTTS,
bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
/// <inheritdoc/>
Task IDiscordInteraction.RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text, Embed[] embeds, bool isTTS, bool ephemeral,
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) => throw new NotSupportedException("REST-Based interactions don't support files.");
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) => throw new NotSupportedException("REST-Based interactions don't support files.");
#if NETCOREAPP3_0_OR_GREATER != true
/// <inheritdoc/>
Task IDiscordInteraction.RespondWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral,
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) => throw new NotSupportedException("REST-Based interactions don't support files.");
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) => throw new NotSupportedException("REST-Based interactions don't support files.");
/// <inheritdoc/>
Task IDiscordInteraction.RespondWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS,
bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) => throw new NotSupportedException("REST-Based interactions don't support files.");
bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) => throw new NotSupportedException("REST-Based interactions don't support files.");
/// <inheritdoc/>
Task IDiscordInteraction.RespondWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral,
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) => throw new NotSupportedException("REST-Based interactions don't support files.");
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) => throw new NotSupportedException("REST-Based interactions don't support files.");
#endif
#endregion
}

View File

@@ -37,11 +37,11 @@ namespace Discord.Rest
public override string Defer(bool ephemeral = false, RequestOptions options = null) => throw new NotSupportedException();
public override string RespondWithModal(Modal modal, RequestOptions options = null) => throw new NotSupportedException();
public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException();
public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException();
public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException();
}
}

View File

@@ -100,17 +100,17 @@ namespace Discord.Rest
=> Respond(result, options);
public override string Defer(bool ephemeral = false, RequestOptions options = null)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override string RespondWithModal(Modal modal, RequestOptions options = null)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");

View File

@@ -55,16 +55,16 @@ namespace Discord.Rest
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
if (allowedMentions is { AllowedTypes: not null })
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
allowedMentions.UserIds is { Count: > 0 })
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
allowedMentions.RoleIds is { Count: > 0 })
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
@@ -93,13 +93,13 @@ namespace Discord.Rest
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create<MessageFlags?>(),
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(),
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() : Optional<API.ActionRowComponent[]>.Unspecified,
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] : Optional<IMessageComponent[]>.Unspecified,
};
return client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options);
}
else
{
var attachments = args.Attachments.Value?.ToArray() ?? Array.Empty<FileAttachment>();
var attachments = args.Attachments.Value?.ToArray() ?? [];
var apiArgs = new UploadFileParams(attachments)
{
@@ -107,7 +107,7 @@ namespace Discord.Rest
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create<MessageFlags?>(),
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(),
MessageComponent = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() : Optional<API.ActionRowComponent[]>.Unspecified
MessageComponent = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] : Optional<IMessageComponent[]>.Unspecified
};
return client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options);

View File

@@ -95,12 +95,12 @@ namespace Discord.Rest
/// <inheritdoc />
public PurchaseNotification PurchaseNotification { get; private set; }
/// <inheritdoc />
public MessageCallData? CallData { get; private set; }
/// <inheritdoc cref="IMessage.Components"/>
public IReadOnlyCollection<ActionRowComponent> Components { get; private set; }
public IReadOnlyCollection<IMessageComponent> Components { get; private set; }
/// <summary>
/// Gets a collection of the mentioned users in the message.
/// </summary>
@@ -150,7 +150,7 @@ namespace Discord.Rest
if (model.Activity.IsSpecified)
{
// create a new Activity from the API model
Activity = new MessageActivity()
Activity = new MessageActivity
{
Type = model.Activity.Value.Type.Value,
PartyId = model.Activity.Value.PartyId.GetValueOrDefault()
@@ -170,62 +170,9 @@ namespace Discord.Rest
};
}
if (model.Components.IsSpecified)
{
Components = model.Components.Value.Where(x => x.Type is ComponentType.ActionRow)
.Select(x => new ActionRowComponent(((API.ActionRowComponent)x).Components.Select<IMessageComponent, IMessageComponent>(y =>
{
switch (y.Type)
{
case ComponentType.Button:
{
var parsed = (API.ButtonComponent)y;
return new Discord.ButtonComponent(
parsed.Style,
parsed.Label.GetValueOrDefault(),
parsed.Emote.IsSpecified
? parsed.Emote.Value.Id.HasValue
? new Emote(parsed.Emote.Value.Id.Value, parsed.Emote.Value.Name, parsed.Emote.Value.Animated.GetValueOrDefault())
: new Emoji(parsed.Emote.Value.Name)
: null,
parsed.CustomId.GetValueOrDefault(),
parsed.Url.GetValueOrDefault(),
parsed.Disabled.GetValueOrDefault(),
parsed.SkuId.ToNullable());
}
case ComponentType.SelectMenu or ComponentType.ChannelSelect or ComponentType.RoleSelect or ComponentType.MentionableSelect or ComponentType.UserSelect:
{
var parsed = (API.SelectMenuComponent)y;
return new SelectMenuComponent(
parsed.CustomId,
parsed.Options?.Select(z => new SelectMenuOption(
z.Label,
z.Value,
z.Description.GetValueOrDefault(),
z.Emoji.IsSpecified
? z.Emoji.Value.Id.HasValue
? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault())
: new Emoji(z.Emoji.Value.Name)
: null,
z.Default.ToNullable())).ToList(),
parsed.Placeholder.GetValueOrDefault(),
parsed.MinValues,
parsed.MaxValues,
parsed.Disabled,
parsed.Type,
parsed.ChannelTypes.GetValueOrDefault(),
parsed.DefaultValues.IsSpecified
? parsed.DefaultValues.Value.Select(x => new SelectMenuDefaultValue(x.Id, x.Type))
: Array.Empty<SelectMenuDefaultValue>()
);
}
default:
return null;
}
}).ToList())).ToImmutableArray();
}
else
Components = new List<ActionRowComponent>();
Components = model.Components.IsSpecified
? model.Components.Value.Select(x => x.ToEntity()).ToImmutableArray()
: [];
if (model.Flags.IsSpecified)
Flags = model.Flags.Value;
@@ -236,15 +183,16 @@ namespace Discord.Rest
if (value.Length > 0)
{
var reactions = ImmutableArray.CreateBuilder<RestReaction>(value.Length);
for (int i = 0; i < value.Length; i++)
reactions.Add(RestReaction.Create(value[i]));
foreach (var t in value)
reactions.Add(RestReaction.Create(t));
_reactions = reactions.ToImmutable();
}
else
_reactions = ImmutableArray.Create<RestReaction>();
_reactions = [];
}
else
_reactions = ImmutableArray.Create<RestReaction>();
_reactions = [];
if (model.Interaction.IsSpecified)
{
@@ -289,11 +237,11 @@ namespace Discord.Rest
? new GuildProductPurchase(model.PurchaseNotification.Value.ProductPurchase.Value.ListingId, model.PurchaseNotification.Value.ProductPurchase.Value.ProductName)
: null);
}
if (model.Call.IsSpecified)
CallData = new MessageCallData(model.Call.Value.Participants, model.Call.Value.EndedTimestamp.ToNullable());
}
/// <inheritdoc />
public async Task UpdateAsync(RequestOptions options = null)
{

View File

@@ -84,7 +84,7 @@ namespace Discord.Rest
if (model.MentionEveryone.IsSpecified)
_isMentioningEveryone = model.MentionEveryone.Value;
if (model.RoleMentions.IsSpecified)
_roleMentionIds = model.RoleMentions.Value.ToImmutableArray();
_roleMentionIds = [..model.RoleMentions.Value];
if (model.Attachments.IsSpecified)
{
@@ -92,12 +92,13 @@ namespace Discord.Rest
if (value.Length > 0)
{
var attachments = ImmutableArray.CreateBuilder<Attachment>(value.Length);
for (int i = 0; i < value.Length; i++)
attachments.Add(Attachment.Create(value[i], Discord));
foreach (var t in value)
attachments.Add(Attachment.Create(t, Discord));
_attachments = attachments.ToImmutable();
}
else
_attachments = ImmutableArray.Create<Attachment>();
_attachments = [];
}
if (model.Embeds.IsSpecified)
@@ -106,12 +107,13 @@ namespace Discord.Rest
if (value.Length > 0)
{
var embeds = ImmutableArray.CreateBuilder<Embed>(value.Length);
for (int i = 0; i < value.Length; i++)
embeds.Add(value[i].ToEntity());
foreach (var t in value)
embeds.Add(t.ToEntity());
_embeds = embeds.ToImmutable();
}
else
_embeds = ImmutableArray.Create<Embed>();
_embeds = [];
}
var guildId = (Channel as IGuildChannel)?.GuildId;
@@ -123,7 +125,7 @@ namespace Discord.Rest
model.Content = text;
}
if (model.ReferencedMessage.IsSpecified && model.ReferencedMessage.Value != null)
if (model.ReferencedMessage is { IsSpecified: true, Value: not null })
{
var refMsg = model.ReferencedMessage.Value;
IUser refMsgAuthor = MessageHelper.GetAuthor(Discord, guild, refMsg.Author.Value, refMsg.WebhookId.ToNullable());
@@ -141,7 +143,7 @@ namespace Discord.Rest
_stickers = stickers.ToImmutable();
}
else
_stickers = ImmutableArray.Create<StickerItem>();
_stickers = [];
}
if (model.Resolved.IsSpecified)

View File

@@ -0,0 +1,198 @@
using System.Collections.Immutable;
using System.Linq;
namespace Discord.Rest;
internal static class MessageComponentExtension
{
internal static IMessageComponent ToModel(this IMessageComponent component)
{
switch (component)
{
case ActionRowComponent actionRow:
return new API.ActionRowComponent(actionRow);
case ButtonComponent btn:
return new API.ButtonComponent(btn);
case SelectMenuComponent select:
return new API.SelectMenuComponent(select);
case TextInputComponent textInput:
return new API.TextInputComponent(textInput);
case TextDisplayComponent textDisplay:
return new API.TextDisplayComponent(textDisplay);
case SectionComponent section:
return new API.SectionComponent(section);
case ThumbnailComponent thumbnail:
return new API.ThumbnailComponent(thumbnail);
case MediaGalleryComponent mediaGallery:
return new API.MediaGalleryComponent(mediaGallery);
case SeparatorComponent separator:
return new API.SeparatorComponent(separator);
case FileComponent file:
return new API.FileComponent(file);
case ContainerComponent container:
return new API.ContainerComponent(container);
}
return null;
}
internal static IMessageComponent ToEntity(this IMessageComponent component)
{
switch (component.Type)
{
case ComponentType.ActionRow:
{
var parsed = (API.ActionRowComponent)component;
return new ActionRowComponent
{
Id = component.Id,
Components = parsed.Components.Select(x => x.ToEntity()).ToImmutableArray()
};
}
case ComponentType.Button:
{
var parsed = (API.ButtonComponent)component;
return new ButtonComponent(
parsed.Style,
parsed.Label.GetValueOrDefault(),
parsed.Emote.IsSpecified
? parsed.Emote.Value.Id.HasValue
? new Emote(parsed.Emote.Value.Id.Value, parsed.Emote.Value.Name, parsed.Emote.Value.Animated.GetValueOrDefault())
: new Emoji(parsed.Emote.Value.Name)
: null,
parsed.CustomId.GetValueOrDefault(),
parsed.Url.GetValueOrDefault(),
parsed.Disabled.GetValueOrDefault(),
parsed.SkuId.ToNullable(),
parsed.Id.ToNullable());
}
case ComponentType.SelectMenu or ComponentType.ChannelSelect or ComponentType.RoleSelect or ComponentType.MentionableSelect or ComponentType.UserSelect:
{
var parsed = (API.SelectMenuComponent)component;
return new SelectMenuComponent(
parsed.CustomId,
parsed.Options?.Select(z => new SelectMenuOption(
z.Label,
z.Value,
z.Description.GetValueOrDefault(),
z.Emoji.IsSpecified
? z.Emoji.Value.Id.HasValue
? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault())
: new Emoji(z.Emoji.Value.Name)
: null,
z.Default.ToNullable())).ToList(),
parsed.Placeholder.GetValueOrDefault(),
parsed.MinValues,
parsed.MaxValues,
parsed.Disabled,
parsed.Type,
parsed.Id.ToNullable(),
parsed.ChannelTypes.GetValueOrDefault(),
parsed.DefaultValues.IsSpecified
? parsed.DefaultValues.Value.Select(x => new SelectMenuDefaultValue(x.Id, x.Type))
: []
);
}
case ComponentType.TextInput:
{
var parsed = (API.TextInputComponent)component;
return new TextInputComponent(parsed.CustomId,
parsed.Label,
parsed.Placeholder.GetValueOrDefault(null),
parsed.MinLength.ToNullable(),
parsed.MaxLength.ToNullable(),
parsed.Style,
parsed.Required.ToNullable(),
parsed.Value.GetValueOrDefault(null),
parsed.Id.ToNullable());
}
case ComponentType.TextDisplay:
{
var parsed = (API.TextDisplayComponent)component;
return new TextDisplayComponent(parsed.Content, parsed.Id.ToNullable());
}
case ComponentType.Section:
{
var parsed = (API.SectionComponent)component;
return new SectionComponent(parsed.Id.ToNullable(),
parsed.Components.Select(x => x.ToEntity()).ToImmutableArray(),
parsed.Accessory.ToEntity());
}
case ComponentType.Thumbnail:
{
var parsed = (API.ThumbnailComponent)component;
return new ThumbnailComponent(parsed.Id.ToNullable(),
parsed.Media.ToEntity(),
parsed.Description.GetValueOrDefault(null),
parsed.IsSpoiler.ToNullable());
}
case ComponentType.MediaGallery:
{
var parsed = (API.MediaGalleryComponent)component;
return new MediaGalleryComponent(
parsed.Items.Select(x => new MediaGalleryItem(x.Media.ToEntity(), x.Description.GetValueOrDefault(null), x.IsSpoiler.GetValueOrDefault(false))).ToList(),
parsed.Id.ToNullable());
}
case ComponentType.Separator:
{
var parsed = (API.SeparatorComponent)component;
return new SeparatorComponent(parsed.IsDivider.ToNullable(), parsed.Spacing.ToNullable(), parsed.Id.ToNullable());
}
case ComponentType.File:
{
var parsed = (API.FileComponent)component;
return new FileComponent(parsed.File.ToEntity(), parsed.IsSpoiler.ToNullable(), parsed.Id.ToNullable());
}
case ComponentType.Container:
{
var parsed = (API.ContainerComponent)component;
return new ContainerComponent(parsed.Components.Select(x => x.ToEntity()).ToImmutableArray(),
parsed.AccentColor.GetValueOrDefault(null),
parsed.IsSpoiler.ToNullable(),
parsed.Id.ToNullable());
}
default:
return null;
}
}
internal static UnfurledMediaItem ToEntity(this API.UnfurledMediaItem mediaItem)
{
return new ResolvedUnfurledMediaItem(mediaItem.Url,
mediaItem.ProxyUrl.GetValueOrDefault(null),
mediaItem.Height.GetValueOrDefault(0).GetValueOrDefault(0),
mediaItem.Width.GetValueOrDefault(0).GetValueOrDefault(0),
mediaItem.ContentType.GetValueOrDefault(null),
mediaItem.LoadingState.GetValueOrDefault(UnfurledMediaItemLoadingState.Unknown));
}
internal static API.UnfurledMediaItem ToModel(this UnfurledMediaItem mediaItem)
{
return new API.UnfurledMediaItem
{
Url = mediaItem.Url
};
}
}

View File

@@ -1,12 +1,13 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.ComponentModel;
namespace Discord.Net.Converters
{
internal class MessageComponentConverter : JsonConverter
{
public static MessageComponentConverter Instance => new MessageComponentConverter();
public static MessageComponentConverter Instance => new ();
public override bool CanRead => true;
public override bool CanWrite => false;
@@ -39,6 +40,27 @@ namespace Discord.Net.Converters
case ComponentType.TextInput:
messageComponent = new API.TextInputComponent();
break;
case ComponentType.TextDisplay:
messageComponent = new API.TextDisplayComponent();
break;
case ComponentType.Thumbnail:
messageComponent = new API.ThumbnailComponent();
break;
case ComponentType.Section:
messageComponent = new API.SectionComponent();
break;
case ComponentType.MediaGallery:
messageComponent = new API.MediaGalleryComponent();
break;
case ComponentType.Separator:
messageComponent = new API.SeparatorComponent();
break;
case ComponentType.File:
messageComponent = new API.FileComponent();
break;
case ComponentType.Container:
messageComponent = new API.ContainerComponent();
break;
}
serializer.Populate(jsonObject.CreateReader(), messageComponent);
return messageComponent;

View File

@@ -82,7 +82,8 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -90,7 +91,7 @@ namespace Discord.WebSocket
if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck)
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -115,6 +116,13 @@ namespace Discord.WebSocket
}
}
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var response = new API.Rest.UploadInteractionFileParams(attachments?.ToArray())
{
Type = InteractionResponseType.ChannelMessageWithSource,
@@ -122,8 +130,8 @@ namespace Discord.WebSocket
AllowedMentions = allowedMentions != null ? allowedMentions?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
IsTTS = isTTS,
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified,
MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Flags = flags,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
@@ -149,7 +157,8 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -157,7 +166,7 @@ namespace Discord.WebSocket
if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck)
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -167,21 +176,28 @@ namespace Discord.WebSocket
Preconditions.ValidatePoll(poll);
// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
if (allowedMentions is { AllowedTypes: not null })
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
allowedMentions.UserIds is { Count: > 0 })
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
allowedMentions.RoleIds is { Count: > 0 })
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
@@ -191,8 +207,8 @@ namespace Discord.WebSocket
AllowedMentions = allowedMentions?.ToModel(),
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
TTS = isTTS,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified,
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = flags,
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
}
};
@@ -250,13 +266,13 @@ namespace Discord.WebSocket
{
var allowedMentions = args.AllowedMentions.Value;
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users)
&& allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
&& allowedMentions.UserIds is { Count: > 0 })
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(args.AllowedMentions));
}
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles)
&& allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
&& allowedMentions.RoleIds is { Count: > 0 })
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(args.AllowedMentions));
}
@@ -273,8 +289,8 @@ namespace Discord.WebSocket
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? []
: Optional<IMessageComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
}
};
@@ -283,7 +299,7 @@ namespace Discord.WebSocket
}
else
{
var attachments = args.Attachments.Value?.ToArray() ?? Array.Empty<FileAttachment>();
var attachments = args.Attachments.Value?.ToArray() ?? [];
var response = new API.Rest.UploadInteractionFileParams(attachments)
{
@@ -292,8 +308,8 @@ namespace Discord.WebSocket
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
MessageComponents = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? []
: Optional<IMessageComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
};
@@ -321,12 +337,13 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -335,19 +352,25 @@ namespace Discord.WebSocket
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.ValidatePoll(poll);
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified,
Flags = flags,
};
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;
return InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}
@@ -362,12 +385,13 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -385,23 +409,24 @@ namespace Discord.WebSocket
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
allowedMentions.UserIds is { Count: > 0 })
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
allowedMentions.RoleIds is { Count: > 0 })
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}
var flags = MessageFlags.None;
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray())
{
Flags = flags,
@@ -409,7 +434,7 @@ namespace Discord.WebSocket
IsTTS = isTTS,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);

View File

@@ -90,13 +90,13 @@ namespace Discord.WebSocket
}
}
internal SocketMessageComponentData(IMessageComponent component, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser)
internal SocketMessageComponentData(IInteractableComponent component, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser)
{
CustomId = component.CustomId;
Type = component.Type;
Value = component.Type == ComponentType.TextInput
? (component as API.TextInputComponent).Value.Value
? ((TextInputComponent)component).Value
: null;
if (component is API.SelectMenuComponent select)

View File

@@ -79,7 +79,8 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -87,7 +88,7 @@ namespace Discord.WebSocket
if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck)
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -111,6 +112,12 @@ namespace Discord.WebSocket
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var response = new API.Rest.UploadInteractionFileParams(attachments?.ToArray())
{
@@ -119,8 +126,8 @@ namespace Discord.WebSocket
AllowedMentions = allowedMentions != null ? allowedMentions?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
IsTTS = isTTS,
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified,
MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Flags = flags,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
@@ -136,7 +143,7 @@ namespace Discord.WebSocket
HasResponded = true;
}
/// <inheritdoc/>
/// <inheritdoc cref="IDiscordInteraction.RespondAsync"/>
public override async Task RespondAsync(
string text = null,
Embed[] embeds = null,
@@ -146,7 +153,8 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -154,7 +162,7 @@ namespace Discord.WebSocket
if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck)
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -164,21 +172,28 @@ namespace Discord.WebSocket
Preconditions.ValidatePoll(poll);
// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
if (allowedMentions is { AllowedTypes: not null })
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
allowedMentions.UserIds is { Count: > 0 })
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
allowedMentions.RoleIds is { Count: > 0 })
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
@@ -188,8 +203,8 @@ namespace Discord.WebSocket
AllowedMentions = allowedMentions?.ToModel(),
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
TTS = isTTS,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified,
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = flags,
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
}
};
@@ -270,9 +285,11 @@ namespace Discord.WebSocket
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? []
: Optional<IMessageComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified
? args.Flags.Value ?? Optional<MessageFlags>.Unspecified
: Optional<MessageFlags>.Unspecified
}
};
@@ -289,8 +306,8 @@ namespace Discord.WebSocket
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
MessageComponents = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
: Optional<API.ActionRowComponent[]>.Unspecified,
? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? []
: Optional<IMessageComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
};
@@ -318,12 +335,13 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -332,20 +350,24 @@ namespace Discord.WebSocket
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.ValidatePoll(poll);
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified,
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Flags = flags,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;
return InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}
@@ -360,12 +382,13 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -395,11 +418,13 @@ namespace Discord.WebSocket
}
}
var flags = MessageFlags.None;
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray())
{
Flags = flags,
@@ -407,7 +432,7 @@ namespace Discord.WebSocket
IsTTS = isTTS,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);

View File

@@ -1,8 +1,6 @@
using System;
using Discord.Rest;
using System.Collections.Generic;
using System.Linq;
using DataModel = Discord.API.MessageComponentInteractionData;
using InterationModel = Discord.API.Interaction;
using Model = Discord.API.ModalInteractionData;
namespace Discord.WebSocket
@@ -26,7 +24,7 @@ namespace Discord.WebSocket
{
CustomId = model.CustomId;
Components = model.Components
.SelectMany(x => x.Components)
.SelectMany(x => x.Components.Select(y => y.ToEntity()).OfType<IInteractableComponent>())
.Select(x => new SocketMessageComponentData(x, discord, state, guild, dmUser))
.ToArray();
}

View File

@@ -91,15 +91,15 @@ namespace Discord.WebSocket
/// </returns>
public Task RespondAsync(RequestOptions options = null, params AutocompleteResult[] result)
=> RespondAsync(result, options);
public override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
public override Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
public override Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> throw new NotSupportedException("Autocomplete interactions don't support this method!");
/// <inheritdoc/>

View File

@@ -77,7 +77,8 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -85,7 +86,7 @@ namespace Discord.WebSocket
if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck)
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -110,6 +111,13 @@ namespace Discord.WebSocket
}
}
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
@@ -119,8 +127,8 @@ namespace Discord.WebSocket
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
TTS = isTTS,
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified,
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Flags = flags,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
}
};
@@ -183,7 +191,8 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
@@ -191,7 +200,7 @@ namespace Discord.WebSocket
if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck)
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -216,6 +225,13 @@ namespace Discord.WebSocket
}
}
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var response = new API.Rest.UploadInteractionFileParams(attachments?.ToArray())
{
Type = InteractionResponseType.ChannelMessageWithSource,
@@ -223,8 +239,8 @@ namespace Discord.WebSocket
AllowedMentions = allowedMentions != null ? allowedMentions?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
IsTTS = isTTS,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified,
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = flags,
MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
@@ -250,12 +266,13 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -264,19 +281,24 @@ namespace Discord.WebSocket
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.ValidatePoll(poll);
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var args = new API.Rest.CreateWebhookMessageParams
{
Content = text ?? Optional<string>.Unspecified,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified,
Flags = flags,
};
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;
return InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}
@@ -291,12 +313,13 @@ namespace Discord.WebSocket
MessageComponent components = null,
Embed embed = null,
RequestOptions options = null,
PollProperties poll = null)
PollProperties poll = null,
MessageFlags flags = MessageFlags.None)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");
embeds ??= Array.Empty<Embed>();
embeds ??= [];
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -326,11 +349,13 @@ namespace Discord.WebSocket
}
}
var flags = MessageFlags.None;
if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false)
flags |= MessageFlags.ComponentsV2;
if (ephemeral)
flags |= MessageFlags.Ephemeral;
Preconditions.ValidateMessageFlags(flags);
var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray())
{
Flags = flags,
@@ -338,7 +363,7 @@ namespace Discord.WebSocket
IsTTS = isTTS,
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options);

View File

@@ -216,7 +216,7 @@ namespace Discord.WebSocket
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
/// <exception cref="InvalidOperationException">The parameters provided were invalid or the token was invalid.</exception>
public abstract Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false,
bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Responds to this interaction with a file attachment.
@@ -237,11 +237,11 @@ namespace Discord.WebSocket
/// contains the sent message.
/// </returns>
public async Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
{
using (var file = new FileAttachment(fileStream, fileName))
{
await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
}
@@ -264,11 +264,11 @@ namespace Discord.WebSocket
/// contains the sent message.
/// </returns>
public async Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
{
using (var file = new FileAttachment(filePath, fileName))
{
await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
}
@@ -290,8 +290,8 @@ namespace Discord.WebSocket
/// contains the sent message.
/// </returns>
public Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> RespondWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> RespondWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <summary>
/// Responds to this interaction with a collection of file attachments.
@@ -311,7 +311,7 @@ namespace Discord.WebSocket
/// contains the sent message.
/// </returns>
public abstract Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Sends a followup message for this interaction.
@@ -329,7 +329,7 @@ namespace Discord.WebSocket
/// The sent message.
/// </returns>
public abstract Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Sends a followup message for this interaction.
@@ -349,11 +349,11 @@ namespace Discord.WebSocket
/// The sent message.
/// </returns>
public async Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
{
using (var file = new FileAttachment(fileStream, fileName))
{
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
}
@@ -375,11 +375,11 @@ namespace Discord.WebSocket
/// The sent message.
/// </returns>
public async Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
{
using (var file = new FileAttachment(filePath, fileName))
{
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
}
}
@@ -401,8 +401,8 @@ namespace Discord.WebSocket
/// contains the sent message.
/// </returns>
public Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null)
=> FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None)
=> FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags);
/// <summary>
/// Sends a followup message for this interaction.
@@ -422,7 +422,7 @@ namespace Discord.WebSocket
/// contains the sent message.
/// </returns>
public abstract Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null);
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None);
/// <summary>
/// Gets the original response for this interaction.
@@ -509,26 +509,26 @@ namespace Discord.WebSocket
=> await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false);
/// <inheritdoc/>
async Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components,
Embed embed, RequestOptions options, PollProperties poll)
=> await RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions,
MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text, Embed[] embeds, bool isTTS,
bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
#if NETCOREAPP3_0_OR_GREATER != true
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll)
=> await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false);
async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags)
=> await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false);
#endif
#endregion
}

Some files were not shown because too many files have changed in this diff Show More