| @@ -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 | |||
| [Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog | |||
| @@ -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 | |||
| @@ -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); | |||
| } | |||
| @@ -16,6 +16,11 @@ public class CommandGroupModule : InteractionModuleBase<SocketInteractionContext | |||
| // group-name subcommand-group-name echo | |||
| [SlashCommand("echo", "Echo an input")] | |||
| public async Task EchoSubcommand(string input) | |||
| => 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); | |||
| } | |||
| } | |||
| @@ -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(); | |||
| @@ -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/ | |||
| @@ -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(); | |||
| } | |||
| @@ -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 | |||
| } | |||
| } | |||
| } | |||
| @@ -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(); | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| using Discord.Commands.Builders; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.Commands | |||
| { | |||
| @@ -13,12 +14,24 @@ namespace Discord.Commands | |||
| /// <param name="context">The context to set.</param> | |||
| void SetContext(ICommandContext context); | |||
| /// <summary> | |||
| /// Executed asynchronously before a command is run in this module base. | |||
| /// </summary> | |||
| /// <param name="command">The command thats about to run.</param> | |||
| Task BeforeExecuteAsync(CommandInfo command); | |||
| /// <summary> | |||
| /// Executed before a command is run in this module base. | |||
| /// </summary> | |||
| /// <param name="command">The command thats about to run.</param> | |||
| void BeforeExecute(CommandInfo command); | |||
| /// <summary> | |||
| /// Executed asynchronously after a command is run in this module base. | |||
| /// </summary> | |||
| /// <param name="command">The command thats about to run.</param> | |||
| Task AfterExecuteAsync(CommandInfo command); | |||
| /// <summary> | |||
| /// Executed after a command is ran in this module base. | |||
| /// </summary> | |||
| @@ -46,6 +46,11 @@ namespace Discord.Commands | |||
| return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||
| } | |||
| /// <summary> | |||
| /// The method to execute asynchronously before executing the command. | |||
| /// </summary> | |||
| /// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | |||
| protected virtual Task BeforeExecuteAsync(CommandInfo command) => Task.CompletedTask; | |||
| /// <summary> | |||
| /// The method to execute before executing the command. | |||
| /// </summary> | |||
| /// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | |||
| @@ -53,6 +58,11 @@ namespace Discord.Commands | |||
| { | |||
| } | |||
| /// <summary> | |||
| /// The method to execute asynchronously after executing the command. | |||
| /// </summary> | |||
| /// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | |||
| protected virtual Task AfterExecuteAsync(CommandInfo command) => Task.CompletedTask; | |||
| /// <summary> | |||
| /// The method to execute after executing the command. | |||
| /// </summary> | |||
| /// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | |||
| @@ -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 | |||
| @@ -12,174 +12,174 @@ namespace Discord | |||
| /// <summary> | |||
| /// The guild has no features. | |||
| /// </summary> | |||
| None = 0, | |||
| None = 0L, | |||
| /// <summary> | |||
| /// The guild has access to animated banners. | |||
| /// </summary> | |||
| AnimatedBanner = 1 << 0, | |||
| AnimatedBanner = 1L << 0, | |||
| /// <summary> | |||
| /// The guild has access to set an animated guild icon. | |||
| /// </summary> | |||
| AnimatedIcon = 1 << 1, | |||
| AnimatedIcon = 1L << 1, | |||
| /// <summary> | |||
| /// The guild has access to set a guild banner image. | |||
| /// </summary> | |||
| Banner = 1 << 2, | |||
| Banner = 1L << 2, | |||
| /// <summary> | |||
| /// The guild has access to channel banners. | |||
| /// </summary> | |||
| ChannelBanner = 1 << 3, | |||
| ChannelBanner = 1L << 3, | |||
| /// <summary> | |||
| /// The guild has access to use commerce features (i.e. create store channels). | |||
| /// </summary> | |||
| Commerce = 1 << 4, | |||
| Commerce = 1L << 4, | |||
| /// <summary> | |||
| /// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates. | |||
| /// </summary> | |||
| Community = 1 << 5, | |||
| Community = 1L << 5, | |||
| /// <summary> | |||
| /// The guild is able to be discovered in the directory. | |||
| /// </summary> | |||
| Discoverable = 1 << 6, | |||
| Discoverable = 1L << 6, | |||
| /// <summary> | |||
| /// The guild has discoverable disabled. | |||
| /// </summary> | |||
| DiscoverableDisabled = 1 << 7, | |||
| DiscoverableDisabled = 1L << 7, | |||
| /// <summary> | |||
| /// The guild has enabled discoverable before. | |||
| /// </summary> | |||
| EnabledDiscoverableBefore = 1 << 8, | |||
| EnabledDiscoverableBefore = 1L << 8, | |||
| /// <summary> | |||
| /// The guild is able to be featured in the directory. | |||
| /// </summary> | |||
| Featureable = 1 << 9, | |||
| Featureable = 1L << 9, | |||
| /// <summary> | |||
| /// The guild has a force relay. | |||
| /// </summary> | |||
| ForceRelay = 1 << 10, | |||
| ForceRelay = 1L << 10, | |||
| /// <summary> | |||
| /// The guild has a directory entry. | |||
| /// </summary> | |||
| HasDirectoryEntry = 1 << 11, | |||
| HasDirectoryEntry = 1L << 11, | |||
| /// <summary> | |||
| /// The guild is a hub. | |||
| /// </summary> | |||
| Hub = 1 << 12, | |||
| Hub = 1L << 12, | |||
| /// <summary> | |||
| /// You shouldn't be here... | |||
| /// </summary> | |||
| InternalEmployeeOnly = 1 << 13, | |||
| InternalEmployeeOnly = 1L << 13, | |||
| /// <summary> | |||
| /// The guild has access to set an invite splash background. | |||
| /// </summary> | |||
| InviteSplash = 1 << 14, | |||
| InviteSplash = 1L << 14, | |||
| /// <summary> | |||
| /// The guild is linked to a hub. | |||
| /// </summary> | |||
| LinkedToHub = 1 << 15, | |||
| LinkedToHub = 1L << 15, | |||
| /// <summary> | |||
| /// The guild has member profiles. | |||
| /// </summary> | |||
| MemberProfiles = 1 << 16, | |||
| MemberProfiles = 1L << 16, | |||
| /// <summary> | |||
| /// The guild has enabled <seealso href="https://discord.com/developers/docs/resources/guild#membership-screening-object">Membership Screening</seealso>. | |||
| /// </summary> | |||
| MemberVerificationGateEnabled = 1 << 17, | |||
| MemberVerificationGateEnabled = 1L << 17, | |||
| /// <summary> | |||
| /// The guild has enabled monetization. | |||
| /// </summary> | |||
| MonetizationEnabled = 1 << 18, | |||
| MonetizationEnabled = 1L << 18, | |||
| /// <summary> | |||
| /// The guild has more emojis. | |||
| /// </summary> | |||
| MoreEmoji = 1 << 19, | |||
| MoreEmoji = 1L << 19, | |||
| /// <summary> | |||
| /// The guild has increased custom sticker slots. | |||
| /// </summary> | |||
| MoreStickers = 1 << 20, | |||
| MoreStickers = 1L << 20, | |||
| /// <summary> | |||
| /// The guild has access to create news channels. | |||
| /// </summary> | |||
| News = 1 << 21, | |||
| News = 1L << 21, | |||
| /// <summary> | |||
| /// The guild has new thread permissions. | |||
| /// </summary> | |||
| NewThreadPermissions = 1 << 22, | |||
| NewThreadPermissions = 1L << 22, | |||
| /// <summary> | |||
| /// The guild is partnered. | |||
| /// </summary> | |||
| Partnered = 1 << 23, | |||
| Partnered = 1L << 23, | |||
| /// <summary> | |||
| /// The guild has a premium tier three override; guilds made by Discord usually have this. | |||
| /// </summary> | |||
| PremiumTier3Override = 1 << 24, | |||
| PremiumTier3Override = 1L << 24, | |||
| /// <summary> | |||
| /// The guild can be previewed before joining via Membership Screening or the directory. | |||
| /// </summary> | |||
| PreviewEnabled = 1 << 25, | |||
| PreviewEnabled = 1L << 25, | |||
| /// <summary> | |||
| /// The guild has access to create private threads. | |||
| /// </summary> | |||
| PrivateThreads = 1 << 26, | |||
| PrivateThreads = 1L << 26, | |||
| /// <summary> | |||
| /// The guild has relay enabled. | |||
| /// </summary> | |||
| RelayEnabled = 1 << 27, | |||
| RelayEnabled = 1L << 27, | |||
| /// <summary> | |||
| /// The guild is able to set role icons. | |||
| /// </summary> | |||
| RoleIcons = 1 << 28, | |||
| RoleIcons = 1L << 28, | |||
| /// <summary> | |||
| /// The guild has role subscriptions available for purchase. | |||
| /// </summary> | |||
| RoleSubscriptionsAvailableForPurchase = 1 << 29, | |||
| RoleSubscriptionsAvailableForPurchase = 1L << 29, | |||
| /// <summary> | |||
| /// The guild has role subscriptions enabled. | |||
| /// </summary> | |||
| RoleSubscriptionsEnabled = 1 << 30, | |||
| RoleSubscriptionsEnabled = 1L << 30, | |||
| /// <summary> | |||
| /// The guild has access to the seven day archive time for threads. | |||
| /// </summary> | |||
| SevenDayThreadArchive = 1 << 31, | |||
| SevenDayThreadArchive = 1L << 31, | |||
| /// <summary> | |||
| /// The guild has text in voice enabled. | |||
| /// </summary> | |||
| TextInVoiceEnabled = 1 << 32, | |||
| TextInVoiceEnabled = 1L << 32, | |||
| /// <summary> | |||
| /// The guild has threads enabled. | |||
| /// </summary> | |||
| ThreadsEnabled = 1 << 33, | |||
| ThreadsEnabled = 1L << 33, | |||
| /// <summary> | |||
| /// The guild has testing threads enabled. | |||
| /// </summary> | |||
| ThreadsEnabledTesting = 1 << 34, | |||
| ThreadsEnabledTesting = 1L << 34, | |||
| /// <summary> | |||
| /// The guild has the default thread auto archive. | |||
| /// </summary> | |||
| ThreadsDefaultAutoArchiveDuration = 1 << 35, | |||
| ThreadsDefaultAutoArchiveDuration = 1L << 35, | |||
| /// <summary> | |||
| /// The guild has access to the three day archive time for threads. | |||
| /// </summary> | |||
| ThreeDayThreadArchive = 1 << 36, | |||
| ThreeDayThreadArchive = 1L << 36, | |||
| /// <summary> | |||
| /// The guild has enabled ticketed events. | |||
| /// </summary> | |||
| TicketedEventsEnabled = 1 << 37, | |||
| TicketedEventsEnabled = 1L << 37, | |||
| /// <summary> | |||
| /// The guild has access to set a vanity URL. | |||
| /// </summary> | |||
| VanityUrl = 1 << 38, | |||
| VanityUrl = 1L << 38, | |||
| /// <summary> | |||
| /// The guild is verified. | |||
| /// </summary> | |||
| Verified = 1 << 39, | |||
| Verified = 1L << 39, | |||
| /// <summary> | |||
| /// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers). | |||
| /// </summary> | |||
| VIPRegions = 1 << 40, | |||
| VIPRegions = 1L << 40, | |||
| /// <summary> | |||
| /// The guild has enabled the welcome screen. | |||
| /// </summary> | |||
| WelcomeScreenEnabled = 1 << 41, | |||
| WelcomeScreenEnabled = 1L << 41, | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| @@ -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); | |||
| @@ -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) | |||
| @@ -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); | |||
| } | |||
| @@ -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); | |||
| /// <inheritdoc/> | |||
| async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(string filePath, string text, string fileName, Embed[] embeds, bool isTTS, bool ephemeral, | |||
| async Task<IUserMessage> 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); | |||
| /// <inheritdoc/> | |||
| @@ -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; } | |||
| } | |||
| } | |||
| @@ -55,7 +55,7 @@ namespace Discord.WebSocket | |||
| /// <summary> Fired when a channel is updated. </summary> | |||
| /// <remarks> | |||
| /// <para> | |||
| /// 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 | |||
| /// <see cref="Task"/> and accept 2 <see cref="SocketChannel"/> as its parameters. | |||
| /// </para> | |||
| /// <para> | |||
| @@ -106,7 +106,7 @@ namespace Discord.WebSocket | |||
| /// <remarks> | |||
| /// <para> | |||
| /// This event is fired when a message is deleted. The event handler must return a | |||
| /// <see cref="Task"/> and accept a <see cref="Cacheable{TEntity,TId}"/> and | |||
| /// <see cref="Task"/> and accept a <see cref="Cacheable{TEntity,TId}"/> and | |||
| /// <see cref="ISocketMessageChannel"/> as its parameters. | |||
| /// </para> | |||
| /// <para> | |||
| @@ -117,11 +117,11 @@ namespace Discord.WebSocket | |||
| /// </note> | |||
| /// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | |||
| /// <see cref="Cacheable{TEntity,TId}"/> 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 | |||
| /// <see cref="ulong"/>. | |||
| /// </para> | |||
| /// <para> | |||
| /// The source channel of the removed message will be passed into the | |||
| /// The source channel of the removed message will be passed into the | |||
| /// <see cref="ISocketMessageChannel"/> parameter. | |||
| /// </para> | |||
| /// </remarks> | |||
| @@ -143,7 +143,7 @@ namespace Discord.WebSocket | |||
| /// </note> | |||
| /// <para> | |||
| /// This event is fired when multiple messages are bulk deleted. The event handler must return a | |||
| /// <see cref="Task"/> and accept an <see cref="IReadOnlyCollection{Cacheable}"/> and | |||
| /// <see cref="Task"/> and accept an <see cref="IReadOnlyCollection{Cacheable}"/> and | |||
| /// <see cref="ISocketMessageChannel"/> as its parameters. | |||
| /// </para> | |||
| /// <para> | |||
| @@ -154,11 +154,11 @@ namespace Discord.WebSocket | |||
| /// </note> | |||
| /// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | |||
| /// <see cref="Cacheable{TEntity,TId}"/> 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 | |||
| /// <see cref="ulong"/>. | |||
| /// </para> | |||
| /// <para> | |||
| /// The source channel of the removed message will be passed into the | |||
| /// The source channel of the removed message will be passed into the | |||
| /// <see cref="ISocketMessageChannel"/> parameter. | |||
| /// </para> | |||
| /// </remarks> | |||
| @@ -178,14 +178,14 @@ namespace Discord.WebSocket | |||
| /// <para> | |||
| /// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | |||
| /// <see cref="Cacheable{TEntity,TId}"/> 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 | |||
| /// <see cref="ulong"/>. | |||
| /// </para> | |||
| /// <para> | |||
| /// The updated message will be passed into the <see cref="SocketMessage"/> parameter. | |||
| /// </para> | |||
| /// <para> | |||
| /// The source channel of the updated message will be passed into the | |||
| /// The source channel of the updated message will be passed into the | |||
| /// <see cref="ISocketMessageChannel"/> parameter. | |||
| /// </para> | |||
| /// </remarks> | |||
| @@ -199,24 +199,24 @@ namespace Discord.WebSocket | |||
| /// <remarks> | |||
| /// <para> | |||
| /// This event is fired when a reaction is added to a user message. The event handler must return a | |||
| /// <see cref="Task"/> and accept a <see cref="Cacheable{TEntity,TId}"/>, an | |||
| /// <see cref="Task"/> and accept a <see cref="Cacheable{TEntity,TId}"/>, an | |||
| /// <see cref="ISocketMessageChannel"/>, and a <see cref="SocketReaction"/> as its parameter. | |||
| /// </para> | |||
| /// <para> | |||
| /// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | |||
| /// <see cref="Cacheable{TEntity,TId}"/> 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 | |||
| /// <see cref="ulong"/>. | |||
| /// </para> | |||
| /// <para> | |||
| /// The source channel of the reaction addition will be passed into the | |||
| /// The source channel of the reaction addition will be passed into the | |||
| /// <see cref="ISocketMessageChannel"/> parameter. | |||
| /// </para> | |||
| /// <para> | |||
| /// The reaction that was added will be passed into the <see cref="SocketReaction"/> parameter. | |||
| /// </para> | |||
| /// <note> | |||
| /// 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 | |||
| /// <see cref="SocketReaction.User"/>. Please see the documentation of the property for more | |||
| /// information. | |||
| /// </note> | |||
| @@ -367,7 +367,7 @@ namespace Discord.WebSocket | |||
| } | |||
| internal readonly AsyncEvent<Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task>> _guildScheduledEventUpdated = new AsyncEvent<Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task>>(); | |||
| /// <summary> | |||
| /// Fired when a guild event is cancelled. | |||
| /// </summary> | |||
| @@ -877,5 +877,20 @@ namespace Discord.WebSocket | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketCustomSticker, Task>> _guildStickerDeleted = new AsyncEvent<Func<SocketCustomSticker, Task>>(); | |||
| #endregion | |||
| #region Webhooks | |||
| /// <summary> | |||
| /// Fired when a webhook is modified, moved, or deleted. If the webhook was | |||
| /// moved the channel represents the destination channel, not the source. | |||
| /// </summary> | |||
| public event Func<SocketGuild, SocketChannel, Task> WebhooksUpdated | |||
| { | |||
| add { _webhooksUpdated.Add(value); } | |||
| remove { _webhooksUpdated.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuild, SocketChannel, Task>> _webhooksUpdated = new AsyncEvent<Func<SocketGuild, SocketChannel, Task>>(); | |||
| #endregion | |||
| } | |||
| } | |||
| @@ -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 | |||
| @@ -2839,6 +2839,23 @@ namespace Discord.WebSocket | |||
| #endregion | |||
| #region Webhooks | |||
| case "WEBHOOKS_UPDATE": | |||
| { | |||
| var data = (payload as JToken).ToObject<WebhooksUpdatedEvent>(_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 | |||
| @@ -532,13 +532,10 @@ namespace Discord.WebSocket | |||
| Features = model.Features; | |||
| var roles = new ConcurrentDictionary<ulong, SocketRole>(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; | |||
| @@ -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) | |||
| { | |||