@@ -0,0 +1,77 @@ | |||||
name: 🐞 Bug Report | |||||
description: File a bug report | |||||
title: "[Bug]: " | |||||
labels: ["bug"] | |||||
body: | |||||
- type: markdown | |||||
attributes: | |||||
value: Thanks for taking the time to fill out this bug report! | |||||
- type: checkboxes | |||||
attributes: | |||||
label: Check The Docs | |||||
description: Please refer to our [FAQs](https://discordnet.dev/faq/basics/getting-started.html), [Documentation](https://discordnet.dev/api/index.html), | |||||
and [Migration Guide](https://discordnet.dev/guides/v2_v3_guide/v2_to_v3_guide.html) before reporting issues. | |||||
options: | |||||
- label: "I double checked the docs and couldn't find any useful information." | |||||
required: true | |||||
- type: checkboxes | |||||
attributes: | |||||
label: Verify Issue Source | |||||
description: If your issue is related to an exception make sure the error was thrown by Discord.Net, and not your code or another library. | |||||
If you get an `HttpException` with the error code `401`, then the error is caused by your bot's permissions, not dnet. | |||||
options: | |||||
- label: I verified the issue was caused by Discord.Net. | |||||
required: true | |||||
- type: checkboxes | |||||
attributes: | |||||
label: Check your intents | |||||
description: If your issue is related to not receiving expected events, you may have setup your gateway intents incorrectly. | |||||
Newer versions of Discord.Net use a more modern version of Discord's API that requires you tell it what events | |||||
you want to receive. Discord.Net defaults to all non-privleged intents, but if your bot requires privileged intents | |||||
you need specify them in your clients config. You can see what intents you need for your events | |||||
[here](https://discord.com/developers/docs/topics/gateway#list-of-intents). | |||||
options: | |||||
- label: I double checked that I have the required intents. | |||||
required: true | |||||
- type: textarea | |||||
id: description | |||||
attributes: | |||||
label: Description | |||||
description: A brief explination of the bug. | |||||
placeholder: When I start a DiscordSocketClient without stopping it, the gateway thread gets blocked. | |||||
validations: | |||||
required: true | |||||
- type: input | |||||
id: version | |||||
attributes: | |||||
label: Version | |||||
description: What version of Discord.Net are you using? | |||||
placeholder: ex. 3.1.0 | |||||
validations: | |||||
required: true | |||||
- type: input | |||||
id: working-version | |||||
attributes: | |||||
label: Working Version | |||||
description: If this worked on an older version of Discord.Net put that version here. | |||||
placeholder: ex. 2.4.0 | |||||
validations: | |||||
required: false | |||||
- type: textarea | |||||
id: logs | |||||
attributes: | |||||
label: Logs | |||||
description: Add applicable logs and/or a stacktrace here. | |||||
validations: | |||||
required: true | |||||
- type: textarea | |||||
id: sample | |||||
attributes: | |||||
label: Sample | |||||
description: Include a (short) code sample that reproduces your issue 100% of time (comments would be great). | |||||
placeholder: | | |||||
```cs | |||||
My.Code(); | |||||
``` | |||||
validations: | |||||
required: false |
@@ -7,7 +7,7 @@ using System.Threading.Tasks; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
[Flags] | [Flags] | ||||
public enum GuildFeature | |||||
public enum GuildFeature : long | |||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// The guild has no features. | /// The guild has no features. | ||||
@@ -53,6 +53,11 @@ namespace Discord | |||||
/// <summary> | /// <summary> | ||||
/// A <see cref="double"/>. | /// A <see cref="double"/>. | ||||
/// </summary> | /// </summary> | ||||
Number = 10 | |||||
Number = 10, | |||||
/// <summary> | |||||
/// A <see cref="Discord.Attachment"/>. | |||||
/// </summary> | |||||
Attachment = 11 | |||||
} | } | ||||
} | } |
@@ -1,3 +1,4 @@ | |||||
using System.Collections.Generic; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord | namespace Discord | ||||
@@ -41,7 +42,7 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
/// <seealso cref="IMessage.AddReactionAsync(IEmote, RequestOptions)"/> | /// <seealso cref="IMessage.AddReactionAsync(IEmote, RequestOptions)"/> | ||||
/// <seealso cref="IEmote"/> | /// <seealso cref="IEmote"/> | ||||
public static async Task AddReactionsAsync(this IUserMessage msg, IEmote[] reactions, RequestOptions options = null) | |||||
public static async Task AddReactionsAsync(this IUserMessage msg, IEnumerable<IEmote> reactions, RequestOptions options = null) | |||||
{ | { | ||||
foreach (var rxn in reactions) | foreach (var rxn in reactions) | ||||
await msg.AddReactionAsync(rxn, options).ConfigureAwait(false); | await msg.AddReactionAsync(rxn, options).ConfigureAwait(false); | ||||
@@ -67,7 +68,7 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
/// <seealso cref="IMessage.RemoveReactionAsync(IEmote, IUser, RequestOptions)"/> | /// <seealso cref="IMessage.RemoveReactionAsync(IEmote, IUser, RequestOptions)"/> | ||||
/// <seealso cref="IEmote"/> | /// <seealso cref="IEmote"/> | ||||
public static async Task RemoveReactionsAsync(this IUserMessage msg, IUser user, IEmote[] reactions, RequestOptions options = null) | |||||
public static async Task RemoveReactionsAsync(this IUserMessage msg, IUser user, IEnumerable<IEmote> reactions, RequestOptions options = null) | |||||
{ | { | ||||
foreach (var rxn in reactions) | foreach (var rxn in reactions) | ||||
await msg.RemoveReactionAsync(rxn, user, options).ConfigureAwait(false); | await msg.RemoveReactionAsync(rxn, user, options).ConfigureAwait(false); | ||||
@@ -1,4 +1,7 @@ | |||||
using Discord.Rest; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord.Interactions | namespace Discord.Interactions | ||||
@@ -47,18 +50,66 @@ namespace Discord.Interactions | |||||
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | ||||
await Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | await Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | ||||
/// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
protected virtual Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||||
=> Context.Interaction.RespondWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||||
/// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
protected virtual Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||||
=> Context.Interaction.RespondWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||||
/// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
protected virtual Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||||
=> Context.Interaction.RespondWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||||
/// <inheritdoc cref="IDiscordInteraction.RespondWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
protected virtual Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||||
=> Context.Interaction.RespondWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||||
/// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, RequestOptions, MessageComponent, Embed)"/> | /// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, RequestOptions, MessageComponent, Embed)"/> | ||||
protected virtual async Task<IUserMessage> FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | protected virtual async Task<IUserMessage> FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | ||||
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | ||||
await Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | await Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | ||||
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
protected virtual Task<IUserMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||||
=> Context.Interaction.FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||||
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
protected virtual Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||||
=> Context.Interaction.FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||||
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
protected virtual Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||||
=> Context.Interaction.FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||||
/// <inheritdoc cref="IDiscordInteraction.FollowupWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
protected virtual Task<IUserMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||||
=> Context.Interaction.FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||||
/// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/> | /// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/> | ||||
protected virtual async Task<IUserMessage> ReplyAsync (string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, | protected virtual async Task<IUserMessage> ReplyAsync (string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, | ||||
AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null) => | AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null) => | ||||
await Context.Channel.SendMessageAsync(text, false, embed, options, allowedMentions, messageReference, components).ConfigureAwait(false); | await Context.Channel.SendMessageAsync(text, false, embed, options, allowedMentions, messageReference, components).ConfigureAwait(false); | ||||
/// <inheritdoc cref="IDiscordInteraction.GetOriginalResponseAsync(RequestOptions)"/> | |||||
protected virtual Task<IUserMessage> GetOriginalResponseAsync(RequestOptions options = null) | |||||
=> Context.Interaction.GetOriginalResponseAsync(options); | |||||
/// <inheritdoc cref="IDiscordInteraction.ModifyOriginalResponseAsync(Action{MessageProperties}, RequestOptions)"/> | |||||
protected virtual Task<IUserMessage> ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options = null) | |||||
=> Context.Interaction.ModifyOriginalResponseAsync(func, options); | |||||
/// <inheritdoc cref="IDeletable.DeleteAsync(RequestOptions)"/> | /// <inheritdoc cref="IDeletable.DeleteAsync(RequestOptions)"/> | ||||
protected virtual async Task DeleteOriginalResponseAsync ( ) | |||||
protected virtual async Task DeleteOriginalResponseAsync() | |||||
{ | { | ||||
var response = await Context.Interaction.GetOriginalResponseAsync().ConfigureAwait(false); | var response = await Context.Interaction.GetOriginalResponseAsync().ConfigureAwait(false); | ||||
await response.DeleteAsync().ConfigureAwait(false); | await response.DeleteAsync().ConfigureAwait(false); | ||||
@@ -183,6 +183,7 @@ namespace Discord.Interactions | |||||
{ | { | ||||
[typeof(IChannel)] = typeof(DefaultChannelConverter<>), | [typeof(IChannel)] = typeof(DefaultChannelConverter<>), | ||||
[typeof(IRole)] = typeof(DefaultRoleConverter<>), | [typeof(IRole)] = typeof(DefaultRoleConverter<>), | ||||
[typeof(IAttachment)] = typeof(DefaultAttachmentConverter<>), | |||||
[typeof(IUser)] = typeof(DefaultUserConverter<>), | [typeof(IUser)] = typeof(DefaultUserConverter<>), | ||||
[typeof(IMentionable)] = typeof(DefaultMentionableConverter<>), | [typeof(IMentionable)] = typeof(DefaultMentionableConverter<>), | ||||
[typeof(IConvertible)] = typeof(DefaultValueConverter<>), | [typeof(IConvertible)] = typeof(DefaultValueConverter<>), | ||||
@@ -20,6 +20,11 @@ namespace Discord.Interactions | |||||
} | } | ||||
} | } | ||||
internal class DefaultAttachmentConverter<T> : DefaultEntityTypeConverter<T> where T : class, IAttachment | |||||
{ | |||||
public override ApplicationCommandOptionType GetDiscordType() => ApplicationCommandOptionType.Attachment; | |||||
} | |||||
internal class DefaultRoleConverter<T> : DefaultEntityTypeConverter<T> where T : class, IRole | internal class DefaultRoleConverter<T> : DefaultEntityTypeConverter<T> where T : class, IRole | ||||
{ | { | ||||
public override ApplicationCommandOptionType GetDiscordType ( ) => ApplicationCommandOptionType.Role; | public override ApplicationCommandOptionType GetDiscordType ( ) => ApplicationCommandOptionType.Role; | ||||
@@ -18,5 +18,7 @@ namespace Discord.API | |||||
public Optional<Dictionary<string, Role>> Roles { get; set; } | public Optional<Dictionary<string, Role>> Roles { get; set; } | ||||
[JsonProperty("messages")] | [JsonProperty("messages")] | ||||
public Optional<Dictionary<string, Message>> Messages { get; set; } | public Optional<Dictionary<string, Message>> Messages { get; set; } | ||||
[JsonProperty("attachments")] | |||||
public Optional<Dictionary<string, Attachment>> Attachments { get; set; } | |||||
} | } | ||||
} | } |
@@ -1362,6 +1362,12 @@ namespace Discord.API | |||||
return await SendJsonAsync<Message>("PATCH", () => $"webhooks/{CurrentApplicationId}/{interactionToken}/messages/@original", args, new BucketIds(), options: options); | return await SendJsonAsync<Message>("PATCH", () => $"webhooks/{CurrentApplicationId}/{interactionToken}/messages/@original", args, new BucketIds(), options: options); | ||||
} | } | ||||
public async Task<Message> ModifyInteractionResponseAsync(UploadWebhookFileParams args, string interactionToken, RequestOptions options = null) | |||||
{ | |||||
options = RequestOptions.CreateOrClone(options); | |||||
return await SendMultipartAsync<Message>("PATCH", () => $"webhooks/{CurrentApplicationId}/{interactionToken}/messages/@original", args.ToDictionary(), new BucketIds(), options: options); | |||||
} | |||||
public async Task DeleteInteractionResponseAsync(string interactionToken, RequestOptions options = null) | public async Task DeleteInteractionResponseAsync(string interactionToken, RequestOptions options = null) | ||||
{ | { | ||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
@@ -19,6 +19,9 @@ namespace Discord.Rest | |||||
internal readonly Dictionary<ulong, RestMessage> Messages | internal readonly Dictionary<ulong, RestMessage> Messages | ||||
= new Dictionary<ulong, RestMessage>(); | = new Dictionary<ulong, RestMessage>(); | ||||
internal readonly Dictionary<ulong, Attachment> Attachments | |||||
= 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) | ||||
{ | { | ||||
var resolved = model.Resolved.Value; | var resolved = model.Resolved.Value; | ||||
@@ -91,6 +94,16 @@ namespace Discord.Rest | |||||
Messages.Add(message.Id, message); | Messages.Add(message.Id, message); | ||||
} | } | ||||
} | } | ||||
if (resolved.Attachments.IsSpecified) | |||||
{ | |||||
foreach (var attachment in resolved.Attachments.Value) | |||||
{ | |||||
var discordAttachment = Attachment.Create(attachment.Value); | |||||
Attachments.Add(ulong.Parse(attachment.Key), discordAttachment); | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -424,16 +424,31 @@ namespace Discord.Rest | |||||
Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); | Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); | ||||
var apiArgs = new ModifyInteractionResponseParams | |||||
if (!args.Attachments.IsSpecified) | |||||
{ | { | ||||
Content = args.Content, | |||||
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||||
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, | |||||
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | |||||
Flags = args.Flags | |||||
}; | |||||
var apiArgs = new ModifyInteractionResponseParams | |||||
{ | |||||
Content = args.Content, | |||||
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||||
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, | |||||
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | |||||
Flags = args.Flags | |||||
}; | |||||
return await client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options).ConfigureAwait(false); | |||||
return await client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options).ConfigureAwait(false); | |||||
} | |||||
else | |||||
{ | |||||
var apiArgs = new UploadWebhookFileParams(args.Attachments.Value.ToArray()) | |||||
{ | |||||
Content = args.Content, | |||||
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||||
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, | |||||
MessageComponents = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | |||||
}; | |||||
return await client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options).ConfigureAwait(false); | |||||
} | |||||
} | } | ||||
public static async Task DeleteInteractionResponseAsync(BaseDiscordClient client, RestInteractionMessage message, RequestOptions options = null) | public static async Task DeleteInteractionResponseAsync(BaseDiscordClient client, RestInteractionMessage message, RequestOptions options = null) | ||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks; | |||||
using Model = Discord.API.Interaction; | using Model = Discord.API.Interaction; | ||||
using DataModel = Discord.API.ApplicationCommandInteractionData; | using DataModel = Discord.API.ApplicationCommandInteractionData; | ||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using Discord.Net; | |||||
namespace Discord.Rest | namespace Discord.Rest | ||||
{ | { | ||||
@@ -133,7 +134,11 @@ namespace Discord.Rest | |||||
if(Channel == null && model.ChannelId.IsSpecified) | if(Channel == null && model.ChannelId.IsSpecified) | ||||
{ | { | ||||
Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value); | |||||
try | |||||
{ | |||||
Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value); | |||||
} | |||||
catch(HttpException x) when(x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore | |||||
} | } | ||||
UserLocale = model.UserLocale.IsSpecified | UserLocale = model.UserLocale.IsSpecified | ||||
@@ -43,6 +43,7 @@ namespace Discord.Rest | |||||
case ApplicationCommandOptionType.Role: | case ApplicationCommandOptionType.Role: | ||||
case ApplicationCommandOptionType.Channel: | case ApplicationCommandOptionType.Channel: | ||||
case ApplicationCommandOptionType.Mentionable: | case ApplicationCommandOptionType.Mentionable: | ||||
case ApplicationCommandOptionType.Attachment: | |||||
if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | ||||
{ | { | ||||
switch (Type) | switch (Type) | ||||
@@ -80,6 +81,9 @@ namespace Discord.Rest | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case ApplicationCommandOptionType.Attachment: | |||||
Value = data.ResolvableData.Attachments.FirstOrDefault(x => x.Key == valueId).Value; | |||||
break; | |||||
default: | default: | ||||
Value = model.Value.Value; | Value = model.Value.Value; | ||||
break; | break; | ||||
@@ -46,6 +46,7 @@ namespace Discord.WebSocket | |||||
private bool _isDisposed; | private bool _isDisposed; | ||||
private GatewayIntents _gatewayIntents; | private GatewayIntents _gatewayIntents; | ||||
private ImmutableArray<StickerPack<SocketSticker>> _defaultStickers; | private ImmutableArray<StickerPack<SocketSticker>> _defaultStickers; | ||||
private SocketSelfUser _previousSessionUser; | |||||
/// <summary> | /// <summary> | ||||
/// Provides access to a REST-only client with a shared state from this client. | /// Provides access to a REST-only client with a shared state from this client. | ||||
@@ -888,6 +889,7 @@ namespace Discord.WebSocket | |||||
_sessionId = data.SessionId; | _sessionId = data.SessionId; | ||||
_unavailableGuildCount = unavailableGuilds; | _unavailableGuildCount = unavailableGuilds; | ||||
CurrentUser = currentUser; | CurrentUser = currentUser; | ||||
_previousSessionUser = CurrentUser; | |||||
State = state; | State = state; | ||||
} | } | ||||
catch (Exception ex) | catch (Exception ex) | ||||
@@ -930,6 +932,9 @@ namespace Discord.WebSocket | |||||
await GuildAvailableAsync(guild).ConfigureAwait(false); | await GuildAvailableAsync(guild).ConfigureAwait(false); | ||||
} | } | ||||
// Restore the previous sessions current user | |||||
CurrentUser = _previousSessionUser; | |||||
await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); | await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); | ||||
} | } | ||||
break; | break; | ||||
@@ -2238,60 +2243,40 @@ namespace Discord.WebSocket | |||||
channel = State.GetDMChannel(data.User.Value.Id); | channel = State.GetDMChannel(data.User.Value.Id); | ||||
} | } | ||||
if (channel == null) | |||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
if (guild != null && !guild.IsSynced) | |||||
{ | { | ||||
var channelModel = await Rest.ApiClient.GetChannelAsync(data.ChannelId.Value); | |||||
if (data.GuildId.IsSpecified) | |||||
channel = SocketTextChannel.Create(State.GetGuild(data.GuildId.Value), State, channelModel); | |||||
else | |||||
channel = (SocketChannel)SocketChannel.CreatePrivate(this, State, channelModel); | |||||
State.AddChannel(channel); | |||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
return; | |||||
} | } | ||||
if (channel is ISocketMessageChannel textChannel) | |||||
{ | |||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
if (guild != null && !guild.IsSynced) | |||||
{ | |||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var interaction = SocketInteraction.Create(this, data, channel as ISocketMessageChannel); | |||||
var interaction = SocketInteraction.Create(this, data, channel as ISocketMessageChannel); | |||||
await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); | |||||
await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); | |||||
switch (interaction) | |||||
{ | |||||
case SocketSlashCommand slashCommand: | |||||
await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false); | |||||
break; | |||||
case SocketMessageComponent messageComponent: | |||||
if(messageComponent.Data.Type == ComponentType.SelectMenu) | |||||
await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false); | |||||
if(messageComponent.Data.Type == ComponentType.Button) | |||||
await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false); | |||||
break; | |||||
case SocketUserCommand userCommand: | |||||
await TimedInvokeAsync(_userCommandExecuted, nameof(UserCommandExecuted), userCommand).ConfigureAwait(false); | |||||
break; | |||||
case SocketMessageCommand messageCommand: | |||||
await TimedInvokeAsync(_messageCommandExecuted, nameof(MessageCommandExecuted), messageCommand).ConfigureAwait(false); | |||||
break; | |||||
case SocketAutocompleteInteraction autocomplete: | |||||
await TimedInvokeAsync(_autocompleteExecuted, nameof(AutocompleteExecuted), autocomplete).ConfigureAwait(false); | |||||
break; | |||||
case SocketModal modal: | |||||
await TimedInvokeAsync(_modalSubmitted, nameof(ModalSubmitted), modal).ConfigureAwait(false); | |||||
break; | |||||
} | |||||
} | |||||
else | |||||
switch (interaction) | |||||
{ | { | ||||
await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); | |||||
return; | |||||
case SocketSlashCommand slashCommand: | |||||
await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false); | |||||
break; | |||||
case SocketMessageComponent messageComponent: | |||||
if (messageComponent.Data.Type == ComponentType.SelectMenu) | |||||
await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false); | |||||
if (messageComponent.Data.Type == ComponentType.Button) | |||||
await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false); | |||||
break; | |||||
case SocketUserCommand userCommand: | |||||
await TimedInvokeAsync(_userCommandExecuted, nameof(UserCommandExecuted), userCommand).ConfigureAwait(false); | |||||
break; | |||||
case SocketMessageCommand messageCommand: | |||||
await TimedInvokeAsync(_messageCommandExecuted, nameof(MessageCommandExecuted), messageCommand).ConfigureAwait(false); | |||||
break; | |||||
case SocketAutocompleteInteraction autocomplete: | |||||
await TimedInvokeAsync(_autocompleteExecuted, nameof(AutocompleteExecuted), autocomplete).ConfigureAwait(false); | |||||
break; | |||||
case SocketModal modal: | |||||
await TimedInvokeAsync(_modalSubmitted, nameof(ModalSubmitted), modal).ConfigureAwait(false); | |||||
break; | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
@@ -39,6 +39,7 @@ namespace Discord.WebSocket | |||||
case ApplicationCommandOptionType.Role: | case ApplicationCommandOptionType.Role: | ||||
case ApplicationCommandOptionType.Channel: | case ApplicationCommandOptionType.Channel: | ||||
case ApplicationCommandOptionType.Mentionable: | case ApplicationCommandOptionType.Mentionable: | ||||
case ApplicationCommandOptionType.Attachment: | |||||
if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | ||||
{ | { | ||||
switch (Type) | switch (Type) | ||||
@@ -76,6 +77,9 @@ namespace Discord.WebSocket | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case ApplicationCommandOptionType.Attachment: | |||||
Value = data.ResolvableData.Attachments.FirstOrDefault(x => x.Key == valueId).Value; | |||||
break; | |||||
default: | default: | ||||
Value = model.Value.Value; | Value = model.Value.Value; | ||||
break; | break; | ||||
@@ -16,6 +16,9 @@ namespace Discord.WebSocket | |||||
internal readonly Dictionary<ulong, SocketMessage> Messages | internal readonly Dictionary<ulong, SocketMessage> Messages | ||||
= new Dictionary<ulong, SocketMessage>(); | = new Dictionary<ulong, SocketMessage>(); | ||||
internal readonly Dictionary<ulong, Attachment> Attachments | |||||
= new Dictionary<ulong, Attachment>(); | |||||
internal SocketResolvableData(DiscordSocketClient discord, ulong? guildId, T model) | internal SocketResolvableData(DiscordSocketClient discord, ulong? guildId, T model) | ||||
{ | { | ||||
var guild = guildId.HasValue ? discord.GetGuild(guildId.Value) : null; | var guild = guildId.HasValue ? discord.GetGuild(guildId.Value) : null; | ||||
@@ -104,6 +107,16 @@ namespace Discord.WebSocket | |||||
Messages.Add(message.Id, message); | Messages.Add(message.Id, message); | ||||
} | } | ||||
} | } | ||||
if (resolved.Attachments.IsSpecified) | |||||
{ | |||||
foreach (var attachment in resolved.Attachments.Value) | |||||
{ | |||||
var discordAttachment = Attachment.Create(attachment.Value); | |||||
Attachments.Add(ulong.Parse(attachment.Key), discordAttachment); | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -17,6 +17,10 @@ namespace Discord.WebSocket | |||||
/// <summary> | /// <summary> | ||||
/// The <see cref="ISocketMessageChannel"/> this interaction was used in. | /// The <see cref="ISocketMessageChannel"/> this interaction was used in. | ||||
/// </summary> | /// </summary> | ||||
/// <remarks> | |||||
/// If the channel isn't cached or the bot doesn't have access to it then | |||||
/// this property will be <see langword="null"/>. | |||||
/// </remarks> | |||||
public ISocketMessageChannel Channel { get; private set; } | public ISocketMessageChannel Channel { get; private set; } | ||||
/// <summary> | /// <summary> | ||||