diff --git a/docs/guides/deployment/deployment.md b/docs/guides/deployment/deployment.md index 0491e841d..4313e85b4 100644 --- a/docs/guides/deployment/deployment.md +++ b/docs/guides/deployment/deployment.md @@ -47,6 +47,12 @@ enough. Here is a list of recommended VPS provider. * Location(s): * Europe: Lithuania * Based in: Europe +* [ServerStarter.Host](https://serverstarter.host/clients/store/discord-bots) + * Description: Bot hosting with a panel for quick deployment and + no Linux knowledge required. + * Location(s): + * America: United States + * Based in: United States ## .NET Core Deployment @@ -100,4 +106,4 @@ Windows 10 x64 based machine: * `dotnet publish -c Release -r win10-x64` [.NET Core application deployment]: https://docs.microsoft.com/en-us/dotnet/core/deploying/ -[Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog \ No newline at end of file +[Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md index 10ca5d4db..5cf38bff1 100644 --- a/docs/guides/int_framework/intro.md +++ b/docs/guides/int_framework/intro.md @@ -279,8 +279,8 @@ Meaning, the constructor parameters and public settable properties of a module w For more information on dependency injection, read the [DependencyInjection] guides. > [!NOTE] -> On every command execution, module dependencies are resolved using a new service scope which allows you to utilize scoped service instances, just like in Asp.Net. -> Including the precondition checks, every module method is executed using the same service scope and service scopes are disposed right after the `AfterExecute` method returns. +> On every command execution, if the 'AutoServiceScopes' option is enabled in the config , module dependencies are resolved using a new service scope which allows you to utilize scoped service instances, just like in Asp.Net. +> Including the precondition checks, every module method is executed using the same service scope and service scopes are disposed right after the `AfterExecute` method returns. This doesn't apply to methods other than `ExecuteAsync()`. ## Module Groups @@ -291,6 +291,11 @@ By nesting commands inside a module that is tagged with [GroupAttribute] you can > Although creating nested module stuctures are allowed, > you are not permitted to use more than 2 [GroupAttribute]'s in module hierarchy. +> [!NOTE] +> To not use the command group's name as a prefix for component or modal interaction's custom id set `ignoreGroupNames` parameter to `true` in classes with [GroupAttribute] +> +> However, you have to be careful to prevent overlapping ids of buttons and modals + [!code-csharp[Command Group Example](samples/intro/groupmodule.cs)] ## Executing Commands @@ -303,8 +308,19 @@ Any of the following socket events can be used to execute commands: - [AutocompleteExecuted] - [UserCommandExecuted] - [MessageCommandExecuted] +- [ModalExecuted] + +These events will trigger for the specific type of interaction they inherit their name from. The [InteractionCreated] event will trigger for all. +An example of executing a command from an event can be seen here: -Commands can be either executed on the gateway thread or on a seperate thread from the thread pool. This behaviour can be configured by changing the *RunMode* property of `InteractionServiceConfig` or by setting the *runMode* parameter of a command attribute. +[!code-csharp[Command Event Example](samples/intro/event.cs)] + +Commands can be either executed on the gateway thread or on a seperate thread from the thread pool. +This behaviour can be configured by changing the `RunMode` property of `InteractionServiceConfig` or by setting the *runMode* parameter of a command attribute. + +> [!WARNING] +> In the example above, no form of post-execution is presented. +> Please carefully read the [Post Execution Documentation] for the best approach in resolving the result based on your `RunMode`. You can also configure the way [InteractionService] executes the commands. By default, commands are executed using `ConstructorInfo.Invoke()` to create module instances and @@ -371,6 +387,7 @@ delegate can be used to create HTTP responses from a deserialized json object st [AutocompleteExecuted]: xref:Discord.WebSocket.BaseSocketClient [UserCommandExecuted]: xref:Discord.WebSocket.BaseSocketClient [MessageCommandExecuted]: xref:Discord.WebSocket.BaseSocketClient +[ModalExecuted]: xref:Discord.WebSocket.BaseSocketClient [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient [DiscordRestClient]: xref:Discord.Rest.DiscordRestClient [SocketInteractionContext]: xref:Discord.Interactions.SocketInteractionContext diff --git a/docs/guides/int_framework/samples/intro/event.cs b/docs/guides/int_framework/samples/intro/event.cs new file mode 100644 index 000000000..0c9f032b9 --- /dev/null +++ b/docs/guides/int_framework/samples/intro/event.cs @@ -0,0 +1,14 @@ +// Theres multiple ways to subscribe to the event, depending on your application. Please use the approach fit to your type of client. +// DiscordSocketClient: +_socketClient.InteractionCreated += async (x) => +{ + var ctx = new SocketInteractionContext(_socketClient, x); + await _interactionService.ExecuteCommandAsync(ctx, _serviceProvider); +} + +// DiscordShardedClient: +_shardedClient.InteractionCreated += async (x) => +{ + var ctx = new ShardedInteractionContext(_shardedClient, x); + await _interactionService.ExecuteCommandAsync(ctx, _serviceProvider); +} diff --git a/docs/guides/int_framework/samples/intro/groupmodule.cs b/docs/guides/int_framework/samples/intro/groupmodule.cs index f0d992aff..a07b2e4d8 100644 --- a/docs/guides/int_framework/samples/intro/groupmodule.cs +++ b/docs/guides/int_framework/samples/intro/groupmodule.cs @@ -16,6 +16,11 @@ public class CommandGroupModule : InteractionModuleBase await RespondAsync(input); + => await RespondAsync(input, components: new ComponentBuilder().WithButton("Echo", $"echoButton_{input}").Build()); + + // Component interaction with ignoreGroupNames set to true + [ComponentInteraction("echoButton_*", true)] + public async Task EchoButton(string input) + => await RespondAsync(input); } } \ No newline at end of file diff --git a/docs/guides/int_framework/samples/intro/modal.cs b/docs/guides/int_framework/samples/intro/modal.cs index 65cc81abf..8a6ba9d8a 100644 --- a/docs/guides/int_framework/samples/intro/modal.cs +++ b/docs/guides/int_framework/samples/intro/modal.cs @@ -12,7 +12,9 @@ public class FoodModal : IModal [ModalTextInput("food_name", placeholder: "Pizza", maxLength: 20)] public string Food { get; set; } - // Additional paremeters can be specified to further customize the input. + // Additional paremeters can be specified to further customize the input. + // Parameters can be optional + [RequiredInput(false)] [InputLabel("Why??")] [ModalTextInput("food_reason", TextInputStyle.Paragraph, "Kuz it's tasty", maxLength: 500)] public string Reason { get; set; } @@ -22,10 +24,15 @@ public class FoodModal : IModal [ModalInteraction("food_menu")] public async Task ModalResponse(FoodModal modal) { + // Check if "Why??" field is populated + string reason = string.IsNullOrWhiteSpace(modal.Reason) + ? "." + : $" because {modal.Reason}"; + // Build the message to send. string message = "hey @everyone, I just learned " + $"{Context.User.Mention}'s favorite food is " + - $"{modal.Food} because {modal.Reason}."; + $"{modal.Food}{reason}"; // Specify the AllowedMentions so we don't actually ping everyone. AllowedMentions mentions = new(); diff --git a/docs/guides/voice/sending-voice.md b/docs/guides/voice/sending-voice.md index 555adbca2..36184e3a3 100644 --- a/docs/guides/voice/sending-voice.md +++ b/docs/guides/voice/sending-voice.md @@ -17,11 +17,9 @@ bot. (When developing on .NET Framework, this would be `bin/debug`, when developing on .NET Core, this is where you execute `dotnet run` from; typically the same directory as your csproj). -For Windows Users, precompiled binaries are available for your -convienence [here](https://github.com/discord-net/Discord.Net/tree/dev/voice-natives). +**For Windows users, precompiled binaries are available for your convienence [here](https://github.com/discord-net/Discord.Net/tree/dev/voice-natives).** -For Linux Users, you will need to compile [Sodium] and [Opus] from -source, or install them from your package manager. +**For Linux users, you will need to compile [Sodium] and [Opus] from source, or install them from your package manager.** [Sodium]: https://download.libsodium.org/libsodium/releases/ [Opus]: http://downloads.xiph.org/releases/opus/ diff --git a/experiment/Discord.Net.BuildOverrides/BuildOverrides.cs b/experiment/Discord.Net.BuildOverrides/BuildOverrides.cs index 54b56cc60..54bc362ec 100644 --- a/experiment/Discord.Net.BuildOverrides/BuildOverrides.cs +++ b/experiment/Discord.Net.BuildOverrides/BuildOverrides.cs @@ -251,7 +251,7 @@ namespace Discord private static Assembly _overrideDomain_Resolving(AssemblyLoadContext arg1, AssemblyName arg2) { // resolve the override id - var v = _loadedOverrides.FirstOrDefault(x => x.Value.Any(x => x.Assembly.FullName == arg1.Assemblies.FirstOrDefault().FullName)); + var v = _loadedOverrides.FirstOrDefault(x => x.Value.Any(x => x.Assembly.FullName == arg1.Assemblies.First().FullName)); return GetDependencyAsync(v.Key.Id, $"{arg2}").GetAwaiter().GetResult(); } diff --git a/samples/ShardedClient/Services/InteractionHandlingService.cs b/samples/ShardedClient/Services/InteractionHandlingService.cs index 3c41d7f33..fc2af8150 100644 --- a/samples/ShardedClient/Services/InteractionHandlingService.cs +++ b/samples/ShardedClient/Services/InteractionHandlingService.cs @@ -22,6 +22,7 @@ namespace ShardedClient.Services _service.Log += LogAsync; _client.InteractionCreated += OnInteractionAsync; + _client.ShardReady += ReadyAsync; // For examples on how to handle post execution, // see the InteractionFramework samples. } @@ -30,11 +31,6 @@ namespace ShardedClient.Services public async Task InitializeAsync() { await _service.AddModulesAsync(typeof(InteractionHandlingService).Assembly, _provider); -#if DEBUG - await _service.RegisterCommandsToGuildAsync(1 /* implement */); -#else - await _service.RegisterCommandsGloballyAsync(); -#endif } private async Task OnInteractionAsync(SocketInteraction interaction) @@ -53,5 +49,14 @@ namespace ShardedClient.Services return Task.CompletedTask; } + + private async Task ReadyAsync(DiscordSocketClient _) + { +#if DEBUG + await _service.RegisterCommandsToGuildAsync(1 /* implement */); +#else + await _service.RegisterCommandsGloballyAsync(); +#endif + } } } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 22c58f5c7..f98c81abd 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -206,6 +206,7 @@ namespace Discord.Commands try { + await instance.BeforeExecuteAsync(cmd).ConfigureAwait(false); instance.BeforeExecute(cmd); var task = method.Invoke(instance, args) as Task ?? Task.Delay(0); @@ -221,6 +222,7 @@ namespace Discord.Commands } finally { + await instance.AfterExecuteAsync(cmd).ConfigureAwait(false); instance.AfterExecute(cmd); (instance as IDisposable)?.Dispose(); } diff --git a/src/Discord.Net.Commands/IModuleBase.cs b/src/Discord.Net.Commands/IModuleBase.cs index 8b021f4de..7a953b47b 100644 --- a/src/Discord.Net.Commands/IModuleBase.cs +++ b/src/Discord.Net.Commands/IModuleBase.cs @@ -1,4 +1,5 @@ using Discord.Commands.Builders; +using System.Threading.Tasks; namespace Discord.Commands { @@ -13,12 +14,24 @@ namespace Discord.Commands /// The context to set. void SetContext(ICommandContext context); + /// + /// Executed asynchronously before a command is run in this module base. + /// + /// The command thats about to run. + Task BeforeExecuteAsync(CommandInfo command); + /// /// Executed before a command is run in this module base. /// /// The command thats about to run. void BeforeExecute(CommandInfo command); + /// + /// Executed asynchronously after a command is run in this module base. + /// + /// The command thats about to run. + Task AfterExecuteAsync(CommandInfo command); + /// /// Executed after a command is ran in this module base. /// diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index 5008cca35..b2d6ba119 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -46,6 +46,11 @@ namespace Discord.Commands return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); } /// + /// The method to execute asynchronously before executing the command. + /// + /// The of the command to be executed. + protected virtual Task BeforeExecuteAsync(CommandInfo command) => Task.CompletedTask; + /// /// The method to execute before executing the command. /// /// The of the command to be executed. @@ -53,6 +58,11 @@ namespace Discord.Commands { } /// + /// The method to execute asynchronously after executing the command. + /// + /// The of the command to be executed. + protected virtual Task AfterExecuteAsync(CommandInfo command) => Task.CompletedTask; + /// /// The method to execute after executing the command. /// /// The of the command to be executed. @@ -76,7 +86,9 @@ namespace Discord.Commands var newValue = context as T; Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}."); } + Task IModuleBase.BeforeExecuteAsync(CommandInfo command) => BeforeExecuteAsync(command); void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command); + Task IModuleBase.AfterExecuteAsync(CommandInfo command) => AfterExecuteAsync(command); void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder); #endregion diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs b/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs index cb57b2726..52a70a6f5 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs @@ -12,174 +12,174 @@ namespace Discord /// /// The guild has no features. /// - None = 0, + None = 0L, /// /// The guild has access to animated banners. /// - AnimatedBanner = 1 << 0, + AnimatedBanner = 1L << 0, /// /// The guild has access to set an animated guild icon. /// - AnimatedIcon = 1 << 1, + AnimatedIcon = 1L << 1, /// /// The guild has access to set a guild banner image. /// - Banner = 1 << 2, + Banner = 1L << 2, /// /// The guild has access to channel banners. /// - ChannelBanner = 1 << 3, + ChannelBanner = 1L << 3, /// /// The guild has access to use commerce features (i.e. create store channels). /// - Commerce = 1 << 4, + Commerce = 1L << 4, /// /// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates. /// - Community = 1 << 5, + Community = 1L << 5, /// /// The guild is able to be discovered in the directory. /// - Discoverable = 1 << 6, + Discoverable = 1L << 6, /// /// The guild has discoverable disabled. /// - DiscoverableDisabled = 1 << 7, + DiscoverableDisabled = 1L << 7, /// /// The guild has enabled discoverable before. /// - EnabledDiscoverableBefore = 1 << 8, + EnabledDiscoverableBefore = 1L << 8, /// /// The guild is able to be featured in the directory. /// - Featureable = 1 << 9, + Featureable = 1L << 9, /// /// The guild has a force relay. /// - ForceRelay = 1 << 10, + ForceRelay = 1L << 10, /// /// The guild has a directory entry. /// - HasDirectoryEntry = 1 << 11, + HasDirectoryEntry = 1L << 11, /// /// The guild is a hub. /// - Hub = 1 << 12, + Hub = 1L << 12, /// /// You shouldn't be here... /// - InternalEmployeeOnly = 1 << 13, + InternalEmployeeOnly = 1L << 13, /// /// The guild has access to set an invite splash background. /// - InviteSplash = 1 << 14, + InviteSplash = 1L << 14, /// /// The guild is linked to a hub. /// - LinkedToHub = 1 << 15, + LinkedToHub = 1L << 15, /// /// The guild has member profiles. /// - MemberProfiles = 1 << 16, + MemberProfiles = 1L << 16, /// /// The guild has enabled Membership Screening. /// - MemberVerificationGateEnabled = 1 << 17, + MemberVerificationGateEnabled = 1L << 17, /// /// The guild has enabled monetization. /// - MonetizationEnabled = 1 << 18, + MonetizationEnabled = 1L << 18, /// /// The guild has more emojis. /// - MoreEmoji = 1 << 19, + MoreEmoji = 1L << 19, /// /// The guild has increased custom sticker slots. /// - MoreStickers = 1 << 20, + MoreStickers = 1L << 20, /// /// The guild has access to create news channels. /// - News = 1 << 21, + News = 1L << 21, /// /// The guild has new thread permissions. /// - NewThreadPermissions = 1 << 22, + NewThreadPermissions = 1L << 22, /// /// The guild is partnered. /// - Partnered = 1 << 23, + Partnered = 1L << 23, /// /// The guild has a premium tier three override; guilds made by Discord usually have this. /// - PremiumTier3Override = 1 << 24, + PremiumTier3Override = 1L << 24, /// /// The guild can be previewed before joining via Membership Screening or the directory. /// - PreviewEnabled = 1 << 25, + PreviewEnabled = 1L << 25, /// /// The guild has access to create private threads. /// - PrivateThreads = 1 << 26, + PrivateThreads = 1L << 26, /// /// The guild has relay enabled. /// - RelayEnabled = 1 << 27, + RelayEnabled = 1L << 27, /// /// The guild is able to set role icons. /// - RoleIcons = 1 << 28, + RoleIcons = 1L << 28, /// /// The guild has role subscriptions available for purchase. /// - RoleSubscriptionsAvailableForPurchase = 1 << 29, + RoleSubscriptionsAvailableForPurchase = 1L << 29, /// /// The guild has role subscriptions enabled. /// - RoleSubscriptionsEnabled = 1 << 30, + RoleSubscriptionsEnabled = 1L << 30, /// /// The guild has access to the seven day archive time for threads. /// - SevenDayThreadArchive = 1 << 31, + SevenDayThreadArchive = 1L << 31, /// /// The guild has text in voice enabled. /// - TextInVoiceEnabled = 1 << 32, + TextInVoiceEnabled = 1L << 32, /// /// The guild has threads enabled. /// - ThreadsEnabled = 1 << 33, + ThreadsEnabled = 1L << 33, /// /// The guild has testing threads enabled. /// - ThreadsEnabledTesting = 1 << 34, + ThreadsEnabledTesting = 1L << 34, /// /// The guild has the default thread auto archive. /// - ThreadsDefaultAutoArchiveDuration = 1 << 35, + ThreadsDefaultAutoArchiveDuration = 1L << 35, /// /// The guild has access to the three day archive time for threads. /// - ThreeDayThreadArchive = 1 << 36, + ThreeDayThreadArchive = 1L << 36, /// /// The guild has enabled ticketed events. /// - TicketedEventsEnabled = 1 << 37, + TicketedEventsEnabled = 1L << 37, /// /// The guild has access to set a vanity URL. /// - VanityUrl = 1 << 38, + VanityUrl = 1L << 38, /// /// The guild is verified. /// - Verified = 1 << 39, + Verified = 1L << 39, /// /// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers). /// - VIPRegions = 1 << 40, + VIPRegions = 1L << 40, /// /// The guild has enabled the welcome screen. /// - WelcomeScreenEnabled = 1 << 41, + WelcomeScreenEnabled = 1L << 41, } } diff --git a/src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs index 874171175..d85b376d1 100644 --- a/src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs +++ b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs @@ -9,10 +9,11 @@ namespace Discord.Interactions public NullableConverter(InteractionService interactionService, IServiceProvider services) { - var type = Nullable.GetUnderlyingType(typeof(T)); + var nullableType = typeof(T); + var type = Nullable.GetUnderlyingType(nullableType); if (type is null) - throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {type.FullName}", "type"); + throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {nullableType.FullName}", nameof(type)); _typeConverter = interactionService.GetTypeConverter(type, services); } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index e179675ba..c5b075103 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1756,7 +1756,7 @@ namespace Discord.API if (args.TargetType.Value == TargetUserType.Stream) Preconditions.GreaterThan(args.TargetUserId, 0, nameof(args.TargetUserId)); if (args.TargetType.Value == TargetUserType.EmbeddedApplication) - Preconditions.GreaterThan(args.TargetApplicationId, 0, nameof(args.TargetUserId)); + Preconditions.GreaterThan(args.TargetApplicationId, 0, nameof(args.TargetApplicationId)); } options = RequestOptions.CreateOrClone(options); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ThreadUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ThreadUpdateAuditLogData.cs index 2b9b95418..8eb03114d 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ThreadUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ThreadUpdateAuditLogData.cs @@ -15,7 +15,7 @@ namespace Discord.Rest Thread = thread; ThreadType = type; Before = before; - After = After; + After = after; } internal static ThreadUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 8bab35937..20140994f 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -428,7 +428,7 @@ namespace Discord.Rest var ids = args.Roles.Value.Select(r => r.Id); if (args.RoleIds.IsSpecified) - args.RoleIds.Value.Concat(ids); + args.RoleIds = Optional.Create(args.RoleIds.Value.Concat(ids)); else args.RoleIds = Optional.Create(ids); } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs index 43d13f521..ba2de12a9 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs @@ -426,7 +426,7 @@ namespace Discord.Rest 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, + 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, text, fileName, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); /// diff --git a/src/Discord.Net.WebSocket/API/Gateway/WebhooksUpdatedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/WebhooksUpdatedEvent.cs new file mode 100644 index 000000000..5555dc842 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/WebhooksUpdatedEvent.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + internal class WebhooksUpdatedEvent + { + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index c47591418..fb2110399 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -55,7 +55,7 @@ namespace Discord.WebSocket /// Fired when a channel is updated. /// /// - /// This event is fired when a generic channel has been destroyed. The event handler must return a + /// This event is fired when a generic channel has been updated. The event handler must return a /// and accept 2 as its parameters. /// /// @@ -106,7 +106,7 @@ namespace Discord.WebSocket /// /// /// This event is fired when a message is deleted. The event handler must return a - /// and accept a and + /// and accept a and /// as its parameters. /// /// @@ -117,11 +117,11 @@ namespace Discord.WebSocket /// /// If caching is enabled via , the /// entity will contain the deleted message; otherwise, in event - /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the + /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the /// . /// /// - /// The source channel of the removed message will be passed into the + /// The source channel of the removed message will be passed into the /// parameter. /// /// @@ -143,7 +143,7 @@ namespace Discord.WebSocket /// /// /// This event is fired when multiple messages are bulk deleted. The event handler must return a - /// and accept an and + /// and accept an and /// as its parameters. /// /// @@ -154,11 +154,11 @@ namespace Discord.WebSocket /// /// If caching is enabled via , the /// entity will contain the deleted message; otherwise, in event - /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the + /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the /// . /// /// - /// The source channel of the removed message will be passed into the + /// The source channel of the removed message will be passed into the /// parameter. /// /// @@ -178,14 +178,14 @@ namespace Discord.WebSocket /// /// If caching is enabled via , the /// entity will contain the original message; otherwise, in event - /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the + /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the /// . /// /// /// The updated message will be passed into the parameter. /// /// - /// The source channel of the updated message will be passed into the + /// The source channel of the updated message will be passed into the /// parameter. /// /// @@ -199,24 +199,24 @@ namespace Discord.WebSocket /// /// /// This event is fired when a reaction is added to a user message. The event handler must return a - /// and accept a , an + /// and accept a , an /// , and a as its parameter. /// /// /// If caching is enabled via , the /// entity will contain the original message; otherwise, in event - /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the + /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the /// . /// /// - /// The source channel of the reaction addition will be passed into the + /// The source channel of the reaction addition will be passed into the /// parameter. /// /// /// The reaction that was added will be passed into the parameter. /// /// - /// When fetching the reaction from this event, a user may not be provided under + /// When fetching the reaction from this event, a user may not be provided under /// . Please see the documentation of the property for more /// information. /// @@ -367,7 +367,7 @@ namespace Discord.WebSocket } internal readonly AsyncEvent, SocketGuildEvent, Task>> _guildScheduledEventUpdated = new AsyncEvent, SocketGuildEvent, Task>>(); - + /// /// Fired when a guild event is cancelled. /// @@ -877,5 +877,20 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _guildStickerDeleted = new AsyncEvent>(); #endregion + + #region Webhooks + + /// + /// Fired when a webhook is modified, moved, or deleted. If the webhook was + /// moved the channel represents the destination channel, not the source. + /// + public event Func WebhooksUpdated + { + add { _webhooksUpdated.Add(value); } + remove { _webhooksUpdated.Remove(value); } + } + internal readonly AsyncEvent> _webhooksUpdated = new AsyncEvent>(); + + #endregion } } diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 3a14692e0..9fc717762 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -496,6 +496,8 @@ namespace Discord.WebSocket client.GuildScheduledEventStarted += (arg) => _guildScheduledEventStarted.InvokeAsync(arg); client.GuildScheduledEventUserAdd += (arg1, arg2) => _guildScheduledEventUserAdd.InvokeAsync(arg1, arg2); client.GuildScheduledEventUserRemove += (arg1, arg2) => _guildScheduledEventUserRemove.InvokeAsync(arg1, arg2); + + client.WebhooksUpdated += (arg1, arg2) => _webhooksUpdated.InvokeAsync(arg1, arg2); } #endregion diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 5743d9abd..3c5621304 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -2839,6 +2839,23 @@ namespace Discord.WebSocket #endregion + #region Webhooks + + case "WEBHOOKS_UPDATE": + { + var data = (payload as JToken).ToObject(_serializer); + type = "WEBHOOKS_UPDATE"; + await _gatewayLogger.DebugAsync("Received Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); + + var guild = State.GetGuild(data.GuildId); + var channel = State.GetChannel(data.ChannelId); + + await TimedInvokeAsync(_webhooksUpdated, nameof(WebhooksUpdated), guild, channel); + } + break; + + #endregion + #region Ignored (User only) case "CHANNEL_PINS_ACK": await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); @@ -2858,9 +2875,6 @@ namespace Discord.WebSocket case "USER_SETTINGS_UPDATE": await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); break; - case "WEBHOOKS_UPDATE": - await _gatewayLogger.DebugAsync("Ignored Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); - break; #endregion #region Others diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 9ce2f507a..cf01857e3 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -532,13 +532,10 @@ namespace Discord.WebSocket Features = model.Features; var roles = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Roles.Length * 1.05)); - if (model.Roles != null) + for (int i = 0; i < model.Roles.Length; i++) { - for (int i = 0; i < model.Roles.Length; i++) - { - var role = SocketRole.Create(this, state, model.Roles[i]); - roles.TryAdd(role.Id, role); - } + var role = SocketRole.Create(this, state, model.Roles[i]); + roles.TryAdd(role.Id, role); } _roles = roles; diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs index a629fd069..98a7daefc 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs @@ -39,7 +39,7 @@ namespace Discord.WebSocket { foreach (var channel in resolved.Channels.Value) { - SocketChannel socketChannel = guild != null + var socketChannel = guild != null ? guild.GetChannel(channel.Value.Id) : discord.GetChannel(channel.Value.Id); @@ -69,7 +69,7 @@ namespace Discord.WebSocket } } - if (resolved.Roles.IsSpecified) + if (resolved.Roles.IsSpecified && guild != null) { foreach (var role in resolved.Roles.Value) {