@@ -8,7 +8,7 @@ | |||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> | |||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | |||
<PackageId>Discord.Net.Labs.Core</PackageId> | |||
<Version>2.3.3</Version> | |||
<Version>2.3.4</Version> | |||
<Product>Discord.Net.Labs.Core</Product> | |||
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | |||
<PackageIcon>Temporary.png</PackageIcon> | |||
@@ -7,16 +7,21 @@ using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents a <see cref="IMessageComponent"/> Row for child components to live in. | |||
/// </summary> | |||
public class ActionRowComponent : IMessageComponent | |||
{ | |||
[JsonProperty("type")] | |||
/// <inheritdoc/> | |||
public ComponentType Type { get; } = ComponentType.ActionRow; | |||
[JsonProperty("components")] | |||
public IReadOnlyCollection<IMessageComponent> Components { get; internal set; } | |||
/// <summary> | |||
/// The child components in this row. | |||
/// </summary> | |||
public IReadOnlyCollection<ButtonComponent> Components { get; internal set; } | |||
internal ActionRowComponent() { } | |||
internal ActionRowComponent(IReadOnlyCollection<IMessageComponent> components) | |||
internal ActionRowComponent(List<ButtonComponent> components) | |||
{ | |||
this.Components = components; | |||
} | |||
@@ -7,27 +7,45 @@ using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents a <see cref="IMessageComponent"/> Button. | |||
/// </summary> | |||
public class ButtonComponent : IMessageComponent | |||
{ | |||
[JsonProperty("type")] | |||
/// <inheritdoc/> | |||
public ComponentType Type { get; } = ComponentType.Button; | |||
[JsonProperty("style")] | |||
/// <summary> | |||
/// 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> | |||
public ButtonStyle Style { get; } | |||
[JsonProperty("label")] | |||
/// <summary> | |||
/// The label of the button, this is the text that is shown. | |||
/// </summary> | |||
public string Label { get; } | |||
[JsonProperty("emoji")] | |||
/// <summary> | |||
/// A <see cref="IEmote"/> that will be displayed with this button. | |||
/// </summary> | |||
public IEmote Emote { get; } | |||
[JsonProperty("custom_id")] | |||
/// <summary> | |||
/// A unique id that will be sent with a <see cref="IDiscordInteraction"/>. This is how you know what button was pressed. | |||
/// </summary> | |||
public string CustomId { get; } | |||
[JsonProperty("url")] | |||
/// <summary> | |||
/// A URL for a <see cref="ButtonStyle.Link"/> button. | |||
/// </summary> | |||
/// <remarks> | |||
/// You cannot have a button with a <b>URL</b> and a <b>CustomId</b>. | |||
/// </remarks> | |||
public string Url { get; } | |||
[JsonProperty("disabled")] | |||
/// <summary> | |||
/// Whether this button is disabled or not. | |||
/// </summary> | |||
public bool Disabled { get; } | |||
internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool disabled) | |||
@@ -39,5 +57,7 @@ namespace Discord | |||
this.Url = url; | |||
this.Disabled = disabled; | |||
} | |||
} | |||
} |
@@ -6,10 +6,19 @@ using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents a builder for creating a <see cref="MessageComponent"/>. | |||
/// </summary> | |||
public class ComponentBuilder | |||
{ | |||
/// <summary> | |||
/// The max amount of rows a message can have. | |||
/// </summary> | |||
public const int MaxActionRowCount = 5; | |||
/// <summary> | |||
/// Gets or sets the Action Rows for this Component Builder. | |||
/// </summary> | |||
public List<ActionRowBuilder> ActionRows | |||
{ | |||
get => _actionRows; | |||
@@ -25,11 +34,22 @@ namespace Discord | |||
private List<ActionRowBuilder> _actionRows { get; set; } | |||
/// <summary> | |||
/// Adds a button to the specified row. | |||
/// </summary> | |||
/// <param name="label">The label text for the newly added button.</param> | |||
/// <param name="style">The style of this newly added button.</param> | |||
/// <param name="emote">A <see cref="IEmote"/> to be used with this button.</param> | |||
/// <param name="customId">The custom id of the newly added button.</param> | |||
/// <param name="url">A URL to be used only if the <see cref="ButtonStyle"/> is a Link.</param> | |||
/// <param name="disabled">Whether or not the newly created button is disabled.</param> | |||
/// <param name="row">The row the button should be placed on.</param> | |||
/// <returns>The current builder.</returns> | |||
public ComponentBuilder WithButton( | |||
string label, | |||
string customId, | |||
ButtonStyle style = ButtonStyle.Primary, | |||
IEmote emote = null, | |||
string customId = null, | |||
string url = null, | |||
bool disabled = false, | |||
int row = 0) | |||
@@ -45,9 +65,20 @@ namespace Discord | |||
return this.WithButton(button, row); | |||
} | |||
/// <summary> | |||
/// Adds a button to the first row. | |||
/// </summary> | |||
/// <param name="button">The button to add to the first row.</param> | |||
/// <returns>The current builder.</returns> | |||
public ComponentBuilder WithButton(ButtonBuilder button) | |||
=> this.WithButton(button, 0); | |||
/// <summary> | |||
/// Adds a button to the specified row. | |||
/// </summary> | |||
/// <param name="button">The button to add.</param> | |||
/// <param name="row">The row to add the button.</param> | |||
/// <returns>The current builder.</returns> | |||
public ComponentBuilder WithButton(ButtonBuilder button, int row) | |||
{ | |||
var builtButton = button.Build(); | |||
@@ -75,6 +106,10 @@ namespace Discord | |||
return this; | |||
} | |||
/// <summary> | |||
/// Builds this builder into a <see cref="MessageComponent"/> used to send your components. | |||
/// </summary> | |||
/// <returns>A <see cref="MessageComponent"/> that can be sent with <see cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent)"/></returns> | |||
public MessageComponent Build() | |||
{ | |||
if (this._actionRows != null) | |||
@@ -84,10 +119,20 @@ namespace Discord | |||
} | |||
} | |||
/// <summary> | |||
/// Represents a class used to build Action rows. | |||
/// </summary> | |||
public class ActionRowBuilder | |||
{ | |||
/// <summary> | |||
/// The max amount of child components this row can hold. | |||
/// </summary> | |||
public const int MaxChildCount = 5; | |||
public List<IMessageComponent> Components | |||
/// <summary> | |||
/// Gets or sets the components inside this row. | |||
/// </summary> | |||
public List<ButtonComponent> Components | |||
{ | |||
get => _components; | |||
set | |||
@@ -99,33 +144,70 @@ namespace Discord | |||
} | |||
} | |||
private List<IMessageComponent> _components { get; set; } | |||
private List<ButtonComponent> _components { get; set; } | |||
public ActionRowBuilder WithComponents(List<IMessageComponent> components) | |||
/// <summary> | |||
/// Adds a list of components to the current row. | |||
/// </summary> | |||
/// <param name="components">The list of components to add.</param> | |||
/// <returns>The current builder.</returns> | |||
public ActionRowBuilder WithComponents(List<ButtonComponent> components) | |||
{ | |||
this.Components = components; | |||
return this; | |||
} | |||
public ActionRowBuilder WithComponent(IMessageComponent component) | |||
/// <summary> | |||
/// Adds a component at the end of the current row. | |||
/// </summary> | |||
/// <param name="component">The component to add.</param> | |||
/// <returns>The current builder.</returns> | |||
public ActionRowBuilder WithComponent(ButtonComponent component) | |||
{ | |||
if (this.Components == null) | |||
this.Components = new List<IMessageComponent>(); | |||
this.Components = new List<ButtonComponent>(); | |||
this.Components.Add(component); | |||
return this; | |||
} | |||
/// <summary> | |||
/// Builds the current builder to a <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/> | |||
/// </summary> | |||
/// <returns>A <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/></returns> | |||
/// <exception cref="ArgumentNullException"><see cref="Components"/> cannot be null.</exception> | |||
/// <exception cref="ArgumentException">There must be at least 1 component in a row.</exception> | |||
public ActionRowComponent Build() | |||
=> new ActionRowComponent(this._components); | |||
{ | |||
if (this.Components == null) | |||
throw new ArgumentNullException($"{nameof(Components)} cannot be null!"); | |||
if (this.Components.Count == 0) | |||
throw new ArgumentException("There must be at least 1 component in a row"); | |||
return new ActionRowComponent(this._components); | |||
} | |||
} | |||
/// <summary> | |||
/// Represents a class used to build <see cref="ButtonComponent"/>'s. | |||
/// </summary> | |||
public class ButtonBuilder | |||
{ | |||
/// <summary> | |||
/// The max length of a <see cref="ButtonComponent.Label"/>. | |||
/// </summary> | |||
public const int MaxLabelLength = 80; | |||
/// <summary> | |||
/// The max length of a <see cref="ButtonComponent.CustomId"/>. | |||
/// </summary> | |||
public const int MaxCustomIdLength = 100; | |||
/// <summary> | |||
/// Gets or sets the label of the current button. | |||
/// </summary> | |||
public string Label | |||
{ | |||
get => _label; | |||
@@ -139,6 +221,9 @@ namespace Discord | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the custom id of the current button. | |||
/// </summary> | |||
public string CustomId | |||
{ | |||
get => _customId; | |||
@@ -151,15 +236,36 @@ namespace Discord | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the <see cref="ButtonStyle"/> of the current button. | |||
/// </summary> | |||
public ButtonStyle Style { get; set; } | |||
/// <summary> | |||
/// Gets or sets the <see cref="IEmote"/> of the current button. | |||
/// </summary> | |||
public IEmote Emote { get; set; } | |||
/// <summary> | |||
/// Gets or sets the url of the current button. | |||
/// </summary> | |||
public string Url { get; set; } | |||
/// <summary> | |||
/// Gets or sets whether the current button is disabled. | |||
/// </summary> | |||
public bool Disabled { get; set; } | |||
private string _label; | |||
private string _customId; | |||
/// <summary> | |||
/// Creates a button with the <see cref="ButtonStyle.Link"/> style. | |||
/// </summary> | |||
/// <param name="label">The label to use on the newly created link button.</param> | |||
/// <param name="url">The url for this link button to go to.</param> | |||
/// <returns>A builder with the newly created button.</returns> | |||
public static ButtonBuilder CreateLinkButton(string label, string url) | |||
{ | |||
var builder = new ButtonBuilder() | |||
@@ -170,6 +276,12 @@ namespace Discord | |||
return builder; | |||
} | |||
/// <summary> | |||
/// Creates a button with the <see cref="ButtonStyle.Danger"/> style. | |||
/// </summary> | |||
/// <param name="label">The label for this danger button.</param> | |||
/// <param name="customId">The custom id for this danger button.</param> | |||
/// <returns>A builder with the newly created button.</returns> | |||
public static ButtonBuilder CreateDangerButton(string label, string customId) | |||
{ | |||
var builder = new ButtonBuilder() | |||
@@ -180,6 +292,12 @@ namespace Discord | |||
return builder; | |||
} | |||
/// <summary> | |||
/// Creates a button with the <see cref="ButtonStyle.Primary"/> style. | |||
/// </summary> | |||
/// <param name="label">The label for this primary button.</param> | |||
/// <param name="customId">The custom id for this primary button.</param> | |||
/// <returns>A builder with the newly created button.</returns> | |||
public static ButtonBuilder CreatePrimaryButton(string label, string customId) | |||
{ | |||
var builder = new ButtonBuilder() | |||
@@ -190,6 +308,12 @@ namespace Discord | |||
return builder; | |||
} | |||
/// <summary> | |||
/// Creates a button with the <see cref="ButtonStyle.Secondary"/> style. | |||
/// </summary> | |||
/// <param name="label">The label for this secondary button.</param> | |||
/// <param name="customId">The custom id for this secondary button.</param> | |||
/// <returns>A builder with the newly created button.</returns> | |||
public static ButtonBuilder CreateSecondaryButton(string label, string customId) | |||
{ | |||
var builder = new ButtonBuilder() | |||
@@ -200,6 +324,12 @@ namespace Discord | |||
return builder; | |||
} | |||
/// <summary> | |||
/// Creates a button with the <see cref="ButtonStyle.Success"/> style. | |||
/// </summary> | |||
/// <param name="label">The label for this success button.</param> | |||
/// <param name="customId">The custom id for this success button.</param> | |||
/// <returns>A builder with the newly created button.</returns> | |||
public static ButtonBuilder CreateSuccessButton(string label, string customId) | |||
{ | |||
var builder = new ButtonBuilder() | |||
@@ -210,41 +340,78 @@ namespace Discord | |||
return builder; | |||
} | |||
/// <summary> | |||
/// Sets the current buttons label to the specified text. | |||
/// </summary> | |||
/// <param name="label">The text for the label</param> | |||
/// <returns>The current builder.</returns> | |||
public ButtonBuilder WithLabel(string label) | |||
{ | |||
this.Label = label; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Sets the current buttons style. | |||
/// </summary> | |||
/// <param name="style">The style for this builders button.</param> | |||
/// <returns>The current builder.</returns> | |||
public ButtonBuilder WithStyle(ButtonStyle style) | |||
{ | |||
this.Style = style; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Sets the current buttons emote. | |||
/// </summary> | |||
/// <param name="emote">The emote to use for the current button.</param> | |||
/// <returns>The current builder.</returns> | |||
public ButtonBuilder WithEmote(IEmote emote) | |||
{ | |||
this.Emote = emote; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Sets the current buttons url. | |||
/// </summary> | |||
/// <param name="url">The url to use for the current button.</param> | |||
/// <returns>The current builder.</returns> | |||
public ButtonBuilder WithUrl(string url) | |||
{ | |||
this.Url = url; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Sets the custom id of the current button. | |||
/// </summary> | |||
/// <param name="id">The id to use for the current button.</param> | |||
/// <returns>The current builder.</returns> | |||
public ButtonBuilder WithCustomId(string id) | |||
{ | |||
this.CustomId = id; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Sets whether the current button is disabled. | |||
/// </summary> | |||
/// <param name="disabled">Whether the current button is disabled or not.</param> | |||
/// <returns>The current builder.</returns> | |||
public ButtonBuilder WithDisabled(bool disabled) | |||
{ | |||
this.Disabled = disabled; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Builds this builder into a <see cref="ButtonComponent"/> to be used in a <see cref="ComponentBuilder"/>. | |||
/// </summary> | |||
/// <returns>A <see cref="ButtonComponent"/> to be used in a <see cref="ComponentBuilder"/>.</returns> | |||
/// <exception cref="InvalidOperationException">A button cannot contain a URL and a CustomId.</exception> | |||
/// <exception cref="ArgumentException">A button must have an Emote or a label.</exception> | |||
public ButtonComponent Build() | |||
{ | |||
if (string.IsNullOrEmpty(this.Label) && this.Emote == null) | |||
@@ -255,7 +422,8 @@ namespace Discord | |||
if (this.Style == ButtonStyle.Link && !string.IsNullOrEmpty(this.CustomId)) | |||
this.CustomId = null; | |||
else if (!string.IsNullOrEmpty(this.Url)) | |||
else if (this.Style != ButtonStyle.Link && !string.IsNullOrEmpty(this.Url)) // Thanks 𝑴𝒓𝑪𝒂𝒌𝒆𝑺𝒍𝒂𝒚𝒆𝒓 :D | |||
this.Url = null; | |||
return new ButtonComponent(this.Style, this.Label, this.Emote, this.CustomId, this.Url, this.Disabled); | |||
@@ -6,19 +6,25 @@ using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents a component object used to send components with messages. | |||
/// </summary> | |||
public class MessageComponent | |||
{ | |||
public IReadOnlyCollection<IMessageComponent> Components { get; } | |||
/// <summary> | |||
/// The components to be used in a message. | |||
/// </summary> | |||
public IReadOnlyCollection<ActionRowComponent> Components { get; } | |||
internal MessageComponent(List<ActionRowComponent> components) | |||
{ | |||
this.Components = components; | |||
} | |||
/// <summary> | |||
/// Returns a empty <see cref="MessageComponent"/>. | |||
/// </summary> | |||
internal static MessageComponent Empty | |||
=> new MessageComponent(new List<ActionRowComponent>()); | |||
internal IMessageComponent[] ToModel() | |||
=> this.Components.ToArray(); | |||
} | |||
} |
@@ -21,5 +21,10 @@ namespace Discord | |||
/// Gets or sets the embed the message should display. | |||
/// </summary> | |||
public Optional<Embed> Embed { get; set; } | |||
/// <summary> | |||
/// Gets or sets the components for this message. | |||
/// </summary> | |||
public Optional<MessageComponent> Components { get; set; } | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.API | |||
{ | |||
internal class ActionRowComponent | |||
{ | |||
[JsonProperty("type")] | |||
public ComponentType Type { get; set; } | |||
[JsonProperty("components")] | |||
public List<ButtonComponent> Components { get; set; } | |||
internal ActionRowComponent() { } | |||
internal ActionRowComponent(Discord.ActionRowComponent c) | |||
{ | |||
this.Type = c.Type; | |||
this.Components = c.Components?.Select(x => new ButtonComponent(x)).ToList(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.API | |||
{ | |||
internal class ButtonComponent | |||
{ | |||
[JsonProperty("type")] | |||
public ComponentType Type { get; set; } | |||
[JsonProperty("style")] | |||
public ButtonStyle Style { get; set; } | |||
[JsonProperty("label")] | |||
public Optional<string> Label { get; set; } | |||
[JsonProperty("emoji")] | |||
public Optional<Emoji> Emote { get; set; } | |||
[JsonProperty("custom_id")] | |||
public Optional<string> CustomId { get; set; } | |||
[JsonProperty("url")] | |||
public Optional<string> Url { get; set; } | |||
[JsonProperty("disabled")] | |||
public Optional<bool> Disabled { get; set; } | |||
public ButtonComponent() { } | |||
public ButtonComponent(Discord.ButtonComponent c) | |||
{ | |||
this.Type = c.Type; | |||
this.Style = c.Style; | |||
this.Label = c.Label; | |||
this.CustomId = c.CustomId; | |||
this.Url = c.Url; | |||
this.Disabled = c.Disabled; | |||
if (c.Emote != null) | |||
{ | |||
if (c.Emote is Emote e) | |||
{ | |||
this.Emote = new Emoji() | |||
{ | |||
Name = e.Name, | |||
Animated = e.Animated, | |||
Id = e.Id, | |||
}; | |||
} | |||
else | |||
{ | |||
this.Emote = new Emoji() | |||
{ | |||
Name = c.Emote.Name | |||
}; | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -26,7 +26,7 @@ namespace Discord.API | |||
public Optional<int> Flags { get; set; } | |||
[JsonProperty("components")] | |||
public Optional<IMessageComponent[]> Components { get; set; } | |||
public Optional<API.ActionRowComponent[]> Components { get; set; } | |||
public InteractionApplicationCommandCallbackData() { } | |||
public InteractionApplicationCommandCallbackData(string text) | |||
@@ -59,6 +59,6 @@ namespace Discord.API | |||
[JsonProperty("referenced_message")] | |||
public Optional<Message> ReferencedMessage { get; set; } | |||
[JsonProperty("components")] | |||
public Optional<IMessageComponent[]> Components { get; set; } | |||
public Optional<API.ActionRowComponent[]> Components { get; set; } | |||
} | |||
} |
@@ -25,7 +25,7 @@ namespace Discord.API.Rest | |||
public Optional<MessageReference> MessageReference { get; set; } | |||
[JsonProperty("components")] | |||
public Optional<IMessageComponent[]> Components { get; set; } | |||
public Optional<API.ActionRowComponent[]> Components { get; set; } | |||
public CreateMessageParams(string content) | |||
{ | |||
Content = content; | |||
@@ -31,7 +31,7 @@ namespace Discord.API.Rest | |||
public Optional<int> Flags { get; set; } | |||
[JsonProperty("components")] | |||
public Optional<IMessageComponent[]> Components { get; set; } | |||
public Optional<API.ActionRowComponent[]> Components { get; set; } | |||
public CreateWebhookMessageParams(string content) | |||
{ | |||
@@ -1,4 +1,4 @@ | |||
#pragma warning disable CS1591 | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Rest | |||
@@ -10,5 +10,7 @@ namespace Discord.API.Rest | |||
public Optional<string> Content { get; set; } | |||
[JsonProperty("embed")] | |||
public Optional<Embed> Embed { get; set; } | |||
[JsonProperty("components")] | |||
public Optional<API.ActionRowComponent[]> Components { get; set; } | |||
} | |||
} |
@@ -21,7 +21,7 @@ namespace Discord.API.Rest | |||
public Optional<Embed> Embed { get; set; } | |||
public Optional<AllowedMentions> AllowedMentions { get; set; } | |||
public Optional<MessageReference> MessageReference { get; set; } | |||
public Optional<IMessageComponent[]> MessageComponent { get; set; } | |||
public Optional<ActionRowComponent[]> MessageComponent { get; set; } | |||
public bool IsSpoiler { get; set; } = false; | |||
public UploadFileParams(Stream file) | |||
@@ -9,9 +9,11 @@ | |||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | |||
<PackageIcon>Temporary.png</PackageIcon> | |||
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> | |||
<Version>2.3.2</Version> | |||
<Version>2.3.5</Version> | |||
<PackageId>Discord.Net.Labs.Rest</PackageId> | |||
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | |||
<AssemblyVersion>2.3.4</AssemblyVersion> | |||
<FileVersion>2.3.4</FileVersion> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
@@ -944,7 +944,7 @@ namespace Discord.API | |||
public async Task CreateInteractionResponse(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) | |||
{ | |||
if(response.Data.IsSpecified && response.Data.Value.Content.IsSpecified) | |||
Preconditions.AtMost(response.Data.Value.Content.Value.Length, 2000, nameof(response.Data.Value.Content)); | |||
Preconditions.AtMost(response.Data.Value.Content.Value?.Length ?? 0, 2000, nameof(response.Data.Value.Content)); | |||
options = RequestOptions.CreateOrClone(options); | |||
@@ -221,7 +221,7 @@ namespace Discord.Rest | |||
} | |||
} | |||
var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel(), Components = component?.ToModel() }; | |||
var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel(), Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified }; | |||
var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | |||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||
} | |||
@@ -281,7 +281,7 @@ namespace Discord.Rest | |||
} | |||
} | |||
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional<API.Embed>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, MessageComponent = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified, IsSpoiler = isSpoiler }; | |||
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional<API.Embed>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, MessageComponent = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, IsSpoiler = isSpoiler }; | |||
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | |||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||
} | |||
@@ -41,7 +41,8 @@ namespace Discord.Rest | |||
var apiArgs = new API.Rest.ModifyMessageParams | |||
{ | |||
Content = args.Content, | |||
Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create<API.Embed>() | |||
Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create<API.Embed>(), | |||
Components = args?.Components.GetValueOrDefault()?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||
}; | |||
return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | |||
} | |||
@@ -129,9 +129,16 @@ namespace Discord.Rest | |||
if (model.Components.IsSpecified) | |||
{ | |||
Components = model.Components.Value.Select(x => | |||
(x as Newtonsoft.Json.Linq.JToken).ToObject<ActionRowComponent>() | |||
).ToList(); | |||
Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select(x => | |||
new ButtonComponent( | |||
x.Style, | |||
x.Label.GetValueOrDefault(), | |||
x.Emote.IsSpecified ? x.Emote.Value.Id.HasValue ? new Emote(x.Emote.Value.Id.Value, x.Emote.Value.Name, x.Emote.Value.Animated.GetValueOrDefault()) : new Emoji(x.Emote.Value.Name) : null, | |||
x.CustomId.GetValueOrDefault(), | |||
x.Url.GetValueOrDefault(), | |||
x.Disabled.GetValueOrDefault()) | |||
).ToList() | |||
)).ToList(); | |||
} | |||
else | |||
Components = new List<ActionRowComponent>(); | |||
@@ -13,6 +13,9 @@ namespace Discord.API.Gateway | |||
[JsonProperty("id")] | |||
public ulong Id { get; set; } | |||
[JsonProperty("application_id")] | |||
public ulong ApplicationId { get; set; } | |||
[JsonProperty("type")] | |||
public InteractionType Type { get; set; } | |||
@@ -20,18 +23,25 @@ namespace Discord.API.Gateway | |||
public Optional<object> Data { get; set; } | |||
[JsonProperty("guild_id")] | |||
public ulong GuildId { get; set; } | |||
public Optional<ulong> GuildId { get; set; } | |||
[JsonProperty("channel_id")] | |||
public ulong ChannelId { get; set; } | |||
public Optional<ulong> ChannelId { get; set; } | |||
[JsonProperty("member")] | |||
public GuildMember Member { get; set; } | |||
public Optional<GuildMember> Member { get; set; } | |||
[JsonProperty("user")] | |||
public Optional<User> User { get; set; } | |||
[JsonProperty("token")] | |||
public string Token { get; set; } | |||
[JsonProperty("version")] | |||
public int Version { get; set; } | |||
[JsonProperty("message")] | |||
public Optional<Message> Message { get; set; } | |||
} | |||
} |
@@ -8,7 +8,7 @@ | |||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> | |||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | |||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
<Version>2.3.2</Version> | |||
<Version>2.3.4</Version> | |||
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | |||
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> | |||
<PackageIcon>Temporary.png</PackageIcon> | |||
@@ -1785,26 +1785,33 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<API.Gateway.InteractionCreated>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | |||
if (data.Member.IsSpecified && data.ChannelId.IsSpecified) | |||
{ | |||
var guild = channel.Guild; | |||
if (!guild.IsSynced) | |||
if (State.GetChannel(data.ChannelId.Value) is SocketGuildChannel channel) | |||
{ | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
var guild = channel.Guild; | |||
if (!guild.IsSynced) | |||
{ | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
var interaction = SocketInteraction.Create(this, data); | |||
var interaction = SocketInteraction.Create(this, data); | |||
if (this.AlwaysAcknowledgeInteractions) | |||
await interaction.AcknowledgeAsync().ConfigureAwait(false); | |||
if (this.AlwaysAcknowledgeInteractions) | |||
await interaction.AcknowledgeAsync().ConfigureAwait(false); | |||
await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); | |||
await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); | |||
return; | |||
} | |||
} | |||
else | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
// DM TODO | |||
} | |||
} | |||
break; | |||
@@ -10,10 +10,21 @@ using Discord.Rest; | |||
namespace Discord.WebSocket | |||
{ | |||
/// <summary> | |||
/// Represents a Websocket-based interaction type for Message Components. | |||
/// </summary> | |||
public class SocketMessageComponent : SocketInteraction | |||
{ | |||
/// <summary> | |||
/// The data received with this interaction, contains the button that was clicked. | |||
/// </summary> | |||
new public SocketMessageComponentData Data { get; } | |||
/// <summary> | |||
/// The message that contained the trigger for this interaction. | |||
/// </summary> | |||
public SocketMessage Message { get; private set; } | |||
internal SocketMessageComponent(DiscordSocketClient client, Model model) | |||
: base(client, model.Id) | |||
{ | |||
@@ -22,6 +33,8 @@ namespace Discord.WebSocket | |||
: null; | |||
this.Data = new SocketMessageComponentData(dataModel); | |||
} | |||
new internal static SocketMessageComponent Create(DiscordSocketClient client, Model model) | |||
@@ -31,6 +44,23 @@ namespace Discord.WebSocket | |||
return entity; | |||
} | |||
internal override void Update(Model model) | |||
{ | |||
base.Update(model); | |||
if (model.Message.IsSpecified) | |||
{ | |||
if (this.Message == null) | |||
{ | |||
this.Message = SocketMessage.Create(this.Discord, this.Discord.State, this.User, this.Channel, model.Message.Value); | |||
} | |||
else | |||
{ | |||
this.Message.Update(this.Discord.State, model.Message.Value); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// Responds to an Interaction. | |||
/// <para> | |||
@@ -51,7 +81,6 @@ namespace Discord.WebSocket | |||
/// </returns> | |||
/// <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 override async Task<RestUserMessage> RespondAsync(string text = null, bool isTTS = false, Embed embed = null, InteractionResponseType type = InteractionResponseType.ChannelMessageWithSource, | |||
bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null) | |||
{ | |||
@@ -94,7 +123,7 @@ namespace Discord.WebSocket | |||
? new API.Embed[] { embed.ToModel() } | |||
: Optional<API.Embed[]>.Unspecified, | |||
TTS = isTTS, | |||
Components = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified | |||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||
} | |||
}; | |||
@@ -134,7 +163,7 @@ namespace Discord.WebSocket | |||
Embeds = embed != null | |||
? new API.Embed[] { embed.ToModel() } | |||
: Optional<API.Embed[]>.Unspecified, | |||
Components = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified | |||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||
}; | |||
if (ephemeral) | |||
@@ -7,6 +7,9 @@ using Model = Discord.API.MessageComponentInteractionData; | |||
namespace Discord.WebSocket | |||
{ | |||
/// <summary> | |||
/// Represents the data sent with a <see cref="InteractionType.MessageComponent"/>. | |||
/// </summary> | |||
public class SocketMessageComponentData | |||
{ | |||
/// <summary> | |||
@@ -21,7 +21,7 @@ namespace Discord.WebSocket | |||
(model.Data.Value as JToken).ToObject<DataModel>() | |||
: null; | |||
Data = SocketSlashCommandData.Create(client, dataModel, model.GuildId); | |||
Data = SocketSlashCommandData.Create(client, dataModel, model.Id); | |||
} | |||
new internal static SocketInteraction Create(DiscordSocketClient client, Model model) | |||
@@ -37,7 +37,7 @@ namespace Discord.WebSocket | |||
(model.Data.Value as JToken).ToObject<DataModel>() | |||
: null; | |||
this.Data.Update(data, model.GuildId); | |||
this.Data.Update(data); | |||
base.Update(model); | |||
} | |||
@@ -107,7 +107,7 @@ namespace Discord.WebSocket | |||
? new API.Embed[] { embed.ToModel() } | |||
: Optional<API.Embed[]>.Unspecified, | |||
TTS = isTTS, | |||
Components = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified | |||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||
} | |||
}; | |||
@@ -146,7 +146,7 @@ namespace Discord.WebSocket | |||
Embeds = embed != null | |||
? new API.Embed[] { embed.ToModel() } | |||
: Optional<API.Embed[]>.Unspecified, | |||
Components = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified | |||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||
}; | |||
if (ephemeral) | |||
@@ -15,27 +15,24 @@ namespace Discord.WebSocket | |||
/// </summary> | |||
public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; private set; } | |||
private ulong guildId; | |||
internal SocketSlashCommandData(DiscordSocketClient client, ulong id) | |||
: base(client, id) | |||
{ | |||
} | |||
internal static SocketSlashCommandData Create(DiscordSocketClient client, Model model, ulong guildId) | |||
internal static SocketSlashCommandData Create(DiscordSocketClient client, Model model, ulong id) | |||
{ | |||
var entity = new SocketSlashCommandData(client, model.Id); | |||
entity.Update(model, guildId); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
internal void Update(Model model, ulong guildId) | |||
internal void Update(Model model) | |||
{ | |||
this.Name = model.Name; | |||
this.guildId = guildId; | |||
this.Options = model.Options.Any() | |||
? model.Options.Select(x => new SocketSlashCommandDataOption(x, this.Discord, guildId)).ToImmutableArray() | |||
? model.Options.Select(x => new SocketSlashCommandDataOption(x, this.Discord)).ToImmutableArray() | |||
: null; | |||
} | |||
@@ -22,18 +22,16 @@ namespace Discord.WebSocket | |||
public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; private set; } | |||
private DiscordSocketClient discord; | |||
private ulong guild; | |||
internal SocketSlashCommandDataOption() { } | |||
internal SocketSlashCommandDataOption(Model model, DiscordSocketClient discord, ulong guild) | |||
internal SocketSlashCommandDataOption(Model model, DiscordSocketClient discord) | |||
{ | |||
this.Name = model.Name; | |||
this.Value = model.Value.IsSpecified ? model.Value.Value : null; | |||
this.discord = discord; | |||
this.guild = guild; | |||
this.Options = model.Options.Any() | |||
? model.Options.Select(x => new SocketSlashCommandDataOption(x, discord, guild)).ToImmutableArray() | |||
? model.Options.Select(x => new SocketSlashCommandDataOption(x, discord)).ToImmutableArray() | |||
: null; | |||
} | |||
@@ -49,7 +47,7 @@ namespace Discord.WebSocket | |||
{ | |||
if (option.Value is ulong id) | |||
{ | |||
var guild = option.discord.GetGuild(option.guild); | |||
var guild = option.discord.GetGuild(id); | |||
if (guild == null) | |||
return null; | |||
@@ -64,7 +62,7 @@ namespace Discord.WebSocket | |||
{ | |||
if (option.Value is ulong id) | |||
{ | |||
var guild = option.discord.GetGuild(option.guild); | |||
var guild = option.discord.GetGuild(id); | |||
if (guild == null) | |||
return null; | |||
@@ -79,7 +77,7 @@ namespace Discord.WebSocket | |||
{ | |||
if(option.Value is ulong id) | |||
{ | |||
var guild = option.discord.GetGuild(option.guild); | |||
var guild = option.discord.GetGuild(id); | |||
if (guild == null) | |||
return null; | |||
@@ -14,21 +14,14 @@ namespace Discord.WebSocket | |||
public abstract class SocketInteraction : SocketEntity<ulong>, IDiscordInteraction | |||
{ | |||
/// <summary> | |||
/// The <see cref="SocketGuild"/> this interaction was used in. | |||
/// The <see cref="ISocketMessageChannel"/> this interaction was used in. | |||
/// </summary> | |||
public SocketGuild Guild | |||
=> Discord.GetGuild(GuildId); | |||
public ISocketMessageChannel Channel { get; private set; } | |||
/// <summary> | |||
/// The <see cref="SocketTextChannel"/> this interaction was used in. | |||
/// The <see cref="SocketUser"/> who triggered this interaction. | |||
/// </summary> | |||
public SocketTextChannel Channel | |||
=> Guild.GetTextChannel(ChannelId); | |||
/// <summary> | |||
/// The <see cref="SocketGuildUser"/> who triggered this interaction. | |||
/// </summary> | |||
public SocketGuildUser User { get; private set; } | |||
public SocketUser User { get; private set; } | |||
/// <summary> | |||
/// The type of this interaction. | |||
@@ -58,9 +51,8 @@ namespace Discord.WebSocket | |||
public bool IsValidToken | |||
=> CheckToken(); | |||
private ulong GuildId { get; set; } | |||
private ulong ChannelId { get; set; } | |||
private ulong UserId { get; set; } | |||
private ulong? GuildId { get; set; } | |||
private ulong? ChannelId { get; set; } | |||
internal SocketInteraction(DiscordSocketClient client, ulong id) | |||
: base(client, id) | |||
@@ -83,15 +75,35 @@ namespace Discord.WebSocket | |||
? model.Data.Value | |||
: null; | |||
this.GuildId = model.GuildId; | |||
this.ChannelId = model.ChannelId; | |||
this.GuildId = model.GuildId.ToNullable(); | |||
this.ChannelId = model.ChannelId.ToNullable(); | |||
this.Token = model.Token; | |||
this.Version = model.Version; | |||
this.UserId = model.Member.User.Id; | |||
this.Type = model.Type; | |||
if (this.User == null) | |||
this.User = SocketGuildUser.Create(this.Guild, Discord.State, model.Member); // Change from getter. | |||
{ | |||
if (model.Member.IsSpecified && model.GuildId.IsSpecified) | |||
{ | |||
this.User = SocketGuildUser.Create(Discord.State.GetGuild(this.GuildId.Value), Discord.State, model.Member.Value); | |||
} | |||
else | |||
{ | |||
this.User = SocketGlobalUser.Create(this.Discord, this.Discord.State, model.User.Value); | |||
} | |||
} | |||
if (this.Channel == null) | |||
{ | |||
if (model.ChannelId.IsSpecified) | |||
{ | |||
this.Channel = Discord.State.GetChannel(model.ChannelId.Value) as ISocketMessageChannel; | |||
} | |||
else | |||
{ | |||
this.Channel = Discord.State.GetDMChannel(this.User.Id); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
@@ -162,9 +162,16 @@ namespace Discord.WebSocket | |||
if (model.Components.IsSpecified) | |||
{ | |||
Components = model.Components.Value.Select(x => | |||
(x as Newtonsoft.Json.Linq.JToken).ToObject<ActionRowComponent>() | |||
).ToList(); | |||
Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select(x => | |||
new ButtonComponent( | |||
x.Style, | |||
x.Label.GetValueOrDefault(), | |||
x.Emote.IsSpecified ? x.Emote.Value.Id.HasValue ? new Emote(x.Emote.Value.Id.Value, x.Emote.Value.Name, x.Emote.Value.Animated.GetValueOrDefault()) : new Emoji(x.Emote.Value.Name) : null, | |||
x.CustomId.GetValueOrDefault(), | |||
x.Url.GetValueOrDefault(), | |||
x.Disabled.GetValueOrDefault()) | |||
).ToList() | |||
)).ToList(); | |||
} | |||
else | |||
Components = new List<ActionRowComponent>(); | |||
@@ -2,7 +2,7 @@ | |||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | |||
<metadata> | |||
<id>Discord.Net.Labs</id> | |||
<version>2.3.1$suffix$</version> | |||
<version>2.3.3$suffix$</version> | |||
<title>Discord.Net Labs</title> | |||
<authors>Discord.Net Contributors</authors> | |||
<owners>quinchs</owners> | |||
@@ -14,23 +14,23 @@ | |||
<iconUrl>https://avatars.githubusercontent.com/u/84047264</iconUrl> | |||
<dependencies> | |||
<group targetFramework="net461"> | |||
<dependency id="Discord.Net.Labs.Core" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Rest" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.WebSocket" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Core" version="2.3.4$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Rest" version="2.3.5$suffix$" /> | |||
<dependency id="Discord.Net.Labs.WebSocket" version="2.3.4$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Commands" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Webhook" version="2.3.1$suffix$" /> | |||
</group> | |||
<group targetFramework="netstandard2.0"> | |||
<dependency id="Discord.Net.Labs.Core" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Rest" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.WebSocket" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Core" version="2.3.4$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Rest" version="2.3.5$suffix$" /> | |||
<dependency id="Discord.Net.Labs.WebSocket" version="2.3.4$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Commands" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Webhook" version="2.3.1$suffix$" /> | |||
</group> | |||
<group targetFramework="netstandard2.1"> | |||
<dependency id="Discord.Net.Labs.Core" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Rest" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.WebSocket" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Core" version="2.3.4$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Rest" version="2.3.5$suffix$" /> | |||
<dependency id="Discord.Net.Labs.WebSocket" version="2.3.4$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Commands" version="2.3.1$suffix$" /> | |||
<dependency id="Discord.Net.Labs.Webhook" version="2.3.1$suffix$" /> | |||
</group> | |||