using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Model = Discord.API.Interaction; using DataModel = Discord.API.ApplicationCommandInteractionData; using Newtonsoft.Json; using Discord.Net; namespace Discord.Rest { /// /// Represents a REST-based interaction. /// public abstract class RestInteraction : RestEntity, IDiscordInteraction { /// public InteractionType Type { get; private set; } /// public IDiscordInteractionData Data { get; private set; } /// public string Token { get; private set; } /// public int Version { get; private set; } /// /// Gets the user who invoked the interaction. /// public RestUser User { get; private set; } /// public string UserLocale { get; private set; } /// public string GuildLocale { get; private set; } /// public DateTimeOffset CreatedAt { get; private set; } /// /// Gets whether or not the token used to respond to this interaction is valid. /// public bool IsValidToken => InteractionHelper.CanRespondOrFollowup(this); /// /// Gets the channel that this interaction was executed in. /// public IRestMessageChannel Channel { get; private set; } /// /// Gets the guild this interaction was executed in. /// public RestGuild Guild { get; private set; } /// public bool HasResponded { get; protected set; } /// public bool IsDMInteraction { get; private set; } internal RestInteraction(BaseDiscordClient discord, ulong id) : base(discord, id) { CreatedAt = discord.UseInteractionSnowflakeDate ? SnowflakeUtils.FromSnowflake(Id) : DateTime.UtcNow; } internal static async Task CreateAsync(DiscordRestClient client, Model model) { if(model.Type == InteractionType.Ping) { return await RestPingInteraction.CreateAsync(client, model); } 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 => await RestSlashCommand.CreateAsync(client, model).ConfigureAwait(false), ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model).ConfigureAwait(false), ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model).ConfigureAwait(false), _ => null }; } if (model.Type == InteractionType.MessageComponent) return await RestMessageComponent.CreateAsync(client, model).ConfigureAwait(false); if (model.Type == InteractionType.ApplicationCommandAutocomplete) return await RestAutocompleteInteraction.CreateAsync(client, model).ConfigureAwait(false); if (model.Type == InteractionType.ModalSubmit) return await RestModal.CreateAsync(client, model).ConfigureAwait(false); return null; } internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model) { IsDMInteraction = !model.GuildId.IsSpecified; Data = model.Data.IsSpecified ? model.Data.Value : null; Token = model.Token; Version = model.Version; Type = model.Type; if(Guild == null && model.GuildId.IsSpecified) { Guild = await discord.GetGuildAsync(model.GuildId.Value); } if (User == null) { if (model.Member.IsSpecified && model.GuildId.IsSpecified) { User = RestGuildUser.Create(Discord, Guild, model.Member.Value); } else { User = RestUser.Create(Discord, model.User.Value); } } if(Channel == null && model.ChannelId.IsSpecified) { try { Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value); } catch(HttpException x) when(x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore } UserLocale = model.UserLocale.IsSpecified ? model.UserLocale.Value : null; GuildLocale = model.GuildLocale.IsSpecified ? model.GuildLocale.Value : null; } internal string SerializePayload(object payload) { var json = new StringBuilder(); using (var text = new StringWriter(json)) using (var writer = new JsonTextWriter(text)) DiscordRestClient.Serializer.Serialize(writer, payload); return json.ToString(); } /// public abstract string Defer(bool ephemeral = false, 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 task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// 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 abstract string RespondWithModal(Modal modal, RequestOptions options = null); /// 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); /// /// 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. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains 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. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// public abstract 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); /// /// 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. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// public abstract 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); /// /// 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 abstract 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); /// /// 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); /// public Task DeleteOriginalResponseAsync(RequestOptions options = null) => InteractionHelper.DeleteInteractionResponseAsync(Discord, this, options); #region IDiscordInteraction /// IUser IDiscordInteraction.User => User; /// Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => Task.FromResult(Respond(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options)); /// Task IDiscordInteraction.DeferAsync(bool ephemeral, RequestOptions options) => Task.FromResult(Defer(ephemeral, options)); /// Task IDiscordInteraction.RespondWithModalAsync(Modal modal, RequestOptions options) => Task.FromResult(RespondWithModal(modal, options)); /// 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.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.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, options).ConfigureAwait(false); /// async Task IDiscordInteraction.FollowupWithFileAsync(string filePath, string text, string fileName, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => await FollowupWithFileAsync(filePath, text, fileName, 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); /// 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); /// Task IDiscordInteraction.RespondWithFilesAsync(IEnumerable attachments, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => throw new NotSupportedException("REST-Based interactions don't support files."); #if NETCOREAPP3_0_OR_GREATER != true /// Task IDiscordInteraction.RespondWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => throw new NotSupportedException("REST-Based interactions don't support files."); /// Task IDiscordInteraction.RespondWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => throw new NotSupportedException("REST-Based interactions don't support files."); /// Task IDiscordInteraction.RespondWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => throw new NotSupportedException("REST-Based interactions don't support files."); #endif #endregion } }