@@ -47,6 +47,12 @@ enough. Here is a list of recommended VPS provider. | |||||
* Location(s): | * Location(s): | ||||
* Europe: Lithuania | * Europe: Lithuania | ||||
* Based in: Europe | * 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 | ## .NET Core Deployment | ||||
@@ -100,4 +106,4 @@ Windows 10 x64 based machine: | |||||
* `dotnet publish -c Release -r win10-x64` | * `dotnet publish -c Release -r win10-x64` | ||||
[.NET Core application deployment]: https://docs.microsoft.com/en-us/dotnet/core/deploying/ | [.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. | For more information on dependency injection, read the [DependencyInjection] guides. | ||||
> [!NOTE] | > [!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 | ## 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, | > Although creating nested module stuctures are allowed, | ||||
> you are not permitted to use more than 2 [GroupAttribute]'s in module hierarchy. | > 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)] | [!code-csharp[Command Group Example](samples/intro/groupmodule.cs)] | ||||
## Executing Commands | ## Executing Commands | ||||
@@ -303,8 +308,19 @@ Any of the following socket events can be used to execute commands: | |||||
- [AutocompleteExecuted] | - [AutocompleteExecuted] | ||||
- [UserCommandExecuted] | - [UserCommandExecuted] | ||||
- [MessageCommandExecuted] | - [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. | You can also configure the way [InteractionService] executes the commands. | ||||
By default, commands are executed using `ConstructorInfo.Invoke()` to create module instances and | 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 | [AutocompleteExecuted]: xref:Discord.WebSocket.BaseSocketClient | ||||
[UserCommandExecuted]: xref:Discord.WebSocket.BaseSocketClient | [UserCommandExecuted]: xref:Discord.WebSocket.BaseSocketClient | ||||
[MessageCommandExecuted]: xref:Discord.WebSocket.BaseSocketClient | [MessageCommandExecuted]: xref:Discord.WebSocket.BaseSocketClient | ||||
[ModalExecuted]: xref:Discord.WebSocket.BaseSocketClient | |||||
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | ||||
[DiscordRestClient]: xref:Discord.Rest.DiscordRestClient | [DiscordRestClient]: xref:Discord.Rest.DiscordRestClient | ||||
[SocketInteractionContext]: xref:Discord.Interactions.SocketInteractionContext | [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 | // group-name subcommand-group-name echo | ||||
[SlashCommand("echo", "Echo an input")] | [SlashCommand("echo", "Echo an input")] | ||||
public async Task EchoSubcommand(string 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)] | [ModalTextInput("food_name", placeholder: "Pizza", maxLength: 20)] | ||||
public string Food { get; set; } | 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??")] | [InputLabel("Why??")] | ||||
[ModalTextInput("food_reason", TextInputStyle.Paragraph, "Kuz it's tasty", maxLength: 500)] | [ModalTextInput("food_reason", TextInputStyle.Paragraph, "Kuz it's tasty", maxLength: 500)] | ||||
public string Reason { get; set; } | public string Reason { get; set; } | ||||
@@ -22,10 +24,15 @@ public class FoodModal : IModal | |||||
[ModalInteraction("food_menu")] | [ModalInteraction("food_menu")] | ||||
public async Task ModalResponse(FoodModal modal) | 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. | // Build the message to send. | ||||
string message = "hey @everyone, I just learned " + | string message = "hey @everyone, I just learned " + | ||||
$"{Context.User.Mention}'s favorite food is " + | $"{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. | // Specify the AllowedMentions so we don't actually ping everyone. | ||||
AllowedMentions mentions = new(); | 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` | when developing on .NET Core, this is where you execute `dotnet run` | ||||
from; typically the same directory as your csproj). | 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/ | [Sodium]: https://download.libsodium.org/libsodium/releases/ | ||||
[Opus]: http://downloads.xiph.org/releases/opus/ | [Opus]: http://downloads.xiph.org/releases/opus/ | ||||
@@ -251,7 +251,7 @@ namespace Discord | |||||
private static Assembly _overrideDomain_Resolving(AssemblyLoadContext arg1, AssemblyName arg2) | private static Assembly _overrideDomain_Resolving(AssemblyLoadContext arg1, AssemblyName arg2) | ||||
{ | { | ||||
// resolve the override id | // 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(); | return GetDependencyAsync(v.Key.Id, $"{arg2}").GetAwaiter().GetResult(); | ||||
} | } | ||||
@@ -22,6 +22,7 @@ namespace ShardedClient.Services | |||||
_service.Log += LogAsync; | _service.Log += LogAsync; | ||||
_client.InteractionCreated += OnInteractionAsync; | _client.InteractionCreated += OnInteractionAsync; | ||||
_client.ShardReady += ReadyAsync; | |||||
// For examples on how to handle post execution, | // For examples on how to handle post execution, | ||||
// see the InteractionFramework samples. | // see the InteractionFramework samples. | ||||
} | } | ||||
@@ -30,11 +31,6 @@ namespace ShardedClient.Services | |||||
public async Task InitializeAsync() | public async Task InitializeAsync() | ||||
{ | { | ||||
await _service.AddModulesAsync(typeof(InteractionHandlingService).Assembly, _provider); | 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) | private async Task OnInteractionAsync(SocketInteraction interaction) | ||||
@@ -53,5 +49,14 @@ namespace ShardedClient.Services | |||||
return Task.CompletedTask; | 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 | try | ||||
{ | { | ||||
await instance.BeforeExecuteAsync(cmd).ConfigureAwait(false); | |||||
instance.BeforeExecute(cmd); | instance.BeforeExecute(cmd); | ||||
var task = method.Invoke(instance, args) as Task ?? Task.Delay(0); | var task = method.Invoke(instance, args) as Task ?? Task.Delay(0); | ||||
@@ -221,6 +222,7 @@ namespace Discord.Commands | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
await instance.AfterExecuteAsync(cmd).ConfigureAwait(false); | |||||
instance.AfterExecute(cmd); | instance.AfterExecute(cmd); | ||||
(instance as IDisposable)?.Dispose(); | (instance as IDisposable)?.Dispose(); | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
using Discord.Commands.Builders; | using Discord.Commands.Builders; | ||||
using System.Threading.Tasks; | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
@@ -13,12 +14,24 @@ namespace Discord.Commands | |||||
/// <param name="context">The context to set.</param> | /// <param name="context">The context to set.</param> | ||||
void SetContext(ICommandContext context); | 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> | /// <summary> | ||||
/// Executed before a command is run in this module base. | /// Executed before a command is run in this module base. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="command">The command thats about to run.</param> | /// <param name="command">The command thats about to run.</param> | ||||
void BeforeExecute(CommandInfo command); | 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> | /// <summary> | ||||
/// Executed after a command is ran in this module base. | /// Executed after a command is ran in this module base. | ||||
/// </summary> | /// </summary> | ||||
@@ -46,6 +46,11 @@ namespace Discord.Commands | |||||
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | ||||
} | } | ||||
/// <summary> | /// <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. | /// The method to execute before executing the command. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | /// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | ||||
@@ -53,6 +58,11 @@ namespace Discord.Commands | |||||
{ | { | ||||
} | } | ||||
/// <summary> | /// <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. | /// The method to execute after executing the command. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param> | /// <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; | var newValue = context as T; | ||||
Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}."); | 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); | void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command); | ||||
Task IModuleBase.AfterExecuteAsync(CommandInfo command) => AfterExecuteAsync(command); | |||||
void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); | void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); | ||||
void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder); | void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder); | ||||
#endregion | #endregion | ||||
@@ -12,174 +12,174 @@ namespace Discord | |||||
/// <summary> | /// <summary> | ||||
/// The guild has no features. | /// The guild has no features. | ||||
/// </summary> | /// </summary> | ||||
None = 0, | |||||
None = 0L, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to animated banners. | /// The guild has access to animated banners. | ||||
/// </summary> | /// </summary> | ||||
AnimatedBanner = 1 << 0, | |||||
AnimatedBanner = 1L << 0, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to set an animated guild icon. | /// The guild has access to set an animated guild icon. | ||||
/// </summary> | /// </summary> | ||||
AnimatedIcon = 1 << 1, | |||||
AnimatedIcon = 1L << 1, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to set a guild banner image. | /// The guild has access to set a guild banner image. | ||||
/// </summary> | /// </summary> | ||||
Banner = 1 << 2, | |||||
Banner = 1L << 2, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to channel banners. | /// The guild has access to channel banners. | ||||
/// </summary> | /// </summary> | ||||
ChannelBanner = 1 << 3, | |||||
ChannelBanner = 1L << 3, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to use commerce features (i.e. create store channels). | /// The guild has access to use commerce features (i.e. create store channels). | ||||
/// </summary> | /// </summary> | ||||
Commerce = 1 << 4, | |||||
Commerce = 1L << 4, | |||||
/// <summary> | /// <summary> | ||||
/// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates. | /// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates. | ||||
/// </summary> | /// </summary> | ||||
Community = 1 << 5, | |||||
Community = 1L << 5, | |||||
/// <summary> | /// <summary> | ||||
/// The guild is able to be discovered in the directory. | /// The guild is able to be discovered in the directory. | ||||
/// </summary> | /// </summary> | ||||
Discoverable = 1 << 6, | |||||
Discoverable = 1L << 6, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has discoverable disabled. | /// The guild has discoverable disabled. | ||||
/// </summary> | /// </summary> | ||||
DiscoverableDisabled = 1 << 7, | |||||
DiscoverableDisabled = 1L << 7, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has enabled discoverable before. | /// The guild has enabled discoverable before. | ||||
/// </summary> | /// </summary> | ||||
EnabledDiscoverableBefore = 1 << 8, | |||||
EnabledDiscoverableBefore = 1L << 8, | |||||
/// <summary> | /// <summary> | ||||
/// The guild is able to be featured in the directory. | /// The guild is able to be featured in the directory. | ||||
/// </summary> | /// </summary> | ||||
Featureable = 1 << 9, | |||||
Featureable = 1L << 9, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has a force relay. | /// The guild has a force relay. | ||||
/// </summary> | /// </summary> | ||||
ForceRelay = 1 << 10, | |||||
ForceRelay = 1L << 10, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has a directory entry. | /// The guild has a directory entry. | ||||
/// </summary> | /// </summary> | ||||
HasDirectoryEntry = 1 << 11, | |||||
HasDirectoryEntry = 1L << 11, | |||||
/// <summary> | /// <summary> | ||||
/// The guild is a hub. | /// The guild is a hub. | ||||
/// </summary> | /// </summary> | ||||
Hub = 1 << 12, | |||||
Hub = 1L << 12, | |||||
/// <summary> | /// <summary> | ||||
/// You shouldn't be here... | /// You shouldn't be here... | ||||
/// </summary> | /// </summary> | ||||
InternalEmployeeOnly = 1 << 13, | |||||
InternalEmployeeOnly = 1L << 13, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to set an invite splash background. | /// The guild has access to set an invite splash background. | ||||
/// </summary> | /// </summary> | ||||
InviteSplash = 1 << 14, | |||||
InviteSplash = 1L << 14, | |||||
/// <summary> | /// <summary> | ||||
/// The guild is linked to a hub. | /// The guild is linked to a hub. | ||||
/// </summary> | /// </summary> | ||||
LinkedToHub = 1 << 15, | |||||
LinkedToHub = 1L << 15, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has member profiles. | /// The guild has member profiles. | ||||
/// </summary> | /// </summary> | ||||
MemberProfiles = 1 << 16, | |||||
MemberProfiles = 1L << 16, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has enabled <seealso href="https://discord.com/developers/docs/resources/guild#membership-screening-object">Membership Screening</seealso>. | /// The guild has enabled <seealso href="https://discord.com/developers/docs/resources/guild#membership-screening-object">Membership Screening</seealso>. | ||||
/// </summary> | /// </summary> | ||||
MemberVerificationGateEnabled = 1 << 17, | |||||
MemberVerificationGateEnabled = 1L << 17, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has enabled monetization. | /// The guild has enabled monetization. | ||||
/// </summary> | /// </summary> | ||||
MonetizationEnabled = 1 << 18, | |||||
MonetizationEnabled = 1L << 18, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has more emojis. | /// The guild has more emojis. | ||||
/// </summary> | /// </summary> | ||||
MoreEmoji = 1 << 19, | |||||
MoreEmoji = 1L << 19, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has increased custom sticker slots. | /// The guild has increased custom sticker slots. | ||||
/// </summary> | /// </summary> | ||||
MoreStickers = 1 << 20, | |||||
MoreStickers = 1L << 20, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to create news channels. | /// The guild has access to create news channels. | ||||
/// </summary> | /// </summary> | ||||
News = 1 << 21, | |||||
News = 1L << 21, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has new thread permissions. | /// The guild has new thread permissions. | ||||
/// </summary> | /// </summary> | ||||
NewThreadPermissions = 1 << 22, | |||||
NewThreadPermissions = 1L << 22, | |||||
/// <summary> | /// <summary> | ||||
/// The guild is partnered. | /// The guild is partnered. | ||||
/// </summary> | /// </summary> | ||||
Partnered = 1 << 23, | |||||
Partnered = 1L << 23, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has a premium tier three override; guilds made by Discord usually have this. | /// The guild has a premium tier three override; guilds made by Discord usually have this. | ||||
/// </summary> | /// </summary> | ||||
PremiumTier3Override = 1 << 24, | |||||
PremiumTier3Override = 1L << 24, | |||||
/// <summary> | /// <summary> | ||||
/// The guild can be previewed before joining via Membership Screening or the directory. | /// The guild can be previewed before joining via Membership Screening or the directory. | ||||
/// </summary> | /// </summary> | ||||
PreviewEnabled = 1 << 25, | |||||
PreviewEnabled = 1L << 25, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to create private threads. | /// The guild has access to create private threads. | ||||
/// </summary> | /// </summary> | ||||
PrivateThreads = 1 << 26, | |||||
PrivateThreads = 1L << 26, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has relay enabled. | /// The guild has relay enabled. | ||||
/// </summary> | /// </summary> | ||||
RelayEnabled = 1 << 27, | |||||
RelayEnabled = 1L << 27, | |||||
/// <summary> | /// <summary> | ||||
/// The guild is able to set role icons. | /// The guild is able to set role icons. | ||||
/// </summary> | /// </summary> | ||||
RoleIcons = 1 << 28, | |||||
RoleIcons = 1L << 28, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has role subscriptions available for purchase. | /// The guild has role subscriptions available for purchase. | ||||
/// </summary> | /// </summary> | ||||
RoleSubscriptionsAvailableForPurchase = 1 << 29, | |||||
RoleSubscriptionsAvailableForPurchase = 1L << 29, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has role subscriptions enabled. | /// The guild has role subscriptions enabled. | ||||
/// </summary> | /// </summary> | ||||
RoleSubscriptionsEnabled = 1 << 30, | |||||
RoleSubscriptionsEnabled = 1L << 30, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to the seven day archive time for threads. | /// The guild has access to the seven day archive time for threads. | ||||
/// </summary> | /// </summary> | ||||
SevenDayThreadArchive = 1 << 31, | |||||
SevenDayThreadArchive = 1L << 31, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has text in voice enabled. | /// The guild has text in voice enabled. | ||||
/// </summary> | /// </summary> | ||||
TextInVoiceEnabled = 1 << 32, | |||||
TextInVoiceEnabled = 1L << 32, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has threads enabled. | /// The guild has threads enabled. | ||||
/// </summary> | /// </summary> | ||||
ThreadsEnabled = 1 << 33, | |||||
ThreadsEnabled = 1L << 33, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has testing threads enabled. | /// The guild has testing threads enabled. | ||||
/// </summary> | /// </summary> | ||||
ThreadsEnabledTesting = 1 << 34, | |||||
ThreadsEnabledTesting = 1L << 34, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has the default thread auto archive. | /// The guild has the default thread auto archive. | ||||
/// </summary> | /// </summary> | ||||
ThreadsDefaultAutoArchiveDuration = 1 << 35, | |||||
ThreadsDefaultAutoArchiveDuration = 1L << 35, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to the three day archive time for threads. | /// The guild has access to the three day archive time for threads. | ||||
/// </summary> | /// </summary> | ||||
ThreeDayThreadArchive = 1 << 36, | |||||
ThreeDayThreadArchive = 1L << 36, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has enabled ticketed events. | /// The guild has enabled ticketed events. | ||||
/// </summary> | /// </summary> | ||||
TicketedEventsEnabled = 1 << 37, | |||||
TicketedEventsEnabled = 1L << 37, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to set a vanity URL. | /// The guild has access to set a vanity URL. | ||||
/// </summary> | /// </summary> | ||||
VanityUrl = 1 << 38, | |||||
VanityUrl = 1L << 38, | |||||
/// <summary> | /// <summary> | ||||
/// The guild is verified. | /// The guild is verified. | ||||
/// </summary> | /// </summary> | ||||
Verified = 1 << 39, | |||||
Verified = 1L << 39, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers). | /// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers). | ||||
/// </summary> | /// </summary> | ||||
VIPRegions = 1 << 40, | |||||
VIPRegions = 1L << 40, | |||||
/// <summary> | /// <summary> | ||||
/// The guild has enabled the welcome screen. | /// The guild has enabled the welcome screen. | ||||
/// </summary> | /// </summary> | ||||
WelcomeScreenEnabled = 1 << 41, | |||||
WelcomeScreenEnabled = 1L << 41, | |||||
} | } | ||||
} | } |
@@ -9,10 +9,11 @@ namespace Discord.Interactions | |||||
public NullableConverter(InteractionService interactionService, IServiceProvider services) | 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) | 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); | _typeConverter = interactionService.GetTypeConverter(type, services); | ||||
} | } | ||||
@@ -1756,7 +1756,7 @@ namespace Discord.API | |||||
if (args.TargetType.Value == TargetUserType.Stream) | if (args.TargetType.Value == TargetUserType.Stream) | ||||
Preconditions.GreaterThan(args.TargetUserId, 0, nameof(args.TargetUserId)); | Preconditions.GreaterThan(args.TargetUserId, 0, nameof(args.TargetUserId)); | ||||
if (args.TargetType.Value == TargetUserType.EmbeddedApplication) | 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); | options = RequestOptions.CreateOrClone(options); | ||||
@@ -15,7 +15,7 @@ namespace Discord.Rest | |||||
Thread = thread; | Thread = thread; | ||||
ThreadType = type; | ThreadType = type; | ||||
Before = before; | Before = before; | ||||
After = After; | |||||
After = after; | |||||
} | } | ||||
internal static ThreadUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | 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); | var ids = args.Roles.Value.Select(r => r.Id); | ||||
if (args.RoleIds.IsSpecified) | if (args.RoleIds.IsSpecified) | ||||
args.RoleIds.Value.Concat(ids); | |||||
args.RoleIds = Optional.Create(args.RoleIds.Value.Concat(ids)); | |||||
else | else | ||||
args.RoleIds = Optional.Create(ids); | args.RoleIds = Optional.Create(ids); | ||||
} | } | ||||
@@ -426,7 +426,7 @@ namespace Discord.Rest | |||||
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) | AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) | ||||
=> await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | ||||
/// <inheritdoc/> | /// <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) | AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) | ||||
=> await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | => await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | ||||
/// <inheritdoc/> | /// <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> | /// <summary> Fired when a channel is updated. </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// <para> | /// <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. | /// <see cref="Task"/> and accept 2 <see cref="SocketChannel"/> as its parameters. | ||||
/// </para> | /// </para> | ||||
/// <para> | /// <para> | ||||
@@ -106,7 +106,7 @@ namespace Discord.WebSocket | |||||
/// <remarks> | /// <remarks> | ||||
/// <para> | /// <para> | ||||
/// This event is fired when a message is deleted. The event handler must return a | /// 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. | /// <see cref="ISocketMessageChannel"/> as its parameters. | ||||
/// </para> | /// </para> | ||||
/// <para> | /// <para> | ||||
@@ -117,11 +117,11 @@ namespace Discord.WebSocket | |||||
/// </note> | /// </note> | ||||
/// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | /// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | ||||
/// <see cref="Cacheable{TEntity,TId}"/> entity will contain the deleted message; otherwise, in event | /// <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"/>. | /// <see cref="ulong"/>. | ||||
/// </para> | /// </para> | ||||
/// <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. | /// <see cref="ISocketMessageChannel"/> parameter. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
@@ -143,7 +143,7 @@ namespace Discord.WebSocket | |||||
/// </note> | /// </note> | ||||
/// <para> | /// <para> | ||||
/// This event is fired when multiple messages are bulk deleted. The event handler must return a | /// 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. | /// <see cref="ISocketMessageChannel"/> as its parameters. | ||||
/// </para> | /// </para> | ||||
/// <para> | /// <para> | ||||
@@ -154,11 +154,11 @@ namespace Discord.WebSocket | |||||
/// </note> | /// </note> | ||||
/// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | /// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | ||||
/// <see cref="Cacheable{TEntity,TId}"/> entity will contain the deleted message; otherwise, in event | /// <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"/>. | /// <see cref="ulong"/>. | ||||
/// </para> | /// </para> | ||||
/// <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. | /// <see cref="ISocketMessageChannel"/> parameter. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
@@ -178,14 +178,14 @@ namespace Discord.WebSocket | |||||
/// <para> | /// <para> | ||||
/// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | /// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | ||||
/// <see cref="Cacheable{TEntity,TId}"/> entity will contain the original message; otherwise, in event | /// <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"/>. | /// <see cref="ulong"/>. | ||||
/// </para> | /// </para> | ||||
/// <para> | /// <para> | ||||
/// The updated message will be passed into the <see cref="SocketMessage"/> parameter. | /// The updated message will be passed into the <see cref="SocketMessage"/> parameter. | ||||
/// </para> | /// </para> | ||||
/// <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. | /// <see cref="ISocketMessageChannel"/> parameter. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
@@ -199,24 +199,24 @@ namespace Discord.WebSocket | |||||
/// <remarks> | /// <remarks> | ||||
/// <para> | /// <para> | ||||
/// This event is fired when a reaction is added to a user message. The event handler must return a | /// 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. | /// <see cref="ISocketMessageChannel"/>, and a <see cref="SocketReaction"/> as its parameter. | ||||
/// </para> | /// </para> | ||||
/// <para> | /// <para> | ||||
/// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | /// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | ||||
/// <see cref="Cacheable{TEntity,TId}"/> entity will contain the original message; otherwise, in event | /// <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"/>. | /// <see cref="ulong"/>. | ||||
/// </para> | /// </para> | ||||
/// <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. | /// <see cref="ISocketMessageChannel"/> parameter. | ||||
/// </para> | /// </para> | ||||
/// <para> | /// <para> | ||||
/// The reaction that was added will be passed into the <see cref="SocketReaction"/> parameter. | /// The reaction that was added will be passed into the <see cref="SocketReaction"/> parameter. | ||||
/// </para> | /// </para> | ||||
/// <note> | /// <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 | /// <see cref="SocketReaction.User"/>. Please see the documentation of the property for more | ||||
/// information. | /// information. | ||||
/// </note> | /// </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>>(); | internal readonly AsyncEvent<Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task>> _guildScheduledEventUpdated = new AsyncEvent<Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task>>(); | ||||
/// <summary> | /// <summary> | ||||
/// Fired when a guild event is cancelled. | /// Fired when a guild event is cancelled. | ||||
/// </summary> | /// </summary> | ||||
@@ -877,5 +877,20 @@ namespace Discord.WebSocket | |||||
} | } | ||||
internal readonly AsyncEvent<Func<SocketCustomSticker, Task>> _guildStickerDeleted = new AsyncEvent<Func<SocketCustomSticker, Task>>(); | internal readonly AsyncEvent<Func<SocketCustomSticker, Task>> _guildStickerDeleted = new AsyncEvent<Func<SocketCustomSticker, Task>>(); | ||||
#endregion | #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.GuildScheduledEventStarted += (arg) => _guildScheduledEventStarted.InvokeAsync(arg); | ||||
client.GuildScheduledEventUserAdd += (arg1, arg2) => _guildScheduledEventUserAdd.InvokeAsync(arg1, arg2); | client.GuildScheduledEventUserAdd += (arg1, arg2) => _guildScheduledEventUserAdd.InvokeAsync(arg1, arg2); | ||||
client.GuildScheduledEventUserRemove += (arg1, arg2) => _guildScheduledEventUserRemove.InvokeAsync(arg1, arg2); | client.GuildScheduledEventUserRemove += (arg1, arg2) => _guildScheduledEventUserRemove.InvokeAsync(arg1, arg2); | ||||
client.WebhooksUpdated += (arg1, arg2) => _webhooksUpdated.InvokeAsync(arg1, arg2); | |||||
} | } | ||||
#endregion | #endregion | ||||
@@ -2839,6 +2839,23 @@ namespace Discord.WebSocket | |||||
#endregion | #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) | #region Ignored (User only) | ||||
case "CHANNEL_PINS_ACK": | case "CHANNEL_PINS_ACK": | ||||
await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | ||||
@@ -2858,9 +2875,6 @@ namespace Discord.WebSocket | |||||
case "USER_SETTINGS_UPDATE": | case "USER_SETTINGS_UPDATE": | ||||
await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | ||||
break; | break; | ||||
case "WEBHOOKS_UPDATE": | |||||
await _gatewayLogger.DebugAsync("Ignored Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); | |||||
break; | |||||
#endregion | #endregion | ||||
#region Others | #region Others | ||||
@@ -532,13 +532,10 @@ namespace Discord.WebSocket | |||||
Features = model.Features; | Features = model.Features; | ||||
var roles = new ConcurrentDictionary<ulong, SocketRole>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Roles.Length * 1.05)); | 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; | _roles = roles; | ||||
@@ -39,7 +39,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
foreach (var channel in resolved.Channels.Value) | foreach (var channel in resolved.Channels.Value) | ||||
{ | { | ||||
SocketChannel socketChannel = guild != null | |||||
var socketChannel = guild != null | |||||
? guild.GetChannel(channel.Value.Id) | ? guild.GetChannel(channel.Value.Id) | ||||
: discord.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) | foreach (var role in resolved.Roles.Value) | ||||
{ | { | ||||