@@ -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) | |||
{ | |||