* Take 2 * Expose channel & guild Id for manual calling * Make api calling optional at runtime * Resolve build errors * Bind runtime option to interaction type * Expose methods to get channel & guild from API * Patch out NRE's, test on all int typestags/3.7.0
| @@ -32,9 +32,15 @@ namespace Discord.Rest | |||||
| /// Initializes a new <see cref="DiscordRestClient"/> with the provided configuration. | /// Initializes a new <see cref="DiscordRestClient"/> with the provided configuration. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="config">The configuration to be used with the client.</param> | /// <param name="config">The configuration to be used with the client.</param> | ||||
| public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { } | |||||
| public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) | |||||
| { | |||||
| _apiOnCreation = config.APIOnRestInteractionCreation; | |||||
| } | |||||
| // used for socket client rest access | // used for socket client rest access | ||||
| internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } | |||||
| internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) | |||||
| { | |||||
| _apiOnCreation = config.APIOnRestInteractionCreation; | |||||
| } | |||||
| private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | ||||
| => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback); | => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback); | ||||
| @@ -82,6 +88,8 @@ namespace Discord.Rest | |||||
| #region Rest interactions | #region Rest interactions | ||||
| private readonly bool _apiOnCreation; | |||||
| public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body) | public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body) | ||||
| => IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body)); | => IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body)); | ||||
| public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body) | public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body) | ||||
| @@ -113,8 +121,8 @@ namespace Discord.Rest | |||||
| /// A <see cref="RestInteraction"/> that represents the incoming http interaction. | /// A <see cref="RestInteraction"/> that represents the incoming http interaction. | ||||
| /// </returns> | /// </returns> | ||||
| /// <exception cref="BadSignatureException">Thrown when the signature doesn't match the public key.</exception> | /// <exception cref="BadSignatureException">Thrown when the signature doesn't match the public key.</exception> | ||||
| public Task<RestInteraction> ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body) | |||||
| => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body)); | |||||
| public Task<RestInteraction> ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body, Func<InteractionType, bool> doApiCallOnCreation = null) | |||||
| => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body), doApiCallOnCreation); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a <see cref="RestInteraction"/> from a http message. | /// Creates a <see cref="RestInteraction"/> from a http message. | ||||
| @@ -127,7 +135,7 @@ namespace Discord.Rest | |||||
| /// A <see cref="RestInteraction"/> that represents the incoming http interaction. | /// A <see cref="RestInteraction"/> that represents the incoming http interaction. | ||||
| /// </returns> | /// </returns> | ||||
| /// <exception cref="BadSignatureException">Thrown when the signature doesn't match the public key.</exception> | /// <exception cref="BadSignatureException">Thrown when the signature doesn't match the public key.</exception> | ||||
| public async Task<RestInteraction> ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body) | |||||
| public async Task<RestInteraction> ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body, Func<InteractionType, bool> doApiCallOnCreation = null) | |||||
| { | { | ||||
| if (!IsValidHttpInteraction(publicKey, signature, timestamp, body)) | if (!IsValidHttpInteraction(publicKey, signature, timestamp, body)) | ||||
| { | { | ||||
| @@ -138,12 +146,12 @@ namespace Discord.Rest | |||||
| using (var jsonReader = new JsonTextReader(textReader)) | using (var jsonReader = new JsonTextReader(textReader)) | ||||
| { | { | ||||
| var model = Serializer.Deserialize<API.Interaction>(jsonReader); | var model = Serializer.Deserialize<API.Interaction>(jsonReader); | ||||
| return await RestInteraction.CreateAsync(this, model); | |||||
| return await RestInteraction.CreateAsync(this, model, doApiCallOnCreation != null ? doApiCallOnCreation(model.Type) : _apiOnCreation); | |||||
| } | } | ||||
| } | } | ||||
| #endregion | #endregion | ||||
| public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null) | public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null) | ||||
| { | { | ||||
| return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false); | return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false); | ||||
| @@ -9,5 +9,7 @@ namespace Discord.Rest | |||||
| { | { | ||||
| /// <summary> Gets or sets the provider used to generate new REST connections. </summary> | /// <summary> Gets or sets the provider used to generate new REST connections. </summary> | ||||
| public RestClientProvider RestClientProvider { get; set; } = DefaultRestClientProvider.Instance; | public RestClientProvider RestClientProvider { get; set; } = DefaultRestClientProvider.Instance; | ||||
| public bool APIOnRestInteractionCreation { get; set; } = true; | |||||
| } | } | ||||
| } | } | ||||
| @@ -39,16 +39,16 @@ namespace Discord.Rest | |||||
| { | { | ||||
| } | } | ||||
| internal new static async Task<RestCommandBase> CreateAsync(DiscordRestClient client, Model model) | |||||
| internal new static async Task<RestCommandBase> CreateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestCommandBase(client, model); | var entity = new RestCommandBase(client, model); | ||||
| await entity.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model) | |||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| await base.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -27,20 +27,20 @@ namespace Discord.Rest | |||||
| { | { | ||||
| } | } | ||||
| internal static async Task<RestCommandBaseData> CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) | |||||
| internal static async Task<RestCommandBaseData> CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestCommandBaseData(client, model); | var entity = new RestCommandBaseData(client, model); | ||||
| await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal virtual async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) | |||||
| internal virtual async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall) | |||||
| { | { | ||||
| Name = model.Name; | Name = model.Name; | ||||
| if (model.Resolved.IsSpecified && ResolvableData == null) | if (model.Resolved.IsSpecified && ResolvableData == null) | ||||
| { | { | ||||
| ResolvableData = new RestResolvableData<Model>(); | ResolvableData = new RestResolvableData<Model>(); | ||||
| await ResolvableData.PopulateAsync(client, guild, channel, model).ConfigureAwait(false); | |||||
| await ResolvableData.PopulateAsync(client, guild, channel, model, doApiCall).ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| @@ -22,7 +22,7 @@ namespace Discord.Rest | |||||
| internal readonly Dictionary<ulong, Attachment> Attachments | internal readonly Dictionary<ulong, Attachment> Attachments | ||||
| = new Dictionary<ulong, Attachment>(); | = new Dictionary<ulong, Attachment>(); | ||||
| internal async Task PopulateAsync(DiscordRestClient discord, RestGuild guild, IRestMessageChannel channel, T model) | |||||
| internal async Task PopulateAsync(DiscordRestClient discord, RestGuild guild, IRestMessageChannel channel, T model, bool doApiCall) | |||||
| { | { | ||||
| var resolved = model.Resolved.Value; | var resolved = model.Resolved.Value; | ||||
| @@ -38,15 +38,26 @@ namespace Discord.Rest | |||||
| if (resolved.Channels.IsSpecified) | if (resolved.Channels.IsSpecified) | ||||
| { | { | ||||
| var channels = await guild.GetChannelsAsync().ConfigureAwait(false); | |||||
| var channels = doApiCall ? await guild.GetChannelsAsync().ConfigureAwait(false) : null; | |||||
| foreach (var channelModel in resolved.Channels.Value) | foreach (var channelModel in resolved.Channels.Value) | ||||
| { | { | ||||
| var restChannel = channels.FirstOrDefault(x => x.Id == channelModel.Value.Id); | |||||
| if (channels != null) | |||||
| { | |||||
| var guildChannel = channels.FirstOrDefault(x => x.Id == channelModel.Value.Id); | |||||
| restChannel.Update(channelModel.Value); | |||||
| guildChannel.Update(channelModel.Value); | |||||
| Channels.Add(ulong.Parse(channelModel.Key), restChannel); | |||||
| Channels.Add(ulong.Parse(channelModel.Key), guildChannel); | |||||
| } | |||||
| else | |||||
| { | |||||
| var restChannel = RestChannel.Create(discord, channelModel.Value); | |||||
| restChannel.Update(channelModel.Value); | |||||
| Channels.Add(ulong.Parse(channelModel.Key), restChannel); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -76,7 +87,10 @@ namespace Discord.Rest | |||||
| { | { | ||||
| foreach (var msg in resolved.Messages.Value) | foreach (var msg in resolved.Messages.Value) | ||||
| { | { | ||||
| channel ??= (IRestMessageChannel)(Channels.FirstOrDefault(x => x.Key == msg.Value.ChannelId).Value ?? await discord.GetChannelAsync(msg.Value.ChannelId).ConfigureAwait(false)); | |||||
| channel ??= (IRestMessageChannel)(Channels.FirstOrDefault(x => x.Key == msg.Value.ChannelId).Value | |||||
| ?? (doApiCall | |||||
| ? await discord.GetChannelAsync(msg.Value.ChannelId).ConfigureAwait(false) | |||||
| : null)); | |||||
| RestUser author; | RestUser author; | ||||
| @@ -20,22 +20,22 @@ namespace Discord.Rest | |||||
| } | } | ||||
| internal new static async Task<RestMessageCommand> CreateAsync(DiscordRestClient client, Model model) | |||||
| internal new static async Task<RestMessageCommand> CreateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestMessageCommand(client, model); | var entity = new RestMessageCommand(client, model); | ||||
| await entity.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model) | |||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| await base.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| var dataModel = model.Data.IsSpecified | var dataModel = model.Data.IsSpecified | ||||
| ? (DataModel)model.Data.Value | ? (DataModel)model.Data.Value | ||||
| : null; | : null; | ||||
| Data = await RestMessageCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false); | |||||
| Data = await RestMessageCommandData.CreateAsync(client, dataModel, Guild, Channel, doApiCall).ConfigureAwait(false); | |||||
| } | } | ||||
| //IMessageCommandInteraction | //IMessageCommandInteraction | ||||
| @@ -23,15 +23,15 @@ namespace Discord.Rest | |||||
| /// <b>Note</b> Not implemented for <see cref="RestMessageCommandData"/> | /// <b>Note</b> Not implemented for <see cref="RestMessageCommandData"/> | ||||
| /// </remarks> | /// </remarks> | ||||
| public override IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options | public override IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options | ||||
| => throw new System.NotImplementedException(); | |||||
| => throw new NotImplementedException(); | |||||
| internal RestMessageCommandData(DiscordRestClient client, Model model) | internal RestMessageCommandData(DiscordRestClient client, Model model) | ||||
| : base(client, model) { } | : base(client, model) { } | ||||
| internal new static async Task<RestMessageCommandData> CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) | |||||
| internal new static async Task<RestMessageCommandData> CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestMessageCommandData(client, model); | var entity = new RestMessageCommandData(client, model); | ||||
| await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -23,22 +23,22 @@ namespace Discord.Rest | |||||
| { | { | ||||
| } | } | ||||
| internal new static async Task<RestUserCommand> CreateAsync(DiscordRestClient client, Model model) | |||||
| internal new static async Task<RestUserCommand> CreateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestUserCommand(client, model); | var entity = new RestUserCommand(client, model); | ||||
| await entity.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model) | |||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| await base.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| var dataModel = model.Data.IsSpecified | var dataModel = model.Data.IsSpecified | ||||
| ? (DataModel)model.Data.Value | ? (DataModel)model.Data.Value | ||||
| : null; | : null; | ||||
| Data = await RestUserCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false); | |||||
| Data = await RestUserCommandData.CreateAsync(client, dataModel, Guild, Channel, doApiCall).ConfigureAwait(false); | |||||
| } | } | ||||
| //IUserCommandInteractionData | //IUserCommandInteractionData | ||||
| @@ -26,10 +26,10 @@ namespace Discord.Rest | |||||
| internal RestUserCommandData(DiscordRestClient client, Model model) | internal RestUserCommandData(DiscordRestClient client, Model model) | ||||
| : base(client, model) { } | : base(client, model) { } | ||||
| internal new static async Task<RestUserCommandData> CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) | |||||
| internal new static async Task<RestUserCommandData> CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestUserCommandData(client, model); | var entity = new RestUserCommandData(client, model); | ||||
| await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -37,15 +37,15 @@ namespace Discord.Rest | |||||
| Data = new RestMessageComponentData(dataModel); | Data = new RestMessageComponentData(dataModel); | ||||
| } | } | ||||
| internal new static async Task<RestMessageComponent> CreateAsync(DiscordRestClient client, Model model) | |||||
| internal new static async Task<RestMessageComponent> CreateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestMessageComponent(client, model); | var entity = new RestMessageComponent(client, model); | ||||
| await entity.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override async Task UpdateAsync(DiscordRestClient discord, Model model) | |||||
| internal override async Task UpdateAsync(DiscordRestClient discord, Model model, bool doApiCall) | |||||
| { | { | ||||
| await base.UpdateAsync(discord, model).ConfigureAwait(false); | |||||
| await base.UpdateAsync(discord, model, doApiCall).ConfigureAwait(false); | |||||
| if (model.Message.IsSpecified && model.ChannelId.IsSpecified) | if (model.Message.IsSpecified && model.ChannelId.IsSpecified) | ||||
| { | { | ||||
| @@ -26,10 +26,10 @@ namespace Discord.Rest | |||||
| Data = new RestModalData(dataModel); | Data = new RestModalData(dataModel); | ||||
| } | } | ||||
| internal new static async Task<RestModal> CreateAsync(DiscordRestClient client, ModelBase model) | |||||
| internal new static async Task<RestModal> CreateAsync(DiscordRestClient client, ModelBase model, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestModal(client, model); | var entity = new RestModal(client, model); | ||||
| await entity.UpdateAsync(client, model); | |||||
| await entity.UpdateAsync(client, model, doApiCall); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -16,6 +16,10 @@ namespace Discord.Rest | |||||
| /// </summary> | /// </summary> | ||||
| public abstract class RestInteraction : RestEntity<ulong>, IDiscordInteraction | public abstract class RestInteraction : RestEntity<ulong>, IDiscordInteraction | ||||
| { | { | ||||
| // Added so channel & guild methods don't need a client reference | |||||
| private Func<RequestOptions, ulong, Task<IRestMessageChannel>> _getChannel = null; | |||||
| private Func<RequestOptions, ulong, Task<RestGuild>> _getGuild = null; | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public InteractionType Type { get; private set; } | public InteractionType Type { get; private set; } | ||||
| @@ -31,6 +35,10 @@ namespace Discord.Rest | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the user who invoked the interaction. | /// Gets the user who invoked the interaction. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | |||||
| /// If this user is an <see cref="RestGuildUser"/> and <see cref="DiscordRestConfig.APIOnRestInteractionCreation"/> is set to false, | |||||
| /// <see cref="RestGuildUser.Guild"/> will return <see langword="null"/> | |||||
| /// </remarks> | |||||
| public RestUser User { get; private set; } | public RestUser User { get; private set; } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| @@ -48,14 +56,38 @@ namespace Discord.Rest | |||||
| public bool IsValidToken | public bool IsValidToken | ||||
| => InteractionHelper.CanRespondOrFollowup(this); | => InteractionHelper.CanRespondOrFollowup(this); | ||||
| /// <summary> | |||||
| /// Gets the ID of the channel this interaction was executed in. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// <see langword="null"/> if the interaction was not executed in a guild. | |||||
| /// </remarks> | |||||
| public ulong? ChannelId { get; private set; } = null; | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the channel that this interaction was executed in. | /// Gets the channel that this interaction was executed in. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | |||||
| /// <see langword="null"/> if <see cref="DiscordRestConfig.APIOnRestInteractionCreation"/> is set to false. | |||||
| /// Call <see cref="GetChannelAsync"/> to set this property and get the interaction channel. | |||||
| /// </remarks> | |||||
| public IRestMessageChannel Channel { get; private set; } | public IRestMessageChannel Channel { get; private set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the guild this interaction was executed in. | |||||
| /// Gets the ID of the guild this interaction was executed in if applicable. | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | |||||
| /// <see langword="null"/> if the interaction was not executed in a guild. | |||||
| /// </remarks> | |||||
| public ulong? GuildId { get; private set; } = null; | |||||
| /// <summary> | |||||
| /// Gets the guild this interaction was executed in if applicable. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This property will be <see langword="null"/> if <see cref="DiscordRestConfig.APIOnRestInteractionCreation"/> is set to false | |||||
| /// or if the interaction was not executed in a guild. | |||||
| /// </remarks> | |||||
| public RestGuild Guild { get; private set; } | public RestGuild Guild { get; private set; } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| @@ -72,11 +104,11 @@ namespace Discord.Rest | |||||
| : DateTime.UtcNow; | : DateTime.UtcNow; | ||||
| } | } | ||||
| internal static async Task<RestInteraction> CreateAsync(DiscordRestClient client, Model model) | |||||
| internal static async Task<RestInteraction> CreateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| if(model.Type == InteractionType.Ping) | if(model.Type == InteractionType.Ping) | ||||
| { | { | ||||
| return await RestPingInteraction.CreateAsync(client, model); | |||||
| return await RestPingInteraction.CreateAsync(client, model, doApiCall); | |||||
| } | } | ||||
| if (model.Type == InteractionType.ApplicationCommand) | if (model.Type == InteractionType.ApplicationCommand) | ||||
| @@ -90,26 +122,26 @@ namespace Discord.Rest | |||||
| return dataModel.Type switch | 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), | |||||
| ApplicationCommandType.Slash => await RestSlashCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false), | |||||
| ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false), | |||||
| ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false), | |||||
| _ => null | _ => null | ||||
| }; | }; | ||||
| } | } | ||||
| if (model.Type == InteractionType.MessageComponent) | if (model.Type == InteractionType.MessageComponent) | ||||
| return await RestMessageComponent.CreateAsync(client, model).ConfigureAwait(false); | |||||
| return await RestMessageComponent.CreateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| if (model.Type == InteractionType.ApplicationCommandAutocomplete) | if (model.Type == InteractionType.ApplicationCommandAutocomplete) | ||||
| return await RestAutocompleteInteraction.CreateAsync(client, model).ConfigureAwait(false); | |||||
| return await RestAutocompleteInteraction.CreateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| if (model.Type == InteractionType.ModalSubmit) | if (model.Type == InteractionType.ModalSubmit) | ||||
| return await RestModal.CreateAsync(client, model).ConfigureAwait(false); | |||||
| return await RestModal.CreateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| return null; | return null; | ||||
| } | } | ||||
| internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model) | |||||
| internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model, bool doApiCall) | |||||
| { | { | ||||
| IsDMInteraction = !model.GuildId.IsSpecified; | IsDMInteraction = !model.GuildId.IsSpecified; | ||||
| @@ -120,16 +152,23 @@ namespace Discord.Rest | |||||
| Version = model.Version; | Version = model.Version; | ||||
| Type = model.Type; | Type = model.Type; | ||||
| if(Guild == null && model.GuildId.IsSpecified) | |||||
| if (Guild == null && model.GuildId.IsSpecified) | |||||
| { | { | ||||
| Guild = await discord.GetGuildAsync(model.GuildId.Value); | |||||
| GuildId = model.GuildId.Value; | |||||
| if (doApiCall) | |||||
| Guild = await discord.GetGuildAsync(model.GuildId.Value); | |||||
| else | |||||
| { | |||||
| Guild = null; | |||||
| _getGuild = new(async (opt, ul) => await discord.GetGuildAsync(ul, opt)); | |||||
| } | |||||
| } | } | ||||
| if (User == null) | if (User == null) | ||||
| { | { | ||||
| if (model.Member.IsSpecified && model.GuildId.IsSpecified) | if (model.Member.IsSpecified && model.GuildId.IsSpecified) | ||||
| { | { | ||||
| User = RestGuildUser.Create(Discord, Guild, model.Member.Value); | |||||
| User = RestGuildUser.Create(Discord, Guild, model.Member.Value, (Guild is null) ? model.GuildId.Value : null); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -137,18 +176,33 @@ namespace Discord.Rest | |||||
| } | } | ||||
| } | } | ||||
| if(Channel == null && model.ChannelId.IsSpecified) | |||||
| if (Channel == null && model.ChannelId.IsSpecified) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value); | |||||
| ChannelId = model.ChannelId.Value; | |||||
| if (doApiCall) | |||||
| Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value); | |||||
| else | |||||
| { | |||||
| _getChannel = new(async (opt, ul) => | |||||
| { | |||||
| if (Guild is null) | |||||
| return (IRestMessageChannel)await discord.GetChannelAsync(ul, opt); | |||||
| else // get a guild channel if the guild is set. | |||||
| return (IRestMessageChannel)await Guild.GetChannelAsync(ul, opt); | |||||
| }); | |||||
| Channel = null; | |||||
| } | |||||
| } | } | ||||
| catch(HttpException x) when(x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore | |||||
| catch (HttpException x) when (x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore | |||||
| } | } | ||||
| UserLocale = model.UserLocale.IsSpecified | UserLocale = model.UserLocale.IsSpecified | ||||
| ? model.UserLocale.Value | |||||
| : null; | |||||
| ? model.UserLocale.Value | |||||
| : null; | |||||
| GuildLocale = model.GuildLocale.IsSpecified | GuildLocale = model.GuildLocale.IsSpecified | ||||
| ? model.GuildLocale.Value | ? model.GuildLocale.Value | ||||
| : null; | : null; | ||||
| @@ -164,6 +218,59 @@ namespace Discord.Rest | |||||
| return json.ToString(); | return json.ToString(); | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets the channel this interaction was executed in. Will be a DM channel if the interaction was executed in DM. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Calling this method succesfully will populate the <see cref="Channel"/> property. | |||||
| /// After this, further calls to this method will no longer call the API, and depend on the value set in <see cref="Channel"/>. | |||||
| /// </remarks> | |||||
| /// <param name="options">The request options for this <see langword="async"/> request.</param> | |||||
| /// <returns>A Rest channel to send messages to.</returns> | |||||
| /// <exception cref="InvalidOperationException">Thrown if no channel can be received.</exception> | |||||
| public async Task<IRestMessageChannel> GetChannelAsync(RequestOptions options = null) | |||||
| { | |||||
| if (IsDMInteraction && Channel is null) | |||||
| { | |||||
| var channel = await User.CreateDMChannelAsync(options); | |||||
| Channel = channel; | |||||
| } | |||||
| else if (Channel is null) | |||||
| { | |||||
| var channel = await _getChannel(options, ChannelId.Value); | |||||
| if (channel is null) | |||||
| throw new InvalidOperationException("The interaction channel was not able to be retrieved."); | |||||
| Channel = channel; | |||||
| _getChannel = null; // get rid of it, we don't need it anymore. | |||||
| } | |||||
| return Channel; | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets the guild this interaction was executed in if applicable. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Calling this method succesfully will populate the <see cref="Guild"/> property. | |||||
| /// After this, further calls to this method will no longer call the API, and depend on the value set in <see cref="Guild"/>. | |||||
| /// </remarks> | |||||
| /// <param name="options">The request options for this <see langword="async"/> request.</param> | |||||
| /// <returns>The guild this interaction was executed in. <see langword="null"/> if the interaction was executed inside DM.</returns> | |||||
| public async Task<RestGuild> GetGuildAsync(RequestOptions options) | |||||
| { | |||||
| if (IsDMInteraction) | |||||
| return null; | |||||
| if (Guild is null) | |||||
| Guild = await _getGuild(options, GuildId.Value); | |||||
| _getGuild = null; // get rid of it, we don't need it anymore. | |||||
| return Guild; | |||||
| } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public abstract string Defer(bool ephemeral = false, RequestOptions options = null); | public abstract string Defer(bool ephemeral = false, RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| @@ -18,10 +18,10 @@ namespace Discord.Rest | |||||
| { | { | ||||
| } | } | ||||
| internal static new async Task<RestPingInteraction> CreateAsync(DiscordRestClient client, Model model) | |||||
| internal static new async Task<RestPingInteraction> CreateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestPingInteraction(client, model.Id); | var entity = new RestPingInteraction(client, model.Id); | ||||
| await entity.UpdateAsync(client, model); | |||||
| await entity.UpdateAsync(client, model, doApiCall); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -32,10 +32,10 @@ namespace Discord.Rest | |||||
| Data = new RestAutocompleteInteractionData(dataModel); | Data = new RestAutocompleteInteractionData(dataModel); | ||||
| } | } | ||||
| internal new static async Task<RestAutocompleteInteraction> CreateAsync(DiscordRestClient client, Model model) | |||||
| internal new static async Task<RestAutocompleteInteraction> CreateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestAutocompleteInteraction(client, model); | var entity = new RestAutocompleteInteraction(client, model); | ||||
| await entity.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -23,22 +23,22 @@ namespace Discord.Rest | |||||
| { | { | ||||
| } | } | ||||
| internal new static async Task<RestSlashCommand> CreateAsync(DiscordRestClient client, Model model) | |||||
| internal new static async Task<RestSlashCommand> CreateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestSlashCommand(client, model); | var entity = new RestSlashCommand(client, model); | ||||
| await entity.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model) | |||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall) | |||||
| { | { | ||||
| await base.UpdateAsync(client, model).ConfigureAwait(false); | |||||
| await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); | |||||
| var dataModel = model.Data.IsSpecified | var dataModel = model.Data.IsSpecified | ||||
| ? (DataModel)model.Data.Value | ? (DataModel)model.Data.Value | ||||
| : null; | : null; | ||||
| Data = await RestSlashCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false); | |||||
| Data = await RestSlashCommandData.CreateAsync(client, dataModel, Guild, Channel, doApiCall).ConfigureAwait(false); | |||||
| } | } | ||||
| //ISlashCommandInteraction | //ISlashCommandInteraction | ||||
| @@ -14,15 +14,15 @@ namespace Discord.Rest | |||||
| internal RestSlashCommandData(DiscordRestClient client, Model model) | internal RestSlashCommandData(DiscordRestClient client, Model model) | ||||
| : base(client, model) { } | : base(client, model) { } | ||||
| internal static new async Task<RestSlashCommandData> CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) | |||||
| internal static new async Task<RestSlashCommandData> CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall) | |||||
| { | { | ||||
| var entity = new RestSlashCommandData(client, model); | var entity = new RestSlashCommandData(client, model); | ||||
| await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); | |||||
| await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) | |||||
| internal override async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall) | |||||
| { | { | ||||
| await base.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); | |||||
| await base.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false); | |||||
| Options = model.Options.IsSpecified | Options = model.Options.IsSpecified | ||||
| ? model.Options.Value.Select(x => new RestSlashCommandDataOption(this, x)).ToImmutableArray() | ? model.Options.Value.Select(x => new RestSlashCommandDataOption(this, x)).ToImmutableArray() | ||||
| @@ -35,7 +35,7 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTimeOffset? PremiumSince => DateTimeUtils.FromTicks(_premiumSinceTicks); | public DateTimeOffset? PremiumSince => DateTimeUtils.FromTicks(_premiumSinceTicks); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public ulong GuildId => Guild.Id; | |||||
| public ulong GuildId { get; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public bool? IsPending { get; private set; } | public bool? IsPending { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -80,14 +80,16 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | ||||
| internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id) | |||||
| internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id, ulong? guildId = null) | |||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| Guild = guild; | |||||
| if (guild is not null) | |||||
| Guild = guild; | |||||
| GuildId = guildId ?? Guild.Id; | |||||
| } | } | ||||
| internal static RestGuildUser Create(BaseDiscordClient discord, IGuild guild, Model model) | |||||
| internal static RestGuildUser Create(BaseDiscordClient discord, IGuild guild, Model model, ulong? guildId = null) | |||||
| { | { | ||||
| var entity = new RestGuildUser(discord, guild, model.User.Id); | |||||
| var entity = new RestGuildUser(discord, guild, model.User.Id, guildId); | |||||
| entity.Update(model); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -116,7 +118,7 @@ namespace Discord.Rest | |||||
| private void UpdateRoles(ulong[] roleIds) | private void UpdateRoles(ulong[] roleIds) | ||||
| { | { | ||||
| var roles = ImmutableArray.CreateBuilder<ulong>(roleIds.Length + 1); | var roles = ImmutableArray.CreateBuilder<ulong>(roleIds.Length + 1); | ||||
| roles.Add(Guild.Id); | |||||
| roles.Add(GuildId); | |||||
| for (int i = 0; i < roleIds.Length; i++) | for (int i = 0; i < roleIds.Length; i++) | ||||
| roles.Add(roleIds[i]); | roles.Add(roleIds[i]); | ||||
| _roleIds = roles.ToImmutable(); | _roleIds = roles.ToImmutable(); | ||||