using Discord.Rest; using System; using System.Threading.Tasks; using Model = Discord.API.Interaction; using DataModel = Discord.API.ApplicationCommandInteractionData; using System.IO; using System.Collections.Generic; using Discord.Net; namespace Discord.WebSocket { /// /// Represents an Interaction received over the gateway. /// public abstract class SocketInteraction : SocketEntity, IDiscordInteraction { #region SocketInteraction /// /// Gets the this interaction was used in. /// /// /// If the channel isn't cached or the bot doesn't have access to it then /// this property will be . /// public ISocketMessageChannel Channel { get; private set; } /// /// Gets the who triggered this interaction. /// public SocketUser User { get; private set; } /// public InteractionType Type { get; private set; } /// public string Token { get; private set; } /// public IDiscordInteractionData Data { get; private set; } /// public string UserLocale { get; private set; } /// public string GuildLocale { get; private set; } /// public int Version { get; private set; } /// public DateTimeOffset CreatedAt { get; private set; } /// public abstract bool HasResponded { get; internal set; } /// /// Gets whether or not the token used to respond to this interaction is valid. /// public bool IsValidToken => InteractionHelper.CanRespondOrFollowup(this); /// public bool IsDMInteraction { get; private set; } private ulong? _channelId; internal SocketInteraction(DiscordSocketClient client, ulong id, ISocketMessageChannel channel, SocketUser user) : base(client, id) { Channel = channel; User = user; CreatedAt = client.UseInteractionSnowflakeDate ? SnowflakeUtils.FromSnowflake(Id) : DateTime.UtcNow; } internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel, SocketUser user) { if (model.Type == InteractionType.ApplicationCommand) { var dataModel = model.Data.IsSpecified ? (DataModel)model.Data.Value : null; if (dataModel == null) return null; return dataModel.Type switch { ApplicationCommandType.Slash => SocketSlashCommand.Create(client, model, channel, user), ApplicationCommandType.Message => SocketMessageCommand.Create(client, model, channel, user), ApplicationCommandType.User => SocketUserCommand.Create(client, model, channel, user), _ => null }; } if (model.Type == InteractionType.MessageComponent) return SocketMessageComponent.Create(client, model, channel, user); if (model.Type == InteractionType.ApplicationCommandAutocomplete) return SocketAutocompleteInteraction.Create(client, model, channel, user); if (model.Type == InteractionType.ModalSubmit) return SocketModal.Create(client, model, channel, user); return null; } internal virtual void Update(Model model) { IsDMInteraction = !model.GuildId.IsSpecified; _channelId = model.ChannelId.ToNullable(); Data = model.Data.IsSpecified ? model.Data.Value : null; Token = model.Token; Version = model.Version; Type = model.Type; UserLocale = model.UserLocale.IsSpecified ? model.UserLocale.Value : null; GuildLocale = model.GuildLocale.IsSpecified ? model.GuildLocale.Value : null; } /// /// Responds to an Interaction with type . /// /// The text of the message to be sent. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// The request options for this response. /// Message content is too long, length must be less or equal to . /// The parameters provided were invalid or the token was invalid. 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); /// /// Responds to this interaction with a file attachment. /// /// The file to upload. /// The file name of the attachment. /// The text of the message to be sent. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// The request options for this response. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// 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) { using (var file = new FileAttachment(fileStream, fileName)) { await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); } } /// /// Responds to this interaction with a file attachment. /// /// The file to upload. /// The file name of the attachment. /// The text of the message to be sent. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// The request options for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// 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) { using (var file = new FileAttachment(filePath, fileName)) { await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); } } /// /// Responds to this interaction with a file attachment. /// /// The attachment containing the file and description. /// The text of the message to be sent. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// The request options for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// 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) => RespondWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); /// /// Responds to this interaction with a collection of file attachments. /// /// A collection of attachments to upload. /// The text of the message to be sent. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// The request options for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// public abstract Task RespondWithFilesAsync(IEnumerable 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); /// /// Sends a followup message for this interaction. /// /// The text of the message to be sent. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// The request options for this response. /// /// The sent message. /// public abstract Task 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); /// /// Sends a followup message for this interaction. /// /// The text of the message to be sent. /// The file to upload. /// The file name of the attachment. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// The request options for this response. /// /// The sent message. /// public async Task 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) { using (var file = new FileAttachment(fileStream, fileName)) { return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); } } /// /// Sends a followup message for this interaction. /// /// The text of the message to be sent. /// The file to upload. /// The file name of the attachment. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// The request options for this response. /// /// The sent message. /// public async Task 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) { using (var file = new FileAttachment(filePath, fileName)) { return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); } } /// /// Sends a followup message for this interaction. /// /// The attachment containing the file and description. /// The text of the message to be sent. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// The request options for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// public Task 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) => FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); /// /// Sends a followup message for this interaction. /// /// A collection of attachments to upload. /// The text of the message to be sent. /// A array of embeds to send with this response. Max 10. /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// The request options for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// public abstract Task FollowupWithFilesAsync(IEnumerable 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); /// /// Gets the original response for this interaction. /// /// The request options for this request. /// A that represents the initial response. public Task GetOriginalResponseAsync(RequestOptions options = null) => InteractionHelper.GetOriginalResponseAsync(Discord, Channel, this, options); /// /// Edits original response for this interaction. /// /// A delegate containing the properties to modify the message with. /// The request options for this request. /// A that represents the initial response. public async Task ModifyOriginalResponseAsync(Action func, RequestOptions options = null) { var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options); return RestInteractionMessage.Create(Discord, model, Token, Channel); } /// public Task DeleteOriginalResponseAsync(RequestOptions options = null) => InteractionHelper.DeleteInteractionResponseAsync(Discord, this, options); /// /// Acknowledges this interaction. /// /// to send this message ephemerally, otherwise . /// The request options for this request. /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null); /// /// Responds to this interaction with a . /// /// The to respond with. /// The request options for this request. /// A task that represents the asynchronous operation of responding to the interaction. public abstract Task RespondWithModalAsync(Modal modal, RequestOptions options = null); #endregion /// /// Attepts to get the channel this interaction was executed in. /// /// The request options for this request. /// /// A task that represents the asynchronous operation of fetching the channel. /// public async ValueTask GetChannelAsync(RequestOptions options = null) { if (Channel != null) return Channel; if (!_channelId.HasValue) return null; try { return (IMessageChannel)await Discord.GetChannelAsync(_channelId.Value, options).ConfigureAwait(false); } catch(HttpException ex) when (ex.DiscordCode == DiscordErrorCode.MissingPermissions) { return null; } // bot can't view that channel, return null instead of throwing. } #region IDiscordInteraction /// IUser IDiscordInteraction.User => User; /// async Task IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options) => await GetOriginalResponseAsync(options).ConfigureAwait(false); /// async Task IDiscordInteraction.ModifyOriginalResponseAsync(Action func, RequestOptions options) => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); /// async Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => await RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); /// async Task IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); /// async Task IDiscordInteraction.FollowupWithFilesAsync(IEnumerable attachments, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); #if NETCOREAPP3_0_OR_GREATER != true /// async Task IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed).ConfigureAwait(false); /// async Task IDiscordInteraction.FollowupWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => await FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); /// async Task IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); #endif #endregion } }