@@ -18,7 +18,8 @@ body: | |||||
attributes: | attributes: | ||||
label: Verify Issue Source | label: Verify Issue Source | ||||
description: If your issue is related to an exception make sure the error was thrown by Discord.Net, and not your code or another library. | description: If your issue is related to an exception make sure the error was thrown by Discord.Net, and not your code or another library. | ||||
If you get an `HttpException` with the error code `401`, then the error is caused by your bot's permissions, not dnet. | |||||
If you get an `HttpException` with the error code `401`, then the error is caused by your bot's permissions, not dnet. | |||||
If you have a issue that does directly relate to an API bug, feel free to open a [Q&A Discussion](https://github.com/discord-net/Discord.Net/discussions) | |||||
options: | options: | ||||
- label: I verified the issue was caused by Discord.Net. | - label: I verified the issue was caused by Discord.Net. | ||||
required: true | required: true | ||||
@@ -75,3 +76,11 @@ body: | |||||
``` | ``` | ||||
validations: | validations: | ||||
required: false | required: false | ||||
- type: textarea | |||||
id: packages | |||||
attributes: | |||||
label: Packages | |||||
description: Please list all 3rd party packages in use if applicable, including their versions. | |||||
placeholder: Discord.Addons.Hosting V5.1.0, Discord.InteractivityAddon V2.4.0, etc. | |||||
validations: | |||||
required: true |
@@ -1,5 +1,53 @@ | |||||
# Changelog | # Changelog | ||||
## [3.4.0] - 2022-3-2 | |||||
## Added | |||||
- #2146 Add FromDateTimeOffset in TimestampTag (553055b) | |||||
- #2062 Add return statement to precondition handling (3e52fab) | |||||
- #2131 Add support for sending Message Flags (1fb62de) | |||||
- #2137 Add self_video to VoiceState (8bcd3da) | |||||
- #2151 Add Image property to Guild Scheduled Events (1dc473c) | |||||
- #2152 Add missing json error codes (202554f) | |||||
- #2153 Add IsInvitable and CreatedAt to threads (6bf5818) | |||||
- #2155 Add Interaction Service Complex Parameters (9ba64f6) | |||||
- #2156 Add Display name support for enum type converter (c800674) | |||||
## Fixed | |||||
- #2117 Fix stream access exception when ratelimited (a1cfa41) | |||||
- #2128 Fix context menu comand message type (f601e9b) | |||||
- #2135 Fix NRE when ratelimmited requests don't return a body (b95b942) | |||||
- #2154 Fix usage of CacheMode.AllowDownload in channels (b3370c3) | |||||
## Misc | |||||
- #2149 Clarify Users property on SocketGuildChannel (5594739) | |||||
- #2157 Enforce valid button styles (507a18d) | |||||
## [3.3.2] - 2022-02-16 | |||||
### Fixed | |||||
- #2116 Fix null rest client in shards | |||||
## [3.3.1] - 2022-02-16 | |||||
### Added | |||||
- #2107 Add DisplayName property to IGuildUser. (abfba3c) | |||||
### Fixed | |||||
- #2110 Fix incorrect ratelimit handles for 429's (b2598d3) | |||||
- #2094 Fix ToString() on CommandInfo (01735c8) | |||||
- #2098 Fix channel being null in DMs on Interactions (7e1b8c9) | |||||
- #2100 Fix crosspost ratelimits (fad217e) | |||||
- #2108 Fix being unable to modify AllowedMentions with no embeds set. (169d54f) | |||||
- #2109 Fix unused creation of REST clients for DiscordShardedClient shards. (6039378) | |||||
### Misc | |||||
- #2099 Update interaction summaries (503d32a) | |||||
## [3.3.0] - 2022-02-09 | ## [3.3.0] - 2022-02-09 | ||||
### Added | ### Added | ||||
@@ -1,6 +1,6 @@ | |||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
<PropertyGroup> | <PropertyGroup> | ||||
<VersionPrefix>3.3.0</VersionPrefix> | |||||
<VersionPrefix>3.4.0</VersionPrefix> | |||||
<LangVersion>latest</LangVersion> | <LangVersion>latest</LangVersion> | ||||
<Authors>Discord.Net Contributors</Authors> | <Authors>Discord.Net Contributors</Authors> | ||||
<PackageTags>discord;discordapp</PackageTags> | <PackageTags>discord;discordapp</PackageTags> | ||||
@@ -60,7 +60,7 @@ | |||||
"overwrite": "_overwrites/**/**.md", | "overwrite": "_overwrites/**/**.md", | ||||
"globalMetadata": { | "globalMetadata": { | ||||
"_appTitle": "Discord.Net Documentation", | "_appTitle": "Discord.Net Documentation", | ||||
"_appFooter": "Discord.Net (c) 2015-2022 3.3.0", | |||||
"_appFooter": "Discord.Net (c) 2015-2022 3.4.0", | |||||
"_enableSearch": true, | "_enableSearch": true, | ||||
"_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg", | "_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg", | ||||
"_appFaviconPath": "favicon.ico" | "_appFaviconPath": "favicon.ico" | ||||
@@ -1,12 +1,12 @@ | |||||
--- | --- | ||||
uid: FAQ.Commands.DI | |||||
title: Questions about Dependency Injection with Commands | |||||
uid: FAQ.Basics.DI | |||||
title: Questions about Dependency Injection. | |||||
--- | --- | ||||
# Dependency-injection-related Questions | # Dependency-injection-related Questions | ||||
In the following section, you will find common questions and answers | In the following section, you will find common questions and answers | ||||
to utilizing dependency injection with @Discord.Commands, as well as | |||||
to utilizing dependency injection with @Discord.Commands and @Discord.Interactions, as well as | |||||
common troubleshooting steps regarding DI. | common troubleshooting steps regarding DI. | ||||
## What is a service? Why does my module not hold any data after execution? | ## What is a service? Why does my module not hold any data after execution? | ||||
@@ -22,8 +22,7 @@ Service is often used to hold data externally so that they persist | |||||
throughout execution. Think of it like a chest that holds | throughout execution. Think of it like a chest that holds | ||||
whatever you throw at it that won't be affected by anything unless | whatever you throw at it that won't be affected by anything unless | ||||
you want it to. Note that you should also learn Microsoft's | you want it to. Note that you should also learn Microsoft's | ||||
implementation of [Dependency Injection] \([video]) before proceeding, | |||||
as well as how it works in [Discord.Net](xref:Guides.TextCommands.DI#usage-in-modules). | |||||
implementation of [Dependency Injection] \([video]) before proceeding. | |||||
A brief example of service and dependency injection can be seen below. | A brief example of service and dependency injection can be seen below. | ||||
@@ -32,18 +31,12 @@ A brief example of service and dependency injection can be seen below. | |||||
[Dependency Injection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection | [Dependency Injection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection | ||||
[video]: https://www.youtube.com/watch?v=QtDTfn8YxXg | [video]: https://www.youtube.com/watch?v=QtDTfn8YxXg | ||||
## Why is my `CommandService` complaining about a missing dependency? | |||||
## Why is my Command/Interaction Service complaining about a missing dependency? | |||||
If you encounter an error similar to `Failed to create MyModule, | If you encounter an error similar to `Failed to create MyModule, | ||||
dependency MyExternalDependency was not found.`, you may have | dependency MyExternalDependency was not found.`, you may have | ||||
forgotten to add the external dependency to the dependency container. | forgotten to add the external dependency to the dependency container. | ||||
Starting from Discord.Net 2.0, all dependencies required by each | |||||
module must be present when the module is loaded into the | |||||
[CommandService]. This means when loading the module, you must pass a | |||||
valid [IServiceProvider] with the dependency loaded before the module | |||||
can be successfully registered. | |||||
For example, if your module, `MyModule`, requests a `DatabaseService` | For example, if your module, `MyModule`, requests a `DatabaseService` | ||||
in its constructor, the `DatabaseService` must be present in the | in its constructor, the `DatabaseService` must be present in the | ||||
[IServiceProvider] when registering `MyModule`. | [IServiceProvider] when registering `MyModule`. | ||||
@@ -51,4 +44,3 @@ in its constructor, the `DatabaseService` must be present in the | |||||
[!code-csharp[Missing Dependencies](samples/missing-dep.cs)] | [!code-csharp[Missing Dependencies](samples/missing-dep.cs)] | ||||
[IServiceProvider]: xref:System.IServiceProvider | [IServiceProvider]: xref:System.IServiceProvider | ||||
[CommandService]: xref:Discord.Commands.CommandService |
@@ -11,18 +11,32 @@ introduction to the Discord API ecosystem. | |||||
## How do I add my bot to my server/guild? | ## How do I add my bot to my server/guild? | ||||
You can do so by using the [permission calculator] provided | |||||
by [FiniteReality]. | |||||
This tool allows you to set permissions that the bot will be assigned | |||||
with, and invite the bot into your guild. With this method, bots will | |||||
also be assigned a unique role that a regular user cannot use; this | |||||
is what we call a `Managed` role. Because you cannot assign this | |||||
role to any other users, it is much safer than creating a single | |||||
role which, intentionally or not, can be applied to other users | |||||
to escalate their privilege. | |||||
[FiniteReality]: https://github.com/FiniteReality/permissions-calculator | |||||
[permission calculator]: https://finitereality.github.io/permissions-calculator | |||||
Inviting your bot can be done by using the OAuth2 url generator provided by the [Discord Developer Portal]. | |||||
Permissions can be granted by selecting the `bot` scope in the scopes section. | |||||
 | |||||
A permissions tab will appear below the scope selection, | |||||
from which you can pick any permissions your bot may require to function. | |||||
When invited, the role this bot is granted will include these permissions. | |||||
If you grant no permissions, no role will be created for your bot upon invitation as there is no need for one. | |||||
 | |||||
When done selecting permissions, you can use the link below in your browser to invite the bot | |||||
to servers where you have the `Manage Server` permission. | |||||
 | |||||
If you are planning to play around with slash/context commands, | |||||
make sure to check the `application commands` scope before inviting your bot! | |||||
> [!NOTE] | |||||
> You do not have to kick and reinvite your bot to update permissions/scopes later on. | |||||
> Simply reusing the invite link with provided scopes/perms will update it accordingly. | |||||
[Discord Developer Portal]: https://discord.com/developers/applications/ | |||||
## What is a token? | ## What is a token? | ||||
@@ -11,8 +11,8 @@ public class CommandHandler | |||||
public CommandHandler(DiscordSocketClient client) | public CommandHandler(DiscordSocketClient client) | ||||
{ | { | ||||
_services = new ServiceCollection() | _services = new ServiceCollection() | ||||
.AddService<CommandService>() | |||||
.AddService(client) | |||||
.AddSingleton<CommandService>() | |||||
.AddSingleton(client) | |||||
// We are missing DatabaseService! | // We are missing DatabaseService! | ||||
.BuildServiceProvider(); | .BuildServiceProvider(); | ||||
} | } | ||||
@@ -25,5 +25,8 @@ public class CommandHandler | |||||
// registered in this instance of _services. | // registered in this instance of _services. | ||||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); | await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); | ||||
// ... | // ... | ||||
// The same approach applies to the interaction service. | |||||
// Make sure to resolve these issues! | |||||
} | } | ||||
} | |||||
} |
@@ -1,34 +1,54 @@ | |||||
--- | --- | ||||
uid: FAQ.Commands.Interactions | |||||
title: Interaction service | |||||
uid: FAQ.Interactions.Framework | |||||
title: Interaction Framework | |||||
--- | --- | ||||
# Interaction commands in services | |||||
# The Interaction Framework | |||||
A chapter talking about the interaction service framework. | |||||
For questions about interactions in general, refer to the [Interactions FAQ] | |||||
Common misconceptions and questions about the Interaction Framework. | |||||
## How can I restrict some of my commands so only specific users can execute them? | |||||
Based on how you want to implement the restrictions, you can use the | |||||
built-in `RequireUserPermission` precondition, which allows you to | |||||
restrict the command based on the user's current permissions in the | |||||
guild or channel (*e.g., `GuildPermission.Administrator`, | |||||
`ChannelPermission.ManageMessages`*). | |||||
[RequireUserPermission]: xref:Discord.Commands.RequireUserPermissionAttribute | |||||
> [!NOTE] | |||||
> There are many more preconditions to use, including being able to make some yourself. | |||||
> Examples on self-made preconditions can be found | |||||
> [here](https://github.com/discord-net/Discord.Net/blob/dev/samples/InteractionFramework/Attributes/RequireOwnerAttribute.cs) | |||||
## Why do preconditions not hide my commands? | |||||
In the current permission design by Discord, | |||||
it is not very straight forward to limit vision of slash/context commands to users. | |||||
If you want to hide commands, you should take a look at the commands' `DefaultPermissions` parameter. | |||||
## Module dependencies aren't getting populated by Property Injection? | ## Module dependencies aren't getting populated by Property Injection? | ||||
Make sure the properties are publicly accessible and publicly settable. | Make sure the properties are publicly accessible and publicly settable. | ||||
## How do I use this * interaction specific method/property? | |||||
If your interaction context holds a down-casted version of the interaction object, you need to up-cast it. | |||||
Ideally, use pattern matching to make sure its the type of interaction you are expecting it to be. | |||||
[!code-csharp[Property Injection](samples/propertyinjection.cs)] | |||||
## `InteractionService.ExecuteAsync()` always returns a successful result, how do i access the failed command execution results? | ## `InteractionService.ExecuteAsync()` always returns a successful result, how do i access the failed command execution results? | ||||
If you are using `RunMode.Async` you need to setup your post-execution pipeline around `CommandExecuted` events. | |||||
If you are using `RunMode.Async` you need to setup your post-execution pipeline around | |||||
`..Executed` events exposed by the Interaction Service. | |||||
## How do I check if the executing user has * permission? | ## How do I check if the executing user has * permission? | ||||
Refer to the [documentation about preconditions] | Refer to the [documentation about preconditions] | ||||
[documentation about preconditions]: xref:Guides.ChatCommands.Preconditions | |||||
## How do I send the HTTP Response from inside the command modules. | ## How do I send the HTTP Response from inside the command modules. | ||||
Set the `RestResponseCallback` property of [InteractionServiceConfig] with a delegate for handling HTTP Responses and use | Set the `RestResponseCallback` property of [InteractionServiceConfig] with a delegate for handling HTTP Responses and use | ||||
`RestInteractionModuleBase` to create your command modules. `RespondAsync()` and `DeferAsync()` methods of this module base will use the | |||||
`RestInteractionModuleBase` to create your command modules. `RespondWithModalAsync()`, `RespondAsync()` and `DeferAsync()` methods of this module base will use the | |||||
`RestResponseCallback` to create interaction responses. | `RestResponseCallback` to create interaction responses. | ||||
## Is there a cleaner way of creating parameter choices other than using `[Choice]`? | ## Is there a cleaner way of creating parameter choices other than using `[Choice]`? | ||||
@@ -49,4 +69,3 @@ It compares the _target base type_ key of the | |||||
[TypeConverter]: xref:Discord.Interactions.TypeConverter | [TypeConverter]: xref:Discord.Interactions.TypeConverter | ||||
[Interactions FAQ]: xref: FAQ.Basics.Interactions | [Interactions FAQ]: xref: FAQ.Basics.Interactions | ||||
[InteractionServiceConfig]: xref:Discord.Interactions.InteractionServiceConfig | [InteractionServiceConfig]: xref:Discord.Interactions.InteractionServiceConfig | ||||
[documentation about preconditions]: xref: Guides.ChatCommands.Preconditions |
@@ -1,11 +1,13 @@ | |||||
--- | --- | ||||
uid: FAQ.Basics.InteractionBasics | |||||
title: Basics of interactions, common practice | |||||
uid: FAQ.Interactions.General | |||||
title: Interactions | |||||
--- | --- | ||||
# Interactions basics, where to get started | |||||
# Interaction basics | |||||
This section answers basic questions and common mistakes in handling application commands, and responding to them. | |||||
This chapter mostly refers to interactions in general, | |||||
and will include questions that are common among users of the Interaction Framework | |||||
as well as users that register and handle commands manually. | |||||
## What's the difference between RespondAsync, DeferAsync and FollowupAsync? | ## What's the difference between RespondAsync, DeferAsync and FollowupAsync? | ||||
@@ -24,33 +26,20 @@ DeferAsync will not send out a response, RespondAsync will. | |||||
## Im getting System.TimeoutException: 'Cannot respond to an interaction after 3 seconds!' | ## Im getting System.TimeoutException: 'Cannot respond to an interaction after 3 seconds!' | ||||
This happens because your computers clock is out of sync or your trying to respond after 3 seconds. If your clock is out of sync and you cant fix it, you can set the `UseInteractionSnowflakeDate` to false in the config. | |||||
This happens because your computer's clock is out of sync or you're trying to respond after 3 seconds. | |||||
If your clock is out of sync and you can't fix it, you can set the `UseInteractionSnowflakeDate` to false in the [DiscordSocketConfig]. | |||||
## Bad form Exception when I try to create my commands, why do I get this? | |||||
[!code-csharp[Interaction Sync](samples/interactionsyncing.cs)] | |||||
Bad form exceptions are thrown if the slash, user or message command builder has invalid values. | |||||
The following options could resolve your error. | |||||
[DiscordClientConfig]: xref:Discord.WebSocket.DiscordSocketConfig | |||||
#### Is your command name lowercase? | |||||
## How do I use this * interaction specific method/property? | |||||
If your command name is not lowercase, it is not seen as a valid command entry. | |||||
`Avatar` is invalid; `avatar` is valid. | |||||
#### Are your values below or above the required amount? (This also applies to message components) | |||||
Discord expects all values to be below maximum allowed. | |||||
Going over this maximum amount of characters causes an exception. | |||||
If your interaction context holds a down-casted version of the interaction object, you need to up-cast it. | |||||
Ideally, use pattern matching to make sure its the type of interaction you are expecting it to be. | |||||
> [!NOTE] | > [!NOTE] | ||||
> All maximum and minimum value requirements can be found in the [Discord Developer Docs]. | |||||
> For components, structure documentation is found [here]. | |||||
[Discord Developer Docs]: https://discord.com/developers/docs/interactions/application-commands#application-commands | |||||
[here]: https://discord.com/developers/docs/interactions/message-components#message-components | |||||
#### Is your subcommand branching correct? | |||||
Branching structure is covered properly here: xref:Guides.SlashCommands.SubCommand | |||||
> Further documentation on pattern matching can be found [here](xref:Guides.Entities.Casting). | |||||
## My interaction commands are not showing up? | ## My interaction commands are not showing up? | ||||
@@ -65,16 +54,6 @@ Did you register a guild command (should be instant), or waited more than an hou | |||||
- Do you have the application commands scope checked when adding your bot to guilds? | - Do you have the application commands scope checked when adding your bot to guilds? | ||||
 | |||||
## There are many options for creating commands, which do I use? | |||||
[!code-csharp[Register examples](samples/registerint.cs)] | |||||
> [!NOTE] | |||||
> You can use bulkoverwrite even if there are no commands in guild, nor globally. | |||||
> The bulkoverwrite method disposes the old set of commands and replaces it with the new. | |||||
## Do I need to create commands on startup? | ## Do I need to create commands on startup? | ||||
If you are registering your commands for the first time, it is required to create them once. | If you are registering your commands for the first time, it is required to create them once. |
@@ -0,0 +1,45 @@ | |||||
--- | |||||
uid: FAQ.Interactions.Manual | |||||
title: Manual handling | |||||
--- | |||||
# Manually handing interactions. | |||||
This section talks about the manual building and responding to interactions. | |||||
If you are using the interaction framework (highly recommended) this section does not apply to you. | |||||
## Bad form Exception when I try to create my commands, why do I get this? | |||||
Bad form exceptions are thrown if the slash, user or message command builder has invalid values. | |||||
The following options could resolve your error. | |||||
#### Is your command name lowercase? | |||||
If your command name is not lowercase, it is not seen as a valid command entry. | |||||
`Avatar` is invalid; `avatar` is valid. | |||||
#### Are your values below or above the required amount? (This also applies to message components) | |||||
Discord expects all values to be below maximum allowed. | |||||
Going over this maximum amount of characters causes an exception. | |||||
> [!NOTE] | |||||
> All maximum and minimum value requirements can be found in the [Discord Developer Docs]. | |||||
> For components, structure documentation is found [here]. | |||||
[Discord Developer Docs]: https://discord.com/developers/docs/interactions/application-commands#application-commands | |||||
[here]: https://discord.com/developers/docs/interactions/message-components#message-components | |||||
#### Is your subcommand branching correct? | |||||
Branching structure is covered properly here: xref:Guides.SlashCommands.SubCommand | |||||
 | |||||
## There are many options for creating commands, which do I use? | |||||
[!code-csharp[Register examples](samples/registerint.cs)] | |||||
> [!NOTE] | |||||
> You can use bulkoverwrite even if there are no commands in guild, nor globally. | |||||
> The bulkoverwrite method disposes the old set of commands and replaces it with the new. |
@@ -0,0 +1,6 @@ | |||||
DiscordSocketConfig config = new() | |||||
{ | |||||
UseInteractionSnowflakeDate = false | |||||
}; | |||||
DiscordSocketclient client = new(config); |
@@ -0,0 +1,8 @@ | |||||
public class MyModule | |||||
{ | |||||
// Intended. | |||||
public InteractionService Service { get; set; } | |||||
// Will not work. A private setter cannot be accessed by the serviceprovider. | |||||
private InteractionService Service { get; private set; } | |||||
} |
@@ -8,15 +8,32 @@ title: Questions about Legacy Versions | |||||
This section refers to legacy library-related questions that do not | This section refers to legacy library-related questions that do not | ||||
apply to the latest or recent version of the Discord.Net library. | apply to the latest or recent version of the Discord.Net library. | ||||
## Migrating your commands to application commands. | |||||
The new interaction service was designed to act like the previous service for text-based commands. | |||||
Your pre-existing code will continue to work, but you will need to migrate your modules and response functions to use the new | |||||
interaction service methods. Documentation on this can be found in the [Guides](xref:Guides.IntFw.Intro). | |||||
## Gateway event parameters changed, why? | |||||
With 3.0, a higher focus on [Cacheable]'s was introduced. | |||||
[Cacheable]'s get an entity from cache, rather than making an API call to retrieve it's data. | |||||
The entity can be retrieved from cache by calling `GetOrDownloadAsync()` on the [Cacheable] type. | |||||
> [!NOTE] | |||||
> GetOrDownloadAsync will download the entity if its not available directly from the cache. | |||||
[Cacheable]: xref:Discord.Cacheable | |||||
## X, Y, Z does not work! It doesn't return a valid value anymore. | ## X, Y, Z does not work! It doesn't return a valid value anymore. | ||||
If you are currently using an older version of the stable branch, | If you are currently using an older version of the stable branch, | ||||
please upgrade to the latest pre-release version to ensure maximum | |||||
please upgrade to the latest release version to ensure maximum | |||||
compatibility. Several features may be broken in older | compatibility. Several features may be broken in older | ||||
versions and will likely not be fixed in the version branch due to | versions and will likely not be fixed in the version branch due to | ||||
their breaking nature. | their breaking nature. | ||||
Visit the repo's [release tag] to see the latest public pre-release. | |||||
Visit the repo's [release tag] to see the latest public release. | |||||
[release tag]: https://github.com/discord-net/Discord.Net/releases | [release tag]: https://github.com/discord-net/Discord.Net/releases | ||||
@@ -1,6 +1,6 @@ | |||||
--- | --- | ||||
uid: FAQ.Commands.General | |||||
title: General Questions about chat Commands | |||||
uid: FAQ.TextCommands.General | |||||
title: General Questions about Text Commands | |||||
--- | --- | ||||
# Chat Command-related Questions | # Chat Command-related Questions | ||||
@@ -10,21 +10,16 @@ answered regarding general command usage when using @Discord.Commands. | |||||
## How can I restrict some of my commands so only specific users can execute them? | ## How can I restrict some of my commands so only specific users can execute them? | ||||
Based on how you want to implement the restrictions, you can use the | |||||
built-in [RequireUserPermission] precondition, which allows you to | |||||
You can use the built-in `RequireUserPermission` precondition, which allows you to | |||||
restrict the command based on the user's current permissions in the | restrict the command based on the user's current permissions in the | ||||
guild or channel (*e.g., `GuildPermission.Administrator`, | guild or channel (*e.g., `GuildPermission.Administrator`, | ||||
`ChannelPermission.ManageMessages`*). | `ChannelPermission.ManageMessages`*). | ||||
If, however, you wish to restrict the commands based on the user's | |||||
role, you can either create your custom precondition or use | |||||
Joe4evr's [Preconditions Addons] that provides a few custom | |||||
preconditions that aren't provided in the stock library. | |||||
Its source can also be used as an example for creating your | |||||
custom preconditions. | |||||
> [!NOTE] | |||||
> There are many more preconditions to use, including being able to make some yourself. | |||||
> Precondition documentation is covered [here](xref:Guides.TextCommands.Preconditions) | |||||
[RequireUserPermission]: xref:Discord.Commands.RequireUserPermissionAttribute | [RequireUserPermission]: xref:Discord.Commands.RequireUserPermissionAttribute | ||||
[Preconditions Addons]: https://github.com/Joe4evr/Discord.Addons/tree/master/src/Discord.Addons.Preconditions | |||||
## Why am I getting an error about `Assembly.GetEntryAssembly`? | ## Why am I getting an error about `Assembly.GetEntryAssembly`? | ||||
@@ -6,15 +6,19 @@ | |||||
topicUid: FAQ.Basics.BasicOp | topicUid: FAQ.Basics.BasicOp | ||||
- name: Client Basics | - name: Client Basics | ||||
topicUid: FAQ.Basics.ClientBasics | topicUid: FAQ.Basics.ClientBasics | ||||
- name: Interactions | |||||
topicUid: FAQ.Basics.InteractionBasics | |||||
- name: Commands | |||||
items: | |||||
- name: String commands | |||||
topicUid: FAQ.Commands.General | |||||
- name: Interaction commands | |||||
topicUid: FAQ.Commands.Interactions | |||||
- name: Dependency Injection | - name: Dependency Injection | ||||
topicUid: FAQ.Commands.DI | |||||
topicUid: FAQ.Basics.DI | |||||
- name: Interactions | |||||
items: | |||||
- name: Starting out | |||||
topicUid: FAQ.Interactions.General | |||||
- name: Interaction Service/Framework | |||||
topicUid: FAQ.Interactions.Framework | |||||
- name: Manual handling | |||||
topicUid: FAQ.Interactions.Manual | |||||
- name: Text Commands | |||||
items: | |||||
- name: Text Command basics | |||||
topicUid: FAQ.TextCommands.General | |||||
- name: Legacy or Upgrade | - name: Legacy or Upgrade | ||||
topicUid: FAQ.Legacy | topicUid: FAQ.Legacy |
@@ -27,7 +27,7 @@ private async Task Client_Ready() | |||||
.AddChoice("Lovely", 4) | .AddChoice("Lovely", 4) | ||||
.AddChoice("Excellent!", 5) | .AddChoice("Excellent!", 5) | ||||
.WithType(ApplicationCommandOptionType.Integer) | .WithType(ApplicationCommandOptionType.Integer) | ||||
).Build(); | |||||
); | |||||
try | try | ||||
{ | { | ||||
@@ -70,14 +70,14 @@ public async Task Client_Ready() | |||||
// Let's do our global command | // Let's do our global command | ||||
var globalCommand = new SlashCommandBuilder(); | var globalCommand = new SlashCommandBuilder(); | ||||
globalCommand.WithName("first-global-command"); | globalCommand.WithName("first-global-command"); | ||||
globalCommand.WithDescription("This is my frist global slash command"); | |||||
globalCommand.WithDescription("This is my first global slash command"); | |||||
try | try | ||||
{ | { | ||||
// Now that we have our builder, we can call the CreateApplicationCommandAsync method to make our slash command. | // Now that we have our builder, we can call the CreateApplicationCommandAsync method to make our slash command. | ||||
await guild.CreateApplicationCommandAsync(guildCommand.Build()); | await guild.CreateApplicationCommandAsync(guildCommand.Build()); | ||||
// With global commands we dont need the guild. | |||||
// With global commands we don't need the guild. | |||||
await client.CreateGlobalApplicationCommandAsync(globalCommand.Build()); | await client.CreateGlobalApplicationCommandAsync(globalCommand.Build()); | ||||
// Using the ready event is a simple implementation for the sake of the example. Suitable for testing and development. | // Using the ready event is a simple implementation for the sake of the example. Suitable for testing and development. | ||||
// For a production bot, it is recommended to only run the CreateGlobalApplicationCommandAsync() once for each command. | // For a production bot, it is recommended to only run the CreateGlobalApplicationCommandAsync() once for each command. | ||||
@@ -43,7 +43,7 @@ var components = new ComponentBuilder() | |||||
.WithSelectMenu(menu); | .WithSelectMenu(menu); | ||||
await arg.RespondAsync("On a scale of one to five, how gaming is this?", component: componBuild(), ephemeral: true); | |||||
await arg.RespondAsync("On a scale of one to five, how gaming is this?", component: components.Build(), ephemeral: true); | |||||
break; | break; | ||||
``` | ``` | ||||
@@ -35,11 +35,11 @@ and min/max length of the input: | |||||
var tb = new TextInputBuilder() | var tb = new TextInputBuilder() | ||||
.WithLabel("Labeled") | .WithLabel("Labeled") | ||||
.WithCustomId("text_input") | .WithCustomId("text_input") | ||||
.WithStyle(TextInputStyle.Paragraph) | |||||
.WithMinLength(6); | |||||
.WithMaxLength(42) | |||||
.WithRequired(true) | |||||
.WithPlaceholder("Consider this place held."); | |||||
.WithStyle(TextInputStyle.Paragraph) | |||||
.WithMinLength(6) | |||||
.WithMaxLength(42) | |||||
.WithRequired(true) | |||||
.WithPlaceholder("Consider this place held."); | |||||
``` | ``` | ||||
 |  | ||||
@@ -143,6 +143,21 @@ In this case, user can only input Stage Channels and Text Channels to this param | |||||
You can specify the permitted max/min value for a number type parameter using the [MaxValueAttribute] and [MinValueAttribute]. | You can specify the permitted max/min value for a number type parameter using the [MaxValueAttribute] and [MinValueAttribute]. | ||||
#### Complex Parameters | |||||
This allows users to create slash command options using an object's constructor allowing complex objects to be created which cannot be infered from only one input value. | |||||
Constructor methods support every attribute type that can be used with the regular slash commands ([Autocomplete], [Summary] etc. ). | |||||
Preferred constructor of a Type can be specified either by passing a `Type[]` to the `[ComplexParameterAttribute]` or tagging a type constructor with the `[ComplexParameterCtorAttribute]`. If nothing is specified, the InteractionService defaults to the only public constructor of the type. | |||||
TypeConverter pattern is used to parse the constructor methods objects. | |||||
[!code-csharp[Complex Parameter](samples/intro/complexparams.cs)] | |||||
Interaction service complex parameter constructors are prioritized in the following order: | |||||
1. Constructor matching the signature provided in the `[ComplexParameter(Type[])]` overload. | |||||
2. Constuctor tagged with `[ComplexParameterCtor]`. | |||||
3. Type's only public constuctor. | |||||
## User Commands | ## User Commands | ||||
A valid User Command must have the following structure: | A valid User Command must have the following structure: | ||||
@@ -0,0 +1,37 @@ | |||||
public class Vector3 | |||||
{ | |||||
public int X {get;} | |||||
public int Y {get;} | |||||
public int Z {get;} | |||||
public Vector3() | |||||
{ | |||||
X = 0; | |||||
Y = 0; | |||||
Z = 0; | |||||
} | |||||
[ComplexParameterCtor] | |||||
public Vector3(int x, int y, int z) | |||||
{ | |||||
X = x; | |||||
Y = y; | |||||
Z = z; | |||||
} | |||||
} | |||||
// Both of the commands below are displayed to the users identically. | |||||
// With complex parameter | |||||
[SlashCommand("create-vector", "Create a 3D vector.")] | |||||
public async Task CreateVector([ComplexParameter]Vector3 vector3) | |||||
{ | |||||
... | |||||
} | |||||
// Without complex parameter | |||||
[SlashCommand("create-vector", "Create a 3D vector.")] | |||||
public async Task CreateVector(int x, int y, int z) | |||||
{ | |||||
... | |||||
} |
@@ -1,7 +1,7 @@ | |||||
discordClient.ButtonExecuted += async (interaction) => | discordClient.ButtonExecuted += async (interaction) => | ||||
{ | { | ||||
var ctx = new SocketInteractionContext<SocketMessageComponent>(discordClient, interaction); | var ctx = new SocketInteractionContext<SocketMessageComponent>(discordClient, interaction); | ||||
await _interactionService.ExecuteAsync(ctx, serviceProvider); | |||||
await _interactionService.ExecuteCommandAsync(ctx, serviceProvider); | |||||
}; | }; | ||||
public class MessageComponentModule : InteractionModuleBase<SocketInteractionContext<SocketMessageComponent>> | public class MessageComponentModule : InteractionModuleBase<SocketInteractionContext<SocketMessageComponent>> | ||||
@@ -1,16 +1,18 @@ | |||||
[SlashCommand("blep", "Send a random adorable animal photo")] | [SlashCommand("blep", "Send a random adorable animal photo")] | ||||
public async Task Blep([Choice("Dog", "dog"), Choice("Cat", "cat"), Choice("Penguin", "penguin")] string animal) | |||||
public async Task Blep([Choice("Dog", "dog"), Choice("Cat", "cat"), Choice("Guinea pig", "GuineaPig")] string animal) | |||||
{ | { | ||||
... | ... | ||||
} | } | ||||
// In most cases, you can use an enum to replace the seperate choice attributes in a command. | |||||
// In most cases, you can use an enum to replace the separate choice attributes in a command. | |||||
public enum Animal | public enum Animal | ||||
{ | { | ||||
Cat, | Cat, | ||||
Dog, | Dog, | ||||
Penguin | |||||
// You can also use the ChoiceDisplay attribute to change how they appear in the choice menu. | |||||
[ChoiceDisplay("Guinea pig")] | |||||
GuineaPig | |||||
} | } | ||||
[SlashCommand("blep", "Send a random adorable animal photo")] | [SlashCommand("blep", "Send a random adorable animal photo")] | ||||
@@ -0,0 +1,61 @@ | |||||
--- | |||||
uid: Guides.OtherLibs.EFCore | |||||
title: EFCore | |||||
--- | |||||
# Entity Framework Core | |||||
In this guide we will set up EFCore with a PostgreSQL database. Information on other databases will be at the bottom of this page. | |||||
## Prerequisites | |||||
- A simple bot with dependency injection configured | |||||
- A running PostgreSQL instance | |||||
- [EFCore CLI tools](https://docs.microsoft.com/en-us/ef/core/cli/dotnet#installing-the-tools) | |||||
## Downloading the required packages | |||||
You can install the following packages through your IDE or go to the nuget link to grab the dotnet cli command. | |||||
|Name|Link| | |||||
|--|--| | |||||
| `Microsoft.EntityFrameworkCore` | [link](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) | | |||||
| `Npgsql.EntityFrameworkCore.PostgreSQL` | [link](https://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL)| | |||||
## Configuring the DbContext | |||||
To use EFCore, you need a DbContext to access everything in your database. The DbContext will look like this. Here is an example entity to show you how you can add more entities yourself later on. | |||||
[!code-csharp[DBContext Sample](samples/DbContextSample.cs)] | |||||
> [!NOTE] | |||||
> To learn more about creating the EFCore model, visit the following [link](https://docs.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli#create-the-model) | |||||
## Adding the DbContext to your Dependency Injection container | |||||
To add your newly created DbContext to your Dependency Injection container, simply use the extension method provided by EFCore to add the context to your container. It should look something like this | |||||
[!code-csharp[DBContext Dependency Injection](samples/DbContextDepInjection.cs)] | |||||
> [!NOTE] | |||||
> You can find out how to get your connection string [here](https://www.connectionstrings.com/npgsql/standard/) | |||||
## Migrations | |||||
Before you can start using your DbContext, you have to migrate the changes you've made in your code to your actual database. | |||||
To learn more about migrations, visit the official Microsoft documentation [here](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli) | |||||
## Using the DbContext | |||||
You can now use the DbContext wherever you can inject it. Here's an example on injecting it into an interaction command module. | |||||
[!code-csharp[DBContext injected into interaction module](samples/InteractionModuleDISample.cs)] | |||||
## Using a different database provider | |||||
Here's a couple of popular database providers for EFCore and links to tutorials on how to set them up. The only thing that usually changes is the provider inside of your `DbContextOptions` | |||||
| Provider | Link | | |||||
|--|--| | |||||
| MySQL | [link](https://dev.mysql.com/doc/connector-net/en/connector-net-entityframework-core-example.html) | | |||||
| SQLite | [link](https://docs.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli) | |
@@ -0,0 +1,36 @@ | |||||
using Discord; | |||||
using Serilog; | |||||
using Serilog.Events; | |||||
public class Program | |||||
{ | |||||
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); | |||||
public async Task MainAsync() | |||||
{ | |||||
Log.Logger = new LoggerConfiguration() | |||||
.MinimumLevel.Verbose() | |||||
.Enrich.FromLogContext() | |||||
.WriteTo.Console() | |||||
.CreateLogger(); | |||||
_client = new DiscordSocketClient(); | |||||
_client.Log += LogAsync; | |||||
// You can assign your bot token to a string, and pass that in to connect. | |||||
// This is, however, insecure, particularly if you plan to have your code hosted in a public repository. | |||||
var token = "token"; | |||||
// Some alternative options would be to keep your token in an Environment Variable or a standalone file. | |||||
// var token = Environment.GetEnvironmentVariable("NameOfYourEnvironmentVariable"); | |||||
// var token = File.ReadAllText("token.txt"); | |||||
// var token = JsonConvert.DeserializeObject<AConfigurationClass>(File.ReadAllText("config.json")).Token; | |||||
await _client.LoginAsync(TokenType.Bot, token); | |||||
await _client.StartAsync(); | |||||
// Block this task until the program is closed. | |||||
await Task.Delay(Timeout.Infinite); | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
private static ServiceProvider ConfigureServices() | |||||
{ | |||||
return new ServiceCollection() | |||||
.AddDbContext<ApplicationDbContext>( | |||||
options => options.UseNpgsql("Your connection string") | |||||
) | |||||
[...] | |||||
.BuildServiceProvider(); | |||||
} |
@@ -0,0 +1,19 @@ | |||||
// ApplicationDbContext.cs | |||||
using Microsoft.EntityFrameworkCore; | |||||
public class ApplicationDbContext : DbContext | |||||
{ | |||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) | |||||
{ | |||||
} | |||||
public DbSet<UserEntity> Users { get; set; } = null!; | |||||
} | |||||
// UserEntity.cs | |||||
public class UserEntity | |||||
{ | |||||
public ulong Id { get; set; } | |||||
public string Name { get; set; } | |||||
} |
@@ -0,0 +1,20 @@ | |||||
using Discord; | |||||
public class SampleModule : InteractionModuleBase<SocketInteractionContext> | |||||
{ | |||||
private readonly ApplicationDbContext _db; | |||||
public SampleModule(ApplicationDbContext db) | |||||
{ | |||||
_db = db; | |||||
} | |||||
[SlashCommand("sample", "sample")] | |||||
public async Task Sample() | |||||
{ | |||||
// Do stuff with your injected DbContext | |||||
var user = _db.Users.FirstOrDefault(x => x.Id == Context.User.Id); | |||||
... | |||||
} | |||||
} |
@@ -0,0 +1 @@ | |||||
Log.Debug("Your log message, with {Variables}!", 10); // This will output "[21:51:00 DBG] Your log message, with 10!" |
@@ -0,0 +1,15 @@ | |||||
private static async Task LogAsync(LogMessage message) | |||||
{ | |||||
var severity = message.Severity switch | |||||
{ | |||||
LogSeverity.Critical => LogEventLevel.Fatal, | |||||
LogSeverity.Error => LogEventLevel.Error, | |||||
LogSeverity.Warning => LogEventLevel.Warning, | |||||
LogSeverity.Info => LogEventLevel.Information, | |||||
LogSeverity.Verbose => LogEventLevel.Verbose, | |||||
LogSeverity.Debug => LogEventLevel.Debug, | |||||
_ => LogEventLevel.Information | |||||
}; | |||||
Log.Write(severity, message.Exception, "[{Source}] {Message}", message.Source, message.Message); | |||||
await Task.CompletedTask; | |||||
} |
@@ -0,0 +1,45 @@ | |||||
--- | |||||
uid: Guides.OtherLibs.Serilog | |||||
title: Serilog | |||||
--- | |||||
# Configuring serilog | |||||
## Prerequisites | |||||
- A basic working bot with a logging method as described in [Creating your first bot](xref:Guides.GettingStarted.FirstBot) | |||||
## Installing the Serilog package | |||||
You can install the following packages through your IDE or go to the nuget link to grab the dotnet cli command. | |||||
|Name|Link| | |||||
|--|--| | |||||
|`Serilog.Extensions.Logging`| [link](https://www.nuget.org/packages/Serilog.Extensions.Logging)| | |||||
|`Serilog.Sinks.Console`| [link](https://www.nuget.org/packages/Serilog.Sinks.Console)| | |||||
## Configuring Serilog | |||||
Serilog will be configured at the top of your async Main method, it looks like this | |||||
[!code-csharp[Configuring serilog](samples/ConfiguringSerilog.cs)] | |||||
## Modifying your logging method | |||||
For Serilog to log Discord events correctly, we have to map the Discord `LogSeverity` to the Serilog `LogEventLevel`. You can modify your log method to look like this. | |||||
[!code-csharp[Modifying your log method](samples/ModifyLogMethod.cs)] | |||||
## Testing | |||||
If you run your application now, you should see something similar to this | |||||
 | |||||
## Using your new logger in other places | |||||
Now that you have set up Serilog, you can use it everywhere in your application by simply calling | |||||
[!code-csharp[Log debug sample](samples/LogDebugSample.cs)] | |||||
> [!NOTE] | |||||
> Depending on your configured log level, the log messages may or may not show up in your console. Refer to [Serilog's github page](https://github.com/serilog/serilog/wiki/Configuration-Basics#minimum-level) for more information about log levels. |
@@ -95,7 +95,7 @@ | |||||
topicUid: Guides.MessageComponents.TextInputs | topicUid: Guides.MessageComponents.TextInputs | ||||
- name: Advanced Concepts | - name: Advanced Concepts | ||||
topicUid: Guides.MessageComponents.Advanced | topicUid: Guides.MessageComponents.Advanced | ||||
- name: Modal Basics | |||||
- name: Modal Basics | |||||
items: | items: | ||||
- name: Introduction | - name: Introduction | ||||
topicUid: Guides.Modals.Intro | topicUid: Guides.Modals.Intro | ||||
@@ -109,6 +109,12 @@ | |||||
topicUid: Guides.GuildEvents.GettingUsers | topicUid: Guides.GuildEvents.GettingUsers | ||||
- name: Modifying Events | - name: Modifying Events | ||||
topicUid: Guides.GuildEvents.Modifying | topicUid: Guides.GuildEvents.Modifying | ||||
- name: Working with other libraries | |||||
items: | |||||
- name: Serilog | |||||
topicUid: Guides.OtherLibs.Serilog | |||||
- name: EFCore | |||||
topicUid: Guides.OtherLibs.EFCore | |||||
- name: Emoji | - name: Emoji | ||||
topicUid: Guides.Emoji | topicUid: Guides.Emoji | ||||
- name: Voice | - name: Voice | ||||
@@ -1,3 +1,11 @@ | |||||
using Discord.Interactions; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.ComponentModel.DataAnnotations; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace InteractionFramework | namespace InteractionFramework | ||||
{ | { | ||||
public enum ExampleEnum | public enum ExampleEnum | ||||
@@ -5,6 +13,8 @@ namespace InteractionFramework | |||||
First, | First, | ||||
Second, | Second, | ||||
Third, | Third, | ||||
Fourth | |||||
Fourth, | |||||
[ChoiceDisplay("Twenty First")] | |||||
TwentyFirst | |||||
} | } | ||||
} | } |
@@ -557,6 +557,7 @@ namespace Discord.Commands | |||||
if (matchResult.Pipeline is PreconditionResult preconditionResult) | if (matchResult.Pipeline is PreconditionResult preconditionResult) | ||||
{ | { | ||||
await _commandExecutedEvent.InvokeAsync(matchResult.Match.Value.Command, context, preconditionResult).ConfigureAwait(false); | await _commandExecutedEvent.InvokeAsync(matchResult.Match.Value.Command, context, preconditionResult).ConfigureAwait(false); | ||||
return preconditionResult; | |||||
} | } | ||||
return matchResult; | return matchResult; | ||||
@@ -208,6 +208,18 @@ namespace Discord | |||||
public static string GetStickerUrl(ulong stickerId, StickerFormatType format = StickerFormatType.Png) | public static string GetStickerUrl(ulong stickerId, StickerFormatType format = StickerFormatType.Png) | ||||
=> $"{DiscordConfig.CDNUrl}stickers/{stickerId}.{FormatToExtension(format)}"; | => $"{DiscordConfig.CDNUrl}stickers/{stickerId}.{FormatToExtension(format)}"; | ||||
/// <summary> | |||||
/// Returns an events cover image url. | |||||
/// </summary> | |||||
/// <param name="guildId">The guild id that the event is in.</param> | |||||
/// <param name="eventId">The id of the event.</param> | |||||
/// <param name="assetId">The id of the cover image asset.</param> | |||||
/// <param name="format">The format of the image.</param> | |||||
/// <param name="size">The size of the image.</param> | |||||
/// <returns></returns> | |||||
public static string GetEventCoverImageUrl(ulong guildId, ulong eventId, string assetId, ImageFormat format = ImageFormat.Auto, ushort size = 1024) | |||||
=> $"{DiscordConfig.CDNUrl}guild-events/{guildId}/{eventId}/{assetId}.{FormatToExtension(format, assetId)}?size={size}"; | |||||
private static string FormatToExtension(StickerFormatType format) | private static string FormatToExtension(StickerFormatType format) | ||||
{ | { | ||||
return format switch | return format switch | ||||
@@ -96,9 +96,11 @@ namespace Discord | |||||
#endregion | #endregion | ||||
#region General Request Errors (40XXX) | #region General Request Errors (40XXX) | ||||
MaximumNumberOfEditsReached = 30046, | |||||
TokenUnauthorized = 40001, | TokenUnauthorized = 40001, | ||||
InvalidVerification = 40002, | InvalidVerification = 40002, | ||||
OpeningDMTooFast = 40003, | OpeningDMTooFast = 40003, | ||||
SendMessagesHasBeenTemporarilyDisabled = 40004, | |||||
RequestEntityTooLarge = 40005, | RequestEntityTooLarge = 40005, | ||||
FeatureDisabled = 40006, | FeatureDisabled = 40006, | ||||
UserBanned = 40007, | UserBanned = 40007, | ||||
@@ -108,6 +110,7 @@ namespace Discord | |||||
#endregion | #endregion | ||||
#region Action Preconditions/Checks (50XXX) | #region Action Preconditions/Checks (50XXX) | ||||
InteractionHasAlreadyBeenAcknowledged = 40060, | |||||
MissingPermissions = 50001, | MissingPermissions = 50001, | ||||
InvalidAccountType = 50002, | InvalidAccountType = 50002, | ||||
CannotExecuteForDM = 50003, | CannotExecuteForDM = 50003, | ||||
@@ -141,12 +144,14 @@ namespace Discord | |||||
InvalidFileUpload = 50046, | InvalidFileUpload = 50046, | ||||
CannotSelfRedeemGift = 50054, | CannotSelfRedeemGift = 50054, | ||||
InvalidGuild = 50055, | InvalidGuild = 50055, | ||||
InvalidMessageType = 50068, | |||||
PaymentSourceRequiredForGift = 50070, | PaymentSourceRequiredForGift = 50070, | ||||
CannotDeleteRequiredCommunityChannel = 50074, | CannotDeleteRequiredCommunityChannel = 50074, | ||||
InvalidSticker = 50081, | InvalidSticker = 50081, | ||||
CannotExecuteOnArchivedThread = 50083, | CannotExecuteOnArchivedThread = 50083, | ||||
InvalidThreadNotificationSettings = 50084, | InvalidThreadNotificationSettings = 50084, | ||||
BeforeValueEarlierThanThreadCreation = 50085, | BeforeValueEarlierThanThreadCreation = 50085, | ||||
CommunityServerChannelsMustBeTextChannels = 50086, | |||||
ServerLocaleUnavailable = 50095, | ServerLocaleUnavailable = 50095, | ||||
ServerRequiresMonetization = 50097, | ServerRequiresMonetization = 50097, | ||||
ServerRequiresBoosts = 50101, | ServerRequiresBoosts = 50101, | ||||
@@ -21,7 +21,7 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// <note type="important"> | /// <note type="important"> | ||||
/// The returned collection is an asynchronous enumerable object; one must call | |||||
/// The returned collection is an asynchronous enumerable object; one must call | |||||
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a | /// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a | ||||
/// collection. | /// collection. | ||||
/// </note> | /// </note> | ||||
@@ -31,11 +31,12 @@ namespace Discord | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | /// <param name="components">The message components to be included with this message. Used for interactions.</param> | ||||
/// <param name="stickers">A collection of stickers to send with the message.</param> | /// <param name="stickers">A collection of stickers to send with the message.</param> | ||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | ||||
/// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
/// <returns> | /// <returns> | ||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <summary> | /// <summary> | ||||
/// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
/// </summary> | /// </summary> | ||||
@@ -71,11 +72,12 @@ namespace Discord | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | /// <param name="components">The message components to be included with this message. Used for interactions.</param> | ||||
/// <param name="stickers">A collection of stickers to send with the file.</param> | /// <param name="stickers">A collection of stickers to send with the file.</param> | ||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | ||||
/// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
/// <returns> | /// <returns> | ||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <summary> | /// <summary> | ||||
/// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
/// </summary> | /// </summary> | ||||
@@ -108,11 +110,12 @@ namespace Discord | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | /// <param name="components">The message components to be included with this message. Used for interactions.</param> | ||||
/// <param name="stickers">A collection of stickers to send with the file.</param> | /// <param name="stickers">A collection of stickers to send with the file.</param> | ||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | ||||
/// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
/// <returns> | /// <returns> | ||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <summary> | /// <summary> | ||||
/// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
/// </summary> | /// </summary> | ||||
@@ -137,11 +140,12 @@ namespace Discord | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | /// <param name="components">The message components to be included with this message. Used for interactions.</param> | ||||
/// <param name="stickers">A collection of stickers to send with the file.</param> | /// <param name="stickers">A collection of stickers to send with the file.</param> | ||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | ||||
/// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
/// <returns> | /// <returns> | ||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
Task<IUserMessage> SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
Task<IUserMessage> SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <summary> | /// <summary> | ||||
/// Sends a collection of files to this message channel. | /// Sends a collection of files to this message channel. | ||||
/// </summary> | /// </summary> | ||||
@@ -166,11 +170,12 @@ namespace Discord | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | /// <param name="components">The message components to be included with this message. Used for interactions.</param> | ||||
/// <param name="stickers">A collection of stickers to send with the file.</param> | /// <param name="stickers">A collection of stickers to send with the file.</param> | ||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | ||||
/// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
/// <returns> | /// <returns> | ||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
Task<IUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
Task<IUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a message from this message channel. | /// Gets a message from this message channel. | ||||
@@ -48,6 +48,23 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
int MessageCount { get; } | int MessageCount { get; } | ||||
/// <summary> | |||||
/// Gets whether non-moderators can add other non-moderators to a thread. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This property is only available on private threads. | |||||
/// </remarks> | |||||
bool? IsInvitable { get; } | |||||
/// <summary> | |||||
/// Gets when the thread was created. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This property is only populated for threads created after 2022-01-09, hence the default date of this | |||||
/// property will be that date. | |||||
/// </remarks> | |||||
new DateTimeOffset CreatedAt { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Joins the current thread. | /// Joins the current thread. | ||||
/// </summary> | /// </summary> | ||||
@@ -54,5 +54,10 @@ namespace Discord | |||||
/// Gets or sets the status of the event. | /// Gets or sets the status of the event. | ||||
/// </summary> | /// </summary> | ||||
public Optional<GuildScheduledEventStatus> Status { get; set; } | public Optional<GuildScheduledEventStatus> Status { get; set; } | ||||
/// <summary> | |||||
/// Gets or sets the banner image of the event. | |||||
/// </summary> | |||||
public Optional<Image?> CoverImage { get; set; } | |||||
} | } | ||||
} | } |
@@ -1105,6 +1105,7 @@ namespace Discord | |||||
/// </param> | /// </param> | ||||
/// <param name="speakers">A collection of speakers for the event.</param> | /// <param name="speakers">A collection of speakers for the event.</param> | ||||
/// <param name="location">The location of the event; links are supported</param> | /// <param name="location">The location of the event; links are supported</param> | ||||
/// <param name="coverImage">The optional banner image for the event.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
/// <returns> | /// <returns> | ||||
/// A task that represents the asynchronous create operation. | /// A task that represents the asynchronous create operation. | ||||
@@ -1118,6 +1119,7 @@ namespace Discord | |||||
DateTimeOffset? endTime = null, | DateTimeOffset? endTime = null, | ||||
ulong? channelId = null, | ulong? channelId = null, | ||||
string location = null, | string location = null, | ||||
Image? coverImage = null, | |||||
RequestOptions options = null); | RequestOptions options = null); | ||||
/// <summary> | /// <summary> | ||||
@@ -39,6 +39,11 @@ namespace Discord | |||||
/// </remarks> | /// </remarks> | ||||
string Description { get; } | string Description { get; } | ||||
/// <summary> | |||||
/// Gets the banner asset id of the event. | |||||
/// </summary> | |||||
string CoverImageId { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the start time of the event. | /// Gets the start time of the event. | ||||
/// </summary> | /// </summary> | ||||
@@ -80,6 +85,14 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
int? UserCount { get; } | int? UserCount { get; } | ||||
/// <summary> | |||||
/// Gets this events banner image url. | |||||
/// </summary> | |||||
/// <param name="format">The format to return.</param> | |||||
/// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048. | |||||
/// <returns>The cover images url.</returns> | |||||
string GetCoverImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 1024); | |||||
/// <summary> | /// <summary> | ||||
/// Starts the event. | /// Starts the event. | ||||
/// </summary> | /// </summary> | ||||
@@ -613,6 +613,9 @@ namespace Discord | |||||
if (!(string.IsNullOrEmpty(Url) ^ string.IsNullOrEmpty(CustomId))) | if (!(string.IsNullOrEmpty(Url) ^ string.IsNullOrEmpty(CustomId))) | ||||
throw new InvalidOperationException("A button must contain either a URL or a CustomId, but not both!"); | throw new InvalidOperationException("A button must contain either a URL or a CustomId, but not both!"); | ||||
if (Style == 0) | |||||
throw new ArgumentException("A button must have a style.", nameof(Style)); | |||||
if (Style == ButtonStyle.Link) | if (Style == ButtonStyle.Link) | ||||
{ | { | ||||
if (string.IsNullOrEmpty(Url)) | if (string.IsNullOrEmpty(Url)) | ||||
@@ -15,7 +15,7 @@ namespace Discord | |||||
/// <summary> | /// <summary> | ||||
/// Gets or sets the time for this timestamp tag. | /// Gets or sets the time for this timestamp tag. | ||||
/// </summary> | /// </summary> | ||||
public DateTime Time { get; set; } | |||||
public DateTimeOffset Time { get; set; } | |||||
/// <summary> | /// <summary> | ||||
/// Converts the current timestamp tag to the string representation supported by discord. | /// Converts the current timestamp tag to the string representation supported by discord. | ||||
@@ -26,11 +26,11 @@ namespace Discord | |||||
/// <returns>A string that is compatible in a discord message, ex: <code><t:1625944201:f></code></returns> | /// <returns>A string that is compatible in a discord message, ex: <code><t:1625944201:f></code></returns> | ||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
return $"<t:{((DateTimeOffset)Time).ToUnixTimeSeconds()}:{(char)Style}>"; | |||||
return $"<t:{Time.ToUnixTimeSeconds()}:{(char)Style}>"; | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// Creates a new timestamp tag with the specified datetime object. | |||||
/// Creates a new timestamp tag with the specified <see cref="DateTime"/> object. | |||||
/// </summary> | /// </summary> | ||||
/// <param name="time">The time of this timestamp tag.</param> | /// <param name="time">The time of this timestamp tag.</param> | ||||
/// <param name="style">The style for this timestamp tag.</param> | /// <param name="style">The style for this timestamp tag.</param> | ||||
@@ -43,5 +43,20 @@ namespace Discord | |||||
Time = time | Time = time | ||||
}; | }; | ||||
} | } | ||||
/// <summary> | |||||
/// Creates a new timestamp tag with the specified <see cref="DateTimeOffset"/> object. | |||||
/// </summary> | |||||
/// <param name="time">The time of this timestamp tag.</param> | |||||
/// <param name="style">The style for this timestamp tag.</param> | |||||
/// <returns>The newly create timestamp tag.</returns> | |||||
public static TimestampTag FromDateTimeOffset(DateTimeOffset time, TimestampTagStyles style = TimestampTagStyles.ShortDateTime) | |||||
{ | |||||
return new TimestampTag | |||||
{ | |||||
Style = style, | |||||
Time = time | |||||
}; | |||||
} | |||||
} | } | ||||
} | |||||
} |
@@ -18,6 +18,13 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
DateTimeOffset? JoinedAt { get; } | DateTimeOffset? JoinedAt { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets the displayed name for this user. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// A string representing the display name of the user; If the nickname is null, this will be the username. | |||||
/// </returns> | |||||
string DisplayName { get; } | |||||
/// <summary> | |||||
/// Gets the nickname for this user. | /// Gets the nickname for this user. | ||||
/// </summary> | /// </summary> | ||||
/// <returns> | /// <returns> | ||||
@@ -25,7 +32,15 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
string Nickname { get; } | string Nickname { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets the guild specific avatar for this users. | |||||
/// Gets the displayed avatar for this user. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// The users displayed avatar hash. If the user does not have a guild avatar, this will be the regular avatar. | |||||
/// If the user also does not have a regular avatar, this will be <see langword="null"/>. | |||||
/// </returns> | |||||
string DisplayAvatarId { get; } | |||||
/// <summary> | |||||
/// Gets the guild specific avatar for this user. | |||||
/// </summary> | /// </summary> | ||||
/// <returns> | /// <returns> | ||||
/// The users guild avatar hash if they have one; otherwise <see langword="null"/>. | /// The users guild avatar hash if they have one; otherwise <see langword="null"/>. | ||||
@@ -119,16 +134,29 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// This property retrieves a URL for this guild user's guild specific avatar. In event that the user does not have a valid guild avatar | /// This property retrieves a URL for this guild user's guild specific avatar. In event that the user does not have a valid guild avatar | ||||
/// (i.e. their avatar identifier is not set), this method will return <c>null</c>. | |||||
/// (i.e. their avatar identifier is not set), this method will return <see langword="null"/>. | |||||
/// </remarks> | /// </remarks> | ||||
/// <param name="format">The format to return.</param> | /// <param name="format">The format to return.</param> | ||||
/// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048. | /// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048. | ||||
/// </param> | /// </param> | ||||
/// <returns> | /// <returns> | ||||
/// A string representing the user's avatar URL; <c>null</c> if the user does not have an avatar in place. | |||||
/// A string representing the user's avatar URL; <see langword="null"/> if the user does not have an avatar in place. | |||||
/// </returns> | /// </returns> | ||||
string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); | string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); | ||||
/// <summary> | /// <summary> | ||||
/// Gets the display avatar URL for this user. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This property retrieves an URL for this guild user's displayed avatar. | |||||
/// If the user does not have a guild avatar, this will be the user's regular avatar. | |||||
/// </remarks> | |||||
/// <param name="format">The format to return.</param> | |||||
/// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048. | |||||
/// <returns> | |||||
/// A string representing the URL of the displayed avatar for this user. <see langword="null"/> if the user does not have an avatar in place. | |||||
/// </returns> | |||||
string GetDisplayAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); | |||||
/// <summary> | |||||
/// Kicks this user from this guild. | /// Kicks this user from this guild. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="reason">The reason for the kick which will be recorded in the audit log.</param> | /// <param name="reason">The reason for the kick which will be recorded in the audit log.</param> | ||||
@@ -65,6 +65,13 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
bool IsStreaming { get; } | bool IsStreaming { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets a value that indicates if the user is videoing in a voice channel. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// <c>true</c> if the user has their camera turned on; otherwise <c>false</c>. | |||||
/// </returns> | |||||
bool IsVideoing { get; } | |||||
/// <summary> | |||||
/// Gets the time on which the user requested to speak. | /// Gets the time on which the user requested to speak. | ||||
/// </summary> | /// </summary> | ||||
DateTimeOffset? RequestToSpeakTimestamp { get; } | DateTimeOffset? RequestToSpeakTimestamp { get; } | ||||
@@ -0,0 +1,30 @@ | |||||
using System; | |||||
namespace Discord.Interactions | |||||
{ | |||||
/// <summary> | |||||
/// Registers a parameter as a complex parameter. | |||||
/// </summary> | |||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] | |||||
public class ComplexParameterAttribute : Attribute | |||||
{ | |||||
/// <summary> | |||||
/// Gets the parameter array of the constructor method that should be prioritized. | |||||
/// </summary> | |||||
public Type[] PrioritizedCtorSignature { get; } | |||||
/// <summary> | |||||
/// Registers a slash command parameter as a complex parameter. | |||||
/// </summary> | |||||
public ComplexParameterAttribute() { } | |||||
/// <summary> | |||||
/// Registers a slash command parameter as a complex parameter with a specified constructor signature. | |||||
/// </summary> | |||||
/// <param name="types">Type array of the preferred constructor parameters.</param> | |||||
public ComplexParameterAttribute(Type[] types) | |||||
{ | |||||
PrioritizedCtorSignature = types; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,10 @@ | |||||
using System; | |||||
namespace Discord.Interactions | |||||
{ | |||||
/// <summary> | |||||
/// Tag a type constructor as the preferred Complex command constructor. | |||||
/// </summary> | |||||
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = true)] | |||||
public class ComplexParameterCtorAttribute : Attribute { } | |||||
} |
@@ -0,0 +1,25 @@ | |||||
using System; | |||||
namespace Discord.Interactions | |||||
{ | |||||
/// <summary> | |||||
/// Customize the displayed value of a slash command choice enum. Only works with the default enum type converter. | |||||
/// </summary> | |||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)] | |||||
public class ChoiceDisplayAttribute : Attribute | |||||
{ | |||||
/// <summary> | |||||
/// Gets the name of the parameter. | |||||
/// </summary> | |||||
public string Name { get; } = null; | |||||
/// <summary> | |||||
/// Modify the default name and description values of a Slash Command parameter. | |||||
/// </summary> | |||||
/// <param name="name">Name of the parameter.</param> | |||||
public ChoiceDisplayAttribute(string name) | |||||
{ | |||||
Name = name; | |||||
} | |||||
} | |||||
} |
@@ -396,7 +396,6 @@ namespace Discord.Interactions.Builders | |||||
builder.Description = paramInfo.Name; | builder.Description = paramInfo.Name; | ||||
builder.IsRequired = !paramInfo.IsOptional; | builder.IsRequired = !paramInfo.IsOptional; | ||||
builder.DefaultValue = paramInfo.DefaultValue; | builder.DefaultValue = paramInfo.DefaultValue; | ||||
builder.SetParameterType(paramType, services); | |||||
foreach (var attribute in attributes) | foreach (var attribute in attributes) | ||||
{ | { | ||||
@@ -434,12 +433,32 @@ namespace Discord.Interactions.Builders | |||||
case MinValueAttribute minValue: | case MinValueAttribute minValue: | ||||
builder.MinValue = minValue.Value; | builder.MinValue = minValue.Value; | ||||
break; | break; | ||||
case ComplexParameterAttribute complexParameter: | |||||
{ | |||||
builder.IsComplexParameter = true; | |||||
ConstructorInfo ctor = GetComplexParameterConstructor(paramInfo.ParameterType.GetTypeInfo(), complexParameter); | |||||
foreach (var parameter in ctor.GetParameters()) | |||||
{ | |||||
if (parameter.IsDefined(typeof(ComplexParameterAttribute))) | |||||
throw new InvalidOperationException("You cannot create nested complex parameters."); | |||||
builder.AddComplexParameterField(fieldBuilder => BuildSlashParameter(fieldBuilder, parameter, services)); | |||||
} | |||||
var initializer = builder.Command.Module.InteractionService._useCompiledLambda ? | |||||
ReflectionUtils<object>.CreateLambdaConstructorInvoker(paramInfo.ParameterType.GetTypeInfo()) : ctor.Invoke; | |||||
builder.ComplexParameterInitializer = args => initializer(args); | |||||
} | |||||
break; | |||||
default: | default: | ||||
builder.AddAttributes(attribute); | builder.AddAttributes(attribute); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
builder.SetParameterType(paramType, services); | |||||
// Replace pascal casings with '-' | // Replace pascal casings with '-' | ||||
builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower(); | builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower(); | ||||
} | } | ||||
@@ -613,5 +632,41 @@ namespace Discord.Interactions.Builders | |||||
propertyInfo.SetMethod?.IsStatic == false && | propertyInfo.SetMethod?.IsStatic == false && | ||||
propertyInfo.IsDefined(typeof(ModalInputAttribute)); | propertyInfo.IsDefined(typeof(ModalInputAttribute)); | ||||
} | } | ||||
private static ConstructorInfo GetComplexParameterConstructor(TypeInfo typeInfo, ComplexParameterAttribute complexParameter) | |||||
{ | |||||
var ctors = typeInfo.GetConstructors(); | |||||
if (ctors.Length == 0) | |||||
throw new InvalidOperationException($"No constructor found for \"{typeInfo.FullName}\"."); | |||||
if (complexParameter.PrioritizedCtorSignature is not null) | |||||
{ | |||||
var ctor = typeInfo.GetConstructor(complexParameter.PrioritizedCtorSignature); | |||||
if (ctor is null) | |||||
throw new InvalidOperationException($"No constructor was found with the signature: {string.Join(",", complexParameter.PrioritizedCtorSignature.Select(x => x.Name))}"); | |||||
return ctor; | |||||
} | |||||
var prioritizedCtors = ctors.Where(x => x.IsDefined(typeof(ComplexParameterCtorAttribute), true)); | |||||
switch (prioritizedCtors.Count()) | |||||
{ | |||||
case > 1: | |||||
throw new InvalidOperationException($"{nameof(ComplexParameterCtorAttribute)} can only be used once in a type."); | |||||
case 1: | |||||
return prioritizedCtors.First(); | |||||
} | |||||
switch (ctors.Length) | |||||
{ | |||||
case > 1: | |||||
throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\"."); | |||||
default: | |||||
return ctors.First(); | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,5 +1,6 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | |||||
namespace Discord.Interactions.Builders | namespace Discord.Interactions.Builders | ||||
{ | { | ||||
@@ -10,6 +11,7 @@ namespace Discord.Interactions.Builders | |||||
{ | { | ||||
private readonly List<ParameterChoice> _choices = new(); | private readonly List<ParameterChoice> _choices = new(); | ||||
private readonly List<ChannelType> _channelTypes = new(); | private readonly List<ChannelType> _channelTypes = new(); | ||||
private readonly List<SlashCommandParameterBuilder> _complexParameterFields = new(); | |||||
/// <summary> | /// <summary> | ||||
/// Gets or sets the description of this parameter. | /// Gets or sets the description of this parameter. | ||||
@@ -36,6 +38,11 @@ namespace Discord.Interactions.Builders | |||||
/// </summary> | /// </summary> | ||||
public IReadOnlyCollection<ChannelType> ChannelTypes => _channelTypes; | public IReadOnlyCollection<ChannelType> ChannelTypes => _channelTypes; | ||||
/// <summary> | |||||
/// Gets the constructor parameters of this parameter, if <see cref="IsComplexParameter"/> is <see langword="true"/>. | |||||
/// </summary> | |||||
public IReadOnlyCollection<SlashCommandParameterBuilder> ComplexParameterFields => _complexParameterFields; | |||||
/// <summary> | /// <summary> | ||||
/// Gets or sets whether this parameter should be configured for Autocomplete Interactions. | /// Gets or sets whether this parameter should be configured for Autocomplete Interactions. | ||||
/// </summary> | /// </summary> | ||||
@@ -46,6 +53,16 @@ namespace Discord.Interactions.Builders | |||||
/// </summary> | /// </summary> | ||||
public TypeConverter TypeConverter { get; private set; } | public TypeConverter TypeConverter { get; private set; } | ||||
/// <summary> | |||||
/// Gets whether this type should be treated as a complex parameter. | |||||
/// </summary> | |||||
public bool IsComplexParameter { get; internal set; } | |||||
/// <summary> | |||||
/// Gets the initializer delegate for this parameter, if <see cref="IsComplexParameter"/> is <see langword="true"/>. | |||||
/// </summary> | |||||
public ComplexParameterInitializer ComplexParameterInitializer { get; internal set; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets or sets the <see cref="IAutocompleteHandler"/> of this parameter. | /// Gets or sets the <see cref="IAutocompleteHandler"/> of this parameter. | ||||
/// </summary> | /// </summary> | ||||
@@ -60,7 +77,14 @@ namespace Discord.Interactions.Builders | |||||
/// <param name="command">Parent command of this parameter.</param> | /// <param name="command">Parent command of this parameter.</param> | ||||
/// <param name="name">Name of this command.</param> | /// <param name="name">Name of this command.</param> | ||||
/// <param name="type">Type of this parameter.</param> | /// <param name="type">Type of this parameter.</param> | ||||
public SlashCommandParameterBuilder(ICommandBuilder command, string name, Type type) : base(command, name, type) { } | |||||
public SlashCommandParameterBuilder(ICommandBuilder command, string name, Type type, ComplexParameterInitializer complexParameterInitializer = null) | |||||
: base(command, name, type) | |||||
{ | |||||
ComplexParameterInitializer = complexParameterInitializer; | |||||
if (complexParameterInitializer is not null) | |||||
IsComplexParameter = true; | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// Sets <see cref="Description"/>. | /// Sets <see cref="Description"/>. | ||||
@@ -168,7 +192,47 @@ namespace Discord.Interactions.Builders | |||||
public SlashCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) | public SlashCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) | ||||
{ | { | ||||
base.SetParameterType(type); | base.SetParameterType(type); | ||||
TypeConverter = Command.Module.InteractionService.GetTypeConverter(ParameterType, services); | |||||
if(!IsComplexParameter) | |||||
TypeConverter = Command.Module.InteractionService.GetTypeConverter(ParameterType, services); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds a parameter builders to <see cref="ComplexParameterFields"/>. | |||||
/// </summary> | |||||
/// <param name="configure"><see cref="SlashCommandParameterBuilder"/> factory.</param> | |||||
/// <returns> | |||||
/// The builder instance. | |||||
/// </returns> | |||||
/// <exception cref="InvalidOperationException">Thrown if the added field has a <see cref="ComplexParameterAttribute"/>.</exception> | |||||
public SlashCommandParameterBuilder AddComplexParameterField(Action<SlashCommandParameterBuilder> configure) | |||||
{ | |||||
SlashCommandParameterBuilder builder = new(Command); | |||||
configure(builder); | |||||
if(builder.IsComplexParameter) | |||||
throw new InvalidOperationException("You cannot create nested complex parameters."); | |||||
_complexParameterFields.Add(builder); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Adds parameter builders to <see cref="ComplexParameterFields"/>. | |||||
/// </summary> | |||||
/// <param name="fields">New parameter builders to be added to <see cref="ComplexParameterFields"/>.</param> | |||||
/// <returns> | |||||
/// The builder instance. | |||||
/// </returns> | |||||
/// <exception cref="InvalidOperationException">Thrown if the added field has a <see cref="ComplexParameterAttribute"/>.</exception> | |||||
public SlashCommandParameterBuilder AddComplexParameterFields(params SlashCommandParameterBuilder[] fields) | |||||
{ | |||||
if(fields.Any(x => x.IsComplexParameter)) | |||||
throw new InvalidOperationException("You cannot create nested complex parameters."); | |||||
_complexParameterFields.AddRange(fields); | |||||
return this; | return this; | ||||
} | } | ||||
@@ -31,7 +31,7 @@ namespace Discord.Interactions | |||||
private readonly ExecuteCallback _action; | private readonly ExecuteCallback _action; | ||||
private readonly ILookup<string, PreconditionAttribute> _groupedPreconditions; | private readonly ILookup<string, PreconditionAttribute> _groupedPreconditions; | ||||
internal IReadOnlyDictionary<string, TParameter> _parameterDictionary; | |||||
internal IReadOnlyDictionary<string, TParameter> _parameterDictionary { get; } | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public ModuleInfo Module { get; } | public ModuleInfo Module { get; } | ||||
@@ -81,6 +81,7 @@ namespace Discord.Interactions | |||||
_action = builder.Callback; | _action = builder.Callback; | ||||
_groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal); | _groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal); | ||||
_parameterDictionary = Parameters?.ToDictionary(x => x.Name, x => x).ToImmutableDictionary(); | |||||
} | } | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
@@ -13,6 +13,8 @@ namespace Discord.Interactions | |||||
/// </summary> | /// </summary> | ||||
public class SlashCommandInfo : CommandInfo<SlashCommandParameterInfo>, IApplicationCommandInfo | public class SlashCommandInfo : CommandInfo<SlashCommandParameterInfo>, IApplicationCommandInfo | ||||
{ | { | ||||
internal IReadOnlyDictionary<string, SlashCommandParameterInfo> _flattenedParameterDictionary { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the command description that will be displayed on Discord. | /// Gets the command description that will be displayed on Discord. | ||||
/// </summary> | /// </summary> | ||||
@@ -30,11 +32,23 @@ namespace Discord.Interactions | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public override bool SupportsWildCards => false; | public override bool SupportsWildCards => false; | ||||
/// <summary> | |||||
/// Gets the flattened collection of command parameters and complex parameter fields. | |||||
/// </summary> | |||||
public IReadOnlyCollection<SlashCommandParameterInfo> FlattenedParameters { get; } | |||||
internal SlashCommandInfo (Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) | internal SlashCommandInfo (Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) | ||||
{ | { | ||||
Description = builder.Description; | Description = builder.Description; | ||||
DefaultPermission = builder.DefaultPermission; | DefaultPermission = builder.DefaultPermission; | ||||
Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | ||||
FlattenedParameters = FlattenParameters(Parameters).ToImmutableArray(); | |||||
for (var i = 0; i < FlattenedParameters.Count - 1; i++) | |||||
if (!FlattenedParameters.ElementAt(i).IsRequired && FlattenedParameters.ElementAt(i + 1).IsRequired) | |||||
throw new InvalidOperationException("Optional parameters must appear after all required parameters, ComplexParameters with optional parameters must be located at the end."); | |||||
_flattenedParameterDictionary = FlattenedParameters?.ToDictionary(x => x.Name, x => x).ToImmutableDictionary(); | |||||
} | } | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
@@ -74,7 +88,7 @@ namespace Discord.Interactions | |||||
} | } | ||||
return await RunAsync(context, args, services).ConfigureAwait(false); | return await RunAsync(context, args, services).ConfigureAwait(false); | ||||
} | } | ||||
catch (Exception ex) | |||||
else | |||||
{ | { | ||||
return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false); | return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false); | ||||
} | } | ||||
@@ -127,5 +141,15 @@ namespace Discord.Interactions | |||||
else | else | ||||
return $"Slash Command: \"{base.ToString()}\" for {context.User} in {context.Channel}"; | return $"Slash Command: \"{base.ToString()}\" for {context.User} in {context.Channel}"; | ||||
} | } | ||||
private static IEnumerable<SlashCommandParameterInfo> FlattenParameters(IEnumerable<SlashCommandParameterInfo> parameters) | |||||
{ | |||||
foreach (var parameter in parameters) | |||||
if (!parameter.IsComplexParameter) | |||||
yield return parameter; | |||||
else | |||||
foreach(var complexParameterField in parameter.ComplexParameterFields) | |||||
yield return complexParameterField; | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,13 +1,25 @@ | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
using System.Linq; | |||||
namespace Discord.Interactions | namespace Discord.Interactions | ||||
{ | { | ||||
/// <summary> | |||||
/// Represents a cached argument constructor delegate. | |||||
/// </summary> | |||||
/// <param name="args">Method arguments array.</param> | |||||
/// <returns> | |||||
/// Returns the constructed object. | |||||
/// </returns> | |||||
public delegate object ComplexParameterInitializer(object[] args); | |||||
/// <summary> | /// <summary> | ||||
/// Represents the parameter info class for <see cref="SlashCommandInfo"/> commands. | /// Represents the parameter info class for <see cref="SlashCommandInfo"/> commands. | ||||
/// </summary> | /// </summary> | ||||
public class SlashCommandParameterInfo : CommandParameterInfo | public class SlashCommandParameterInfo : CommandParameterInfo | ||||
{ | { | ||||
internal readonly ComplexParameterInitializer _complexParameterInitializer; | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public new SlashCommandInfo Command => base.Command as SlashCommandInfo; | public new SlashCommandInfo Command => base.Command as SlashCommandInfo; | ||||
@@ -43,9 +55,14 @@ namespace Discord.Interactions | |||||
public bool IsAutocomplete { get; } | public bool IsAutocomplete { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets the Discord option type this parameter represents. | |||||
/// Gets whether this type should be treated as a complex parameter. | |||||
/// </summary> | /// </summary> | ||||
public ApplicationCommandOptionType DiscordOptionType => TypeConverter.GetDiscordType(); | |||||
public bool IsComplexParameter { get; } | |||||
/// <summary> | |||||
/// Gets the Discord option type this parameter represents. If the parameter is not a complex parameter. | |||||
/// </summary> | |||||
public ApplicationCommandOptionType? DiscordOptionType => TypeConverter?.GetDiscordType(); | |||||
/// <summary> | /// <summary> | ||||
/// Gets the parameter choices of this Slash Application Command parameter. | /// Gets the parameter choices of this Slash Application Command parameter. | ||||
@@ -57,6 +74,11 @@ namespace Discord.Interactions | |||||
/// </summary> | /// </summary> | ||||
public IReadOnlyCollection<ChannelType> ChannelTypes { get; } | public IReadOnlyCollection<ChannelType> ChannelTypes { get; } | ||||
/// <summary> | |||||
/// Gets the constructor parameters of this parameter, if <see cref="IsComplexParameter"/> is <see langword="true"/>. | |||||
/// </summary> | |||||
public IReadOnlyCollection<SlashCommandParameterInfo> ComplexParameterFields { get; } | |||||
internal SlashCommandParameterInfo(Builders.SlashCommandParameterBuilder builder, SlashCommandInfo command) : base(builder, command) | internal SlashCommandParameterInfo(Builders.SlashCommandParameterBuilder builder, SlashCommandInfo command) : base(builder, command) | ||||
{ | { | ||||
TypeConverter = builder.TypeConverter; | TypeConverter = builder.TypeConverter; | ||||
@@ -64,9 +86,13 @@ namespace Discord.Interactions | |||||
Description = builder.Description; | Description = builder.Description; | ||||
MaxValue = builder.MaxValue; | MaxValue = builder.MaxValue; | ||||
MinValue = builder.MinValue; | MinValue = builder.MinValue; | ||||
IsComplexParameter = builder.IsComplexParameter; | |||||
IsAutocomplete = builder.Autocomplete; | IsAutocomplete = builder.Autocomplete; | ||||
Choices = builder.Choices.ToImmutableArray(); | Choices = builder.Choices.ToImmutableArray(); | ||||
ChannelTypes = builder.ChannelTypes.ToImmutableArray(); | ChannelTypes = builder.ChannelTypes.ToImmutableArray(); | ||||
ComplexParameterFields = builder.ComplexParameterFields?.Select(x => x.Build(command)).ToImmutableArray(); | |||||
_complexParameterInitializer = builder.ComplexParameterInitializer; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -765,9 +765,7 @@ namespace Discord.Interactions | |||||
if(autocompleteHandlerResult.IsSuccess) | if(autocompleteHandlerResult.IsSuccess) | ||||
{ | { | ||||
var parameter = autocompleteHandlerResult.Command.Parameters.FirstOrDefault(x => string.Equals(x.Name, interaction.Data.Current.Name, StringComparison.Ordinal)); | |||||
if(parameter?.AutocompleteHandler is not null) | |||||
if (autocompleteHandlerResult.Command._flattenedParameterDictionary.TryGetValue(interaction.Data.Current.Name, out var parameter) && parameter?.AutocompleteHandler is not null) | |||||
return await parameter.AutocompleteHandler.ExecuteAsync(context, interaction, parameter, services).ConfigureAwait(false); | return await parameter.AutocompleteHandler.ExecuteAsync(context, interaction, parameter, services).ConfigureAwait(false); | ||||
} | } | ||||
} | } | ||||
@@ -0,0 +1,36 @@ | |||||
using System; | |||||
namespace Discord.Interactions | |||||
{ | |||||
internal struct ParseResult : IResult | |||||
{ | |||||
public object Value { get; } | |||||
public InteractionCommandError? Error { get; } | |||||
public string ErrorReason { get; } | |||||
public bool IsSuccess => !Error.HasValue; | |||||
private ParseResult(object value, InteractionCommandError? error, string reason) | |||||
{ | |||||
Value = value; | |||||
Error = error; | |||||
ErrorReason = reason; | |||||
} | |||||
public static ParseResult FromSuccess(object value) => | |||||
new ParseResult(value, null, null); | |||||
public static ParseResult FromError(Exception exception) => | |||||
new ParseResult(null, InteractionCommandError.Exception, exception.Message); | |||||
public static ParseResult FromError(InteractionCommandError error, string reason) => | |||||
new ParseResult(null, error, reason); | |||||
public static ParseResult FromError(IResult result) => | |||||
new ParseResult(null, result.Error, result.ErrorReason); | |||||
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; | |||||
} | |||||
} |
@@ -2,6 +2,7 @@ using Discord.WebSocket; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Reflection; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord.Interactions | namespace Discord.Interactions | ||||
@@ -27,12 +28,14 @@ namespace Discord.Interactions | |||||
var choices = new List<ApplicationCommandOptionChoiceProperties>(); | var choices = new List<ApplicationCommandOptionChoiceProperties>(); | ||||
foreach (var member in members) | foreach (var member in members) | ||||
{ | |||||
var displayValue = member.GetCustomAttribute<ChoiceDisplayAttribute>()?.Name ?? member.Name; | |||||
choices.Add(new ApplicationCommandOptionChoiceProperties | choices.Add(new ApplicationCommandOptionChoiceProperties | ||||
{ | { | ||||
Name = member.Name, | |||||
Name = displayValue, | |||||
Value = member.Name | Value = member.Name | ||||
}); | }); | ||||
} | |||||
properties.Choices = choices; | properties.Choices = choices; | ||||
} | } | ||||
} | } | ||||
@@ -13,7 +13,7 @@ namespace Discord.Interactions | |||||
{ | { | ||||
Name = parameterInfo.Name, | Name = parameterInfo.Name, | ||||
Description = parameterInfo.Description, | Description = parameterInfo.Description, | ||||
Type = parameterInfo.DiscordOptionType, | |||||
Type = parameterInfo.DiscordOptionType.Value, | |||||
IsRequired = parameterInfo.IsRequired, | IsRequired = parameterInfo.IsRequired, | ||||
Choices = parameterInfo.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties | Choices = parameterInfo.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties | ||||
{ | { | ||||
@@ -46,7 +46,7 @@ namespace Discord.Interactions | |||||
if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount) | if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount) | ||||
throw new InvalidOperationException($"Slash Commands cannot have more than {SlashCommandBuilder.MaxOptionsCount} command parameters"); | throw new InvalidOperationException($"Slash Commands cannot have more than {SlashCommandBuilder.MaxOptionsCount} command parameters"); | ||||
props.Options = commandInfo.Parameters.Select(x => x.ToApplicationCommandOptionProps())?.ToList() ?? Optional<List<ApplicationCommandOptionProperties>>.Unspecified; | |||||
props.Options = commandInfo.FlattenedParameters.Select(x => x.ToApplicationCommandOptionProps())?.ToList() ?? Optional<List<ApplicationCommandOptionProperties>>.Unspecified; | |||||
return props; | return props; | ||||
} | } | ||||
@@ -58,7 +58,7 @@ namespace Discord.Interactions | |||||
Description = commandInfo.Description, | Description = commandInfo.Description, | ||||
Type = ApplicationCommandOptionType.SubCommand, | Type = ApplicationCommandOptionType.SubCommand, | ||||
IsRequired = false, | IsRequired = false, | ||||
Options = commandInfo.Parameters?.Select(x => x.ToApplicationCommandOptionProps())?.ToList() | |||||
Options = commandInfo.FlattenedParameters?.Select(x => x.ToApplicationCommandOptionProps())?.ToList() | |||||
}; | }; | ||||
public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo) | public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo) | ||||
@@ -39,5 +39,7 @@ namespace Discord.API | |||||
public Optional<User> Creator { get; set; } | public Optional<User> Creator { get; set; } | ||||
[JsonProperty("user_count")] | [JsonProperty("user_count")] | ||||
public Optional<int> UserCount { get; set; } | public Optional<int> UserCount { get; set; } | ||||
[JsonProperty("image")] | |||||
public string Image { get; set; } | |||||
} | } | ||||
} | } |
@@ -16,5 +16,11 @@ namespace Discord.API | |||||
[JsonProperty("locked")] | [JsonProperty("locked")] | ||||
public Optional<bool> Locked { get; set; } | public Optional<bool> Locked { get; set; } | ||||
[JsonProperty("invitable")] | |||||
public Optional<bool> Invitable { get; set; } | |||||
[JsonProperty("create_timestamp")] | |||||
public Optional<DateTimeOffset> CreatedAt { get; set; } | |||||
} | } | ||||
} | } |
@@ -28,6 +28,8 @@ namespace Discord.API | |||||
public bool Suppress { get; set; } | public bool Suppress { get; set; } | ||||
[JsonProperty("self_stream")] | [JsonProperty("self_stream")] | ||||
public bool SelfStream { get; set; } | public bool SelfStream { get; set; } | ||||
[JsonProperty("self_video")] | |||||
public bool SelfVideo { get; set; } | |||||
[JsonProperty("request_to_speak_timestamp")] | [JsonProperty("request_to_speak_timestamp")] | ||||
public Optional<DateTimeOffset?> RequestToSpeakTimestamp { get; set; } | public Optional<DateTimeOffset?> RequestToSpeakTimestamp { get; set; } | ||||
} | } | ||||
@@ -25,5 +25,7 @@ namespace Discord.API.Rest | |||||
public Optional<string> Description { get; set; } | public Optional<string> Description { get; set; } | ||||
[JsonProperty("entity_type")] | [JsonProperty("entity_type")] | ||||
public GuildScheduledEventType Type { get; set; } | public GuildScheduledEventType Type { get; set; } | ||||
[JsonProperty("image")] | |||||
public Optional<Image> Image { get; set; } | |||||
} | } | ||||
} | } |
@@ -28,6 +28,9 @@ namespace Discord.API.Rest | |||||
[JsonProperty("sticker_ids")] | [JsonProperty("sticker_ids")] | ||||
public Optional<ulong[]> Stickers { get; set; } | public Optional<ulong[]> Stickers { get; set; } | ||||
[JsonProperty("flags")] | |||||
public Optional<MessageFlags> Flags { get; set; } | |||||
public CreateMessageParams(string content) | public CreateMessageParams(string content) | ||||
{ | { | ||||
@@ -27,5 +27,7 @@ namespace Discord.API.Rest | |||||
public Optional<GuildScheduledEventType> Type { get; set; } | public Optional<GuildScheduledEventType> Type { get; set; } | ||||
[JsonProperty("status")] | [JsonProperty("status")] | ||||
public Optional<GuildScheduledEventStatus> Status { get; set; } | public Optional<GuildScheduledEventStatus> Status { get; set; } | ||||
[JsonProperty("image")] | |||||
public Optional<Image?> Image { get; set; } | |||||
} | } | ||||
} | } |
@@ -51,7 +51,7 @@ namespace Discord.API.Rest | |||||
if (Stickers.IsSpecified) | if (Stickers.IsSpecified) | ||||
payload["sticker_ids"] = Stickers.Value; | payload["sticker_ids"] = Stickers.Value; | ||||
if (Flags.IsSpecified) | if (Flags.IsSpecified) | ||||
payload["flags"] = Flags; | |||||
payload["flags"] = Flags.Value; | |||||
List<object> attachments = new(); | List<object> attachments = new(); | ||||
@@ -266,8 +266,10 @@ namespace Discord.Rest | |||||
} | } | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, | public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, Embed[] embeds) | |||||
string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, RequestOptions options, Embed[] embeds, MessageFlags flags) | |||||
{ | { | ||||
embeds ??= Array.Empty<Embed>(); | embeds ??= Array.Empty<Embed>(); | ||||
if (embed != null) | if (embed != null) | ||||
@@ -298,6 +300,10 @@ namespace Discord.Rest | |||||
Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); | Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); | ||||
} | } | ||||
if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds) | |||||
throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags)); | |||||
var args = new CreateMessageParams(text) | var args = new CreateMessageParams(text) | ||||
{ | { | ||||
IsTTS = isTTS, | IsTTS = isTTS, | ||||
@@ -305,7 +311,8 @@ namespace Discord.Rest | |||||
AllowedMentions = allowedMentions?.ToModel(), | AllowedMentions = allowedMentions?.ToModel(), | ||||
MessageReference = messageReference?.ToModel(), | MessageReference = messageReference?.ToModel(), | ||||
Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | ||||
Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified | |||||
Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified, | |||||
Flags = flags | |||||
}; | }; | ||||
var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | return RestUserMessage.Create(client, channel, client.CurrentUser, model); | ||||
@@ -335,29 +342,44 @@ namespace Discord.Rest | |||||
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | ||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | /// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
string filePath, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, bool isSpoiler, Embed[] embeds) | |||||
string filePath, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, | |||||
MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, | |||||
bool isSpoiler, Embed[] embeds, MessageFlags flags = MessageFlags.None) | |||||
{ | { | ||||
string filename = Path.GetFileName(filePath); | string filename = Path.GetFileName(filePath); | ||||
using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, isSpoiler, embeds).ConfigureAwait(false); | |||||
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, allowedMentions, | |||||
messageReference, components, stickers, options, isSpoiler, embeds, flags).ConfigureAwait(false); | |||||
} | } | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
Stream stream, string filename, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, bool isSpoiler, Embed[] embeds) | |||||
Stream stream, string filename, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, | |||||
MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, | |||||
bool isSpoiler, Embed[] embeds, MessageFlags flags = MessageFlags.None) | |||||
{ | { | ||||
using (var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler)) | using (var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler)) | ||||
return await SendFileAsync(channel, client, file, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds).ConfigureAwait(false); | |||||
return await SendFileAsync(channel, client, file, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags).ConfigureAwait(false); | |||||
} | } | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public static Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | public static Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
FileAttachment attachment, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, Embed[] embeds) | |||||
=> SendFilesAsync(channel, client, new[] { attachment }, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
FileAttachment attachment, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, | |||||
MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, | |||||
Embed[] embeds, MessageFlags flags = MessageFlags.None) | |||||
=> SendFilesAsync(channel, client, new[] { attachment }, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public static async Task<RestUserMessage> SendFilesAsync(IMessageChannel channel, BaseDiscordClient client, | public static async Task<RestUserMessage> SendFilesAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
IEnumerable<FileAttachment> attachments, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, Embed[] embeds) | |||||
IEnumerable<FileAttachment> attachments, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, | |||||
MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, | |||||
Embed[] embeds, MessageFlags flags) | |||||
{ | { | ||||
embeds ??= Array.Empty<Embed>(); | embeds ??= Array.Empty<Embed>(); | ||||
if (embed != null) | if (embed != null) | ||||
@@ -366,7 +388,7 @@ namespace Discord.Rest | |||||
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | ||||
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); | Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); | ||||
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); | Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); | ||||
foreach(var attachment in attachments) | foreach(var attachment in attachments) | ||||
{ | { | ||||
Preconditions.NotNullOrEmpty(attachment.FileName, nameof(attachment.FileName), "File Name must not be empty or null"); | Preconditions.NotNullOrEmpty(attachment.FileName, nameof(attachment.FileName), "File Name must not be empty or null"); | ||||
@@ -398,12 +420,26 @@ namespace Discord.Rest | |||||
} | } | ||||
} | } | ||||
if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds) | |||||
throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags)); | |||||
if (stickers != null) | if (stickers != null) | ||||
{ | { | ||||
Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); | Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); | ||||
} | } | ||||
var args = new UploadFileParams(attachments.ToArray()) { Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, MessageComponent = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified }; | |||||
var args = new UploadFileParams(attachments.ToArray()) | |||||
{ | |||||
Content = text, | |||||
IsTTS = isTTS, | |||||
Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, | |||||
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, | |||||
MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, | |||||
MessageComponent = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | |||||
Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified, | |||||
Flags = flags | |||||
}; | |||||
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | return RestUserMessage.Create(client, channel, client.CurrentUser, model); | ||||
} | } | ||||
@@ -9,84 +9,14 @@ namespace Discord.Rest | |||||
/// </summary> | /// </summary> | ||||
public interface IRestMessageChannel : IMessageChannel | public interface IRestMessageChannel : IMessageChannel | ||||
{ | { | ||||
/// <summary> | |||||
/// Sends a message to this message channel. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendMessageAsync"/>. | |||||
/// Please visit its documentation for more details on this method. | |||||
/// </remarks> | |||||
/// <param name="text">The message to be sent.</param> | |||||
/// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | |||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <param name="allowedMentions"> | |||||
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||||
/// </param> | |||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param> | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
/// <param name="stickers">A collection of stickers to send with the message.</param> | |||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
/// <summary> | |||||
/// Sends a file to this message channel with an optional caption. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method follows the same behavior as described in | |||||
/// <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/>. Please visit | |||||
/// its documentation for more details on this method. | |||||
/// </remarks> | |||||
/// <param name="filePath">The file path of the file.</param> | |||||
/// <param name="text">The message to be sent.</param> | |||||
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich" /> <see cref="Embed" /> to be sent.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
/// <param name="allowedMentions"> | |||||
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||||
/// </param> | |||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param> | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
/// <param name="stickers">A collection of stickers to send with the message.</param> | |||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
/// <summary> | |||||
/// Sends a file to this message channel with an optional caption. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/>. | |||||
/// Please visit its documentation for more details on this method. | |||||
/// </remarks> | |||||
/// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param> | |||||
/// <param name="filename">The name of the attachment.</param> | |||||
/// <param name="text">The message to be sent.</param> | |||||
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
/// <param name="allowedMentions"> | |||||
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||||
/// </param> | |||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param> | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
/// <param name="stickers">A collection of stickers to send with the message.</param> | |||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
/// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <inheritdoc cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <inheritdoc cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a message from this message channel. | /// Gets a message from this message channel. | ||||
@@ -13,7 +13,7 @@ namespace Discord.Rest | |||||
{ | { | ||||
#region RestChannel | #region RestChannel | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||||
public virtual DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||||
internal RestChannel(BaseDiscordClient discord, ulong id) | internal RestChannel(BaseDiscordClient discord, ulong id) | ||||
: base(discord, id) | : base(discord, id) | ||||
@@ -94,8 +94,12 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, | |||||
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentException"> | /// <exception cref="ArgumentException"> | ||||
@@ -122,22 +126,39 @@ namespace Discord.Rest | |||||
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | ||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | /// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, isSpoiler, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, isSpoiler, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, isSpoiler, embeds); | |||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, | |||||
messageReference, components, stickers, options, isSpoiler, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
public Task<RestUserMessage> SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | ||||
@@ -219,20 +240,38 @@ namespace Discord.Rest | |||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | ||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, | |||||
RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, | |||||
components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, | |||||
Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, | |||||
components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, | |||||
Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, | |||||
stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, | |||||
bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, | |||||
AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | |||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
#region IChannel | #region IChannel | ||||
@@ -104,8 +104,12 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, | |||||
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentException"> | /// <exception cref="ArgumentException"> | ||||
@@ -132,20 +136,40 @@ namespace Discord.Rest | |||||
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | ||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | /// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, isSpoiler, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, isSpoiler, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, isSpoiler, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, | |||||
messageReference, components, stickers, options, isSpoiler, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, | |||||
messageReference, components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
@@ -197,17 +221,41 @@ namespace Discord.Rest | |||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | ||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, | |||||
RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, | |||||
components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, | |||||
Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, | |||||
components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, | |||||
Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, | |||||
stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, | |||||
bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, | |||||
stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, | |||||
AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | |||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, | |||||
stickers, embeds, flags).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
#region IAudioChannel | #region IAudioChannel | ||||
@@ -227,7 +227,7 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
=> AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overridden //Overridden in Text/Voice | |||||
=> AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overridden in Text/Voice | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
=> Task.FromResult<IGuildUser>(null); //Overridden in Text/Voice | => Task.FromResult<IGuildUser>(null); //Overridden in Text/Voice | ||||
@@ -103,8 +103,12 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, | |||||
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentException"> | /// <exception cref="ArgumentException"> | ||||
@@ -131,23 +135,42 @@ namespace Discord.Rest | |||||
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | ||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | /// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, isSpoiler, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, isSpoiler, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, isSpoiler, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, isSpoiler, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | ||||
@@ -261,7 +284,7 @@ namespace Discord.Rest | |||||
/// The duration on which this thread archives after. | /// The duration on which this thread archives after. | ||||
/// <para> | /// <para> | ||||
/// <b>Note: </b> Options <see cref="ThreadArchiveDuration.OneWeek"/> and <see cref="ThreadArchiveDuration.ThreeDays"/> | /// <b>Note: </b> Options <see cref="ThreadArchiveDuration.OneWeek"/> and <see cref="ThreadArchiveDuration.ThreeDays"/> | ||||
/// are only available for guilds that are boosted. You can check in the <see cref="IGuild.Features"/> to see if the | |||||
/// are only available for guilds that are boosted. You can check in the <see cref="IGuild.Features"/> to see if the | |||||
/// guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>. | /// guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>. | ||||
/// </para> | /// </para> | ||||
/// </param> | /// </param> | ||||
@@ -332,24 +355,37 @@ namespace Discord.Rest | |||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, | |||||
RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, | |||||
components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, | |||||
Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, | |||||
components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, | |||||
Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, | |||||
stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, | |||||
bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, | |||||
AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | |||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
#region IGuildChannel | #region IGuildChannel | ||||
@@ -364,10 +400,9 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
{ | { | ||||
if (mode == CacheMode.AllowDownload) | |||||
return GetUsersAsync(options); | |||||
else | |||||
return AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); | |||||
return mode == CacheMode.AllowDownload | |||||
? GetUsersAsync(options) | |||||
: AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); | |||||
} | } | ||||
#endregion | #endregion | ||||
@@ -34,17 +34,26 @@ namespace Discord.Rest | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public int MessageCount { get; private set; } | public int MessageCount { get; private set; } | ||||
/// <inheritdoc/> | |||||
public bool? IsInvitable { get; private set; } | |||||
/// <inheritdoc cref="IThreadChannel.CreatedAt"/> | |||||
public override DateTimeOffset CreatedAt { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the parent text channel id. | /// Gets the parent text channel id. | ||||
/// </summary> | /// </summary> | ||||
public ulong ParentChannelId { get; private set; } | public ulong ParentChannelId { get; private set; } | ||||
internal RestThreadChannel(BaseDiscordClient discord, IGuild guild, ulong id) | |||||
: base(discord, guild, id) { } | |||||
internal RestThreadChannel(BaseDiscordClient discord, IGuild guild, ulong id, DateTimeOffset? createdAt) | |||||
: base(discord, guild, id) | |||||
{ | |||||
CreatedAt = createdAt ?? new DateTimeOffset(2022, 1, 9, 0, 0, 0, TimeSpan.Zero); | |||||
} | |||||
internal new static RestThreadChannel Create(BaseDiscordClient discord, IGuild guild, Model model) | internal new static RestThreadChannel Create(BaseDiscordClient discord, IGuild guild, Model model) | ||||
{ | { | ||||
var entity = new RestThreadChannel(discord, guild, model.Id); | |||||
var entity = new RestThreadChannel(discord, guild, model.Id, model.ThreadMetadata.GetValueOrDefault()?.CreatedAt.GetValueOrDefault()); | |||||
entity.Update(model); | entity.Update(model); | ||||
return entity; | return entity; | ||||
} | } | ||||
@@ -57,6 +66,7 @@ namespace Discord.Rest | |||||
if (model.ThreadMetadata.IsSpecified) | if (model.ThreadMetadata.IsSpecified) | ||||
{ | { | ||||
IsInvitable = model.ThreadMetadata.Value.Invitable.ToNullable(); | |||||
IsArchived = model.ThreadMetadata.Value.Archived; | IsArchived = model.ThreadMetadata.Value.Archived; | ||||
AutoArchiveDuration = model.ThreadMetadata.Value.AutoArchiveDuration; | AutoArchiveDuration = model.ThreadMetadata.Value.AutoArchiveDuration; | ||||
ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp; | ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp; | ||||
@@ -799,7 +799,12 @@ namespace Discord.Rest | |||||
PrivacyLevel = args.PrivacyLevel, | PrivacyLevel = args.PrivacyLevel, | ||||
StartTime = args.StartTime, | StartTime = args.StartTime, | ||||
Status = args.Status, | Status = args.Status, | ||||
Type = args.Type | |||||
Type = args.Type, | |||||
Image = args.CoverImage.IsSpecified | |||||
? args.CoverImage.Value.HasValue | |||||
? args.CoverImage.Value.Value.ToModel() | |||||
: null | |||||
: Optional<ImageModel?>.Unspecified | |||||
}; | }; | ||||
if(args.Location.IsSpecified) | if(args.Location.IsSpecified) | ||||
@@ -839,6 +844,7 @@ namespace Discord.Rest | |||||
DateTimeOffset? endTime = null, | DateTimeOffset? endTime = null, | ||||
ulong? channelId = null, | ulong? channelId = null, | ||||
string location = null, | string location = null, | ||||
Image? bannerImage = null, | |||||
RequestOptions options = null) | RequestOptions options = null) | ||||
{ | { | ||||
if(location != null) | if(location != null) | ||||
@@ -864,6 +870,7 @@ namespace Discord.Rest | |||||
if (endTime != null && endTime <= startTime) | if (endTime != null && endTime <= startTime) | ||||
throw new ArgumentOutOfRangeException(nameof(endTime), $"{nameof(endTime)} cannot be before the start time"); | throw new ArgumentOutOfRangeException(nameof(endTime), $"{nameof(endTime)} cannot be before the start time"); | ||||
var apiArgs = new CreateGuildScheduledEventParams() | var apiArgs = new CreateGuildScheduledEventParams() | ||||
{ | { | ||||
ChannelId = channelId ?? Optional<ulong>.Unspecified, | ChannelId = channelId ?? Optional<ulong>.Unspecified, | ||||
@@ -872,7 +879,8 @@ namespace Discord.Rest | |||||
Name = name, | Name = name, | ||||
PrivacyLevel = privacyLevel, | PrivacyLevel = privacyLevel, | ||||
StartTime = startTime, | StartTime = startTime, | ||||
Type = type | |||||
Type = type, | |||||
Image = bannerImage.HasValue ? bannerImage.Value.ToModel() : Optional<ImageModel>.Unspecified | |||||
}; | }; | ||||
if(location != null) | if(location != null) | ||||
@@ -1167,6 +1167,7 @@ namespace Discord.Rest | |||||
/// </param> | /// </param> | ||||
/// <param name="speakers">A collection of speakers for the event.</param> | /// <param name="speakers">A collection of speakers for the event.</param> | ||||
/// <param name="location">The location of the event; links are supported</param> | /// <param name="location">The location of the event; links are supported</param> | ||||
/// <param name="coverImage">The optional banner image for the event.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
/// <returns> | /// <returns> | ||||
/// A task that represents the asynchronous create operation. | /// A task that represents the asynchronous create operation. | ||||
@@ -1180,8 +1181,9 @@ namespace Discord.Rest | |||||
DateTimeOffset? endTime = null, | DateTimeOffset? endTime = null, | ||||
ulong? channelId = null, | ulong? channelId = null, | ||||
string location = null, | string location = null, | ||||
Image? coverImage = null, | |||||
RequestOptions options = null) | RequestOptions options = null) | ||||
=> GuildHelper.CreateGuildEventAsync(Discord, this, name, privacyLevel, startTime, type, description, endTime, channelId, location, options); | |||||
=> GuildHelper.CreateGuildEventAsync(Discord, this, name, privacyLevel, startTime, type, description, endTime, channelId, location, coverImage, options); | |||||
#endregion | #endregion | ||||
@@ -1198,8 +1200,8 @@ namespace Discord.Rest | |||||
IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers; | IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IGuildScheduledEvent> IGuild.CreateEventAsync(string name, DateTimeOffset startTime, GuildScheduledEventType type, GuildScheduledEventPrivacyLevel privacyLevel, string description, DateTimeOffset? endTime, ulong? channelId, string location, RequestOptions options) | |||||
=> await CreateEventAsync(name, startTime, type, privacyLevel, description, endTime, channelId, location, options).ConfigureAwait(false); | |||||
async Task<IGuildScheduledEvent> IGuild.CreateEventAsync(string name, DateTimeOffset startTime, GuildScheduledEventType type, GuildScheduledEventPrivacyLevel privacyLevel, string description, DateTimeOffset? endTime, ulong? channelId, string location, Image? coverImage, RequestOptions options) | |||||
=> await CreateEventAsync(name, startTime, type, privacyLevel, description, endTime, channelId, location, coverImage, options).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions options) | async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions options) | ||||
@@ -28,6 +28,9 @@ namespace Discord.Rest | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public string Description { get; private set; } | public string Description { get; private set; } | ||||
/// <inheritdoc/> | |||||
public string CoverImageId { get; private set; } | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public DateTimeOffset StartTime { get; private set; } | public DateTimeOffset StartTime { get; private set; } | ||||
@@ -98,8 +101,13 @@ namespace Discord.Rest | |||||
EntityId = model.EntityId; | EntityId = model.EntityId; | ||||
Location = model.EntityMetadata?.Location.GetValueOrDefault(); | Location = model.EntityMetadata?.Location.GetValueOrDefault(); | ||||
UserCount = model.UserCount.ToNullable(); | UserCount = model.UserCount.ToNullable(); | ||||
CoverImageId = model.Image; | |||||
} | } | ||||
/// <inheritdoc/> | |||||
public string GetCoverImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 1024) | |||||
=> CDN.GetEventCoverImageUrl(Guild.Id, Id, CoverImageId, format, size); | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public Task StartAsync(RequestOptions options = null) | public Task StartAsync(RequestOptions options = null) | ||||
=> ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active); | => ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active); | ||||
@@ -53,7 +53,6 @@ namespace Discord.Rest | |||||
AllowedMentions allowedMentions = args.AllowedMentions.Value; | AllowedMentions allowedMentions = args.AllowedMentions.Value; | ||||
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | ||||
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); | Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); | ||||
Preconditions.AtMost(args.Embeds.Value?.Length ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); | |||||
// check that user flag and user Id list are exclusive, same with role flag and role Id list | // check that user flag and user Id list are exclusive, same with role flag and role Id list | ||||
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) | if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) | ||||
@@ -41,6 +41,8 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
bool IVoiceState.IsStreaming => false; | bool IVoiceState.IsStreaming => false; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
bool IVoiceState.IsVideoing => false; | |||||
/// <inheritdoc /> | |||||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | ||||
#endregion | #endregion | ||||
} | } | ||||
@@ -19,10 +19,13 @@ namespace Discord.Rest | |||||
private long? _timedOutTicks; | private long? _timedOutTicks; | ||||
private long? _joinedAtTicks; | private long? _joinedAtTicks; | ||||
private ImmutableArray<ulong> _roleIds; | private ImmutableArray<ulong> _roleIds; | ||||
/// <inheritdoc /> | |||||
public string DisplayName => Nickname ?? Username; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public string Nickname { get; private set; } | public string Nickname { get; private set; } | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public string DisplayAvatarId => GuildAvatarId ?? AvatarId; | |||||
/// <inheritdoc/> | |||||
public string GuildAvatarId { get; private set; } | public string GuildAvatarId { get; private set; } | ||||
internal IGuild Guild { get; private set; } | internal IGuild Guild { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -182,6 +185,13 @@ namespace Discord.Rest | |||||
return new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, guildPerms.RawValue)); | return new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, guildPerms.RawValue)); | ||||
} | } | ||||
/// <inheritdoc /> | |||||
public string GetDisplayAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||||
=> GuildAvatarId is not null | |||||
? GetGuildAvatarUrl(format, size) | |||||
: GetAvatarUrl(format, size); | |||||
/// <inheritdoc /> | |||||
public string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | public string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | ||||
=> CDN.GetGuildUserAvatarUrl(Id, GuildId, GuildAvatarId, size, format); | => CDN.GetGuildUserAvatarUrl(Id, GuildId, GuildAvatarId, size, format); | ||||
#endregion | #endregion | ||||
@@ -213,6 +223,8 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
bool IVoiceState.IsStreaming => false; | bool IVoiceState.IsStreaming => false; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
bool IVoiceState.IsVideoing => false; | |||||
/// <inheritdoc /> | |||||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | ||||
#endregion | #endregion | ||||
} | } | ||||
@@ -52,10 +52,16 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
DateTimeOffset? IGuildUser.JoinedAt => null; | DateTimeOffset? IGuildUser.JoinedAt => null; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
string IGuildUser.DisplayName => null; | |||||
/// <inheritdoc /> | |||||
string IGuildUser.Nickname => null; | string IGuildUser.Nickname => null; | ||||
/// <inheritdoc/> | |||||
string IGuildUser.DisplayAvatarId => null; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
string IGuildUser.GuildAvatarId => null; | string IGuildUser.GuildAvatarId => null; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
string IGuildUser.GetDisplayAvatarUrl(ImageFormat format, ushort size) => null; | |||||
/// <inheritdoc /> | |||||
string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => null; | string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => null; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
bool? IGuildUser.IsPending => null; | bool? IGuildUser.IsPending => null; | ||||
@@ -125,6 +131,8 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
bool IVoiceState.IsStreaming => false; | bool IVoiceState.IsStreaming => false; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
bool IVoiceState.IsVideoing => false; | |||||
/// <inheritdoc /> | |||||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | ||||
#endregion | #endregion | ||||
} | } | ||||
@@ -75,7 +75,6 @@ namespace Discord.Net.Queue | |||||
switch (response.StatusCode) | switch (response.StatusCode) | ||||
{ | { | ||||
case (HttpStatusCode)429: | case (HttpStatusCode)429: | ||||
info.ReadRatelimitPayload(response.Stream); | |||||
if (info.IsGlobal) | if (info.IsGlobal) | ||||
{ | { | ||||
#if DEBUG_LIMITS | #if DEBUG_LIMITS | ||||
@@ -88,7 +87,7 @@ namespace Discord.Net.Queue | |||||
#if DEBUG_LIMITS | #if DEBUG_LIMITS | ||||
Debug.WriteLine($"[{id}] (!) 429"); | Debug.WriteLine($"[{id}] (!) 429"); | ||||
#endif | #endif | ||||
UpdateRateLimit(id, request, info, true); | |||||
UpdateRateLimit(id, request, info, true, body:response.Stream); | |||||
} | } | ||||
await _queue.RaiseRateLimitTriggered(Id, info, $"{request.Method} {request.Endpoint}").ConfigureAwait(false); | await _queue.RaiseRateLimitTriggered(Id, info, $"{request.Method} {request.Endpoint}").ConfigureAwait(false); | ||||
continue; //Retry | continue; //Retry | ||||
@@ -316,7 +315,7 @@ namespace Discord.Net.Queue | |||||
} | } | ||||
} | } | ||||
private void UpdateRateLimit(int id, IRequest request, RateLimitInfo info, bool is429, bool redirected = false) | |||||
private void UpdateRateLimit(int id, IRequest request, RateLimitInfo info, bool is429, bool redirected = false, Stream body = null) | |||||
{ | { | ||||
if (WindowCount == 0) | if (WindowCount == 0) | ||||
return; | return; | ||||
@@ -373,7 +372,18 @@ namespace Discord.Net.Queue | |||||
Debug.WriteLine($"[{id}] X-RateLimit-Remaining: " + info.Remaining.Value); | Debug.WriteLine($"[{id}] X-RateLimit-Remaining: " + info.Remaining.Value); | ||||
_semaphore = info.Remaining.Value; | _semaphore = info.Remaining.Value; | ||||
}*/ | }*/ | ||||
if (info.RetryAfter.HasValue) | |||||
if (is429) | |||||
{ | |||||
// use the payload reset after value | |||||
var payload = info.ReadRatelimitPayload(body); | |||||
// fallback on stored ratelimit info when payload is null, https://github.com/discord-net/Discord.Net/issues/2123 | |||||
resetTick = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(payload?.RetryAfter ?? info.ResetAfter?.TotalSeconds ?? 0)); | |||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Reset-After: {info.ResetAfter.Value} ({info.ResetAfter?.TotalMilliseconds} ms)"); | |||||
#endif | |||||
} | |||||
else if (info.RetryAfter.HasValue) | |||||
{ | { | ||||
//RetryAfter is more accurate than Reset, where available | //RetryAfter is more accurate than Reset, where available | ||||
resetTick = DateTimeOffset.UtcNow.AddSeconds(info.RetryAfter.Value); | resetTick = DateTimeOffset.UtcNow.AddSeconds(info.RetryAfter.Value); | ||||
@@ -61,22 +61,18 @@ namespace Discord.Net | |||||
DateTimeOffset.TryParse(temp, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) ? DateTimeOffset.UtcNow - date : (TimeSpan?)null; | DateTimeOffset.TryParse(temp, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) ? DateTimeOffset.UtcNow - date : (TimeSpan?)null; | ||||
} | } | ||||
internal void ReadRatelimitPayload(Stream response) | |||||
internal Ratelimit ReadRatelimitPayload(Stream response) | |||||
{ | { | ||||
try | |||||
if (response != null && response.Length != 0) | |||||
{ | { | ||||
if (response != null && response.Length != 0) | |||||
using (TextReader text = new StreamReader(response)) | |||||
using (JsonReader reader = new JsonTextReader(text)) | |||||
{ | { | ||||
using (TextReader text = new StreamReader(response)) | |||||
using (JsonReader reader = new JsonTextReader(text)) | |||||
{ | |||||
var ratelimit = Discord.Rest.DiscordRestClient.Serializer.Deserialize<Ratelimit>(reader); | |||||
ResetAfter = TimeSpan.FromSeconds(ratelimit.RetryAfter); | |||||
} | |||||
return Discord.Rest.DiscordRestClient.Serializer.Deserialize<Ratelimit>(reader); | |||||
} | } | ||||
} | } | ||||
catch { } | |||||
return null; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -209,7 +209,7 @@ namespace Discord.WebSocket | |||||
/// Sets the <paramref name="activity"/> of the logged-in user. | /// Sets the <paramref name="activity"/> of the logged-in user. | ||||
/// </summary> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// This method sets the <paramref name="activity"/> of the user. | |||||
/// This method sets the <paramref name="activity"/> of the user. | |||||
/// <note type="note"> | /// <note type="note"> | ||||
/// Discord will only accept setting of name and the type of activity. | /// Discord will only accept setting of name and the type of activity. | ||||
/// </note> | /// </note> | ||||
@@ -219,7 +219,7 @@ namespace Discord.WebSocket | |||||
/// </note> | /// </note> | ||||
/// <note type="warning"> | /// <note type="warning"> | ||||
/// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC | /// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC | ||||
/// clients only. | |||||
/// clients only. | |||||
/// </note> | /// </note> | ||||
/// </remarks> | /// </remarks> | ||||
/// <param name="activity">The activity to be set.</param> | /// <param name="activity">The activity to be set.</param> | ||||
@@ -240,7 +240,7 @@ namespace Discord.WebSocket | |||||
/// Creates a guild for the logged-in user who is in less than 10 active guilds. | /// Creates a guild for the logged-in user who is in less than 10 active guilds. | ||||
/// </summary> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// This method creates a new guild on behalf of the logged-in user. | |||||
/// This method creates a new guild on behalf of the logged-in user. | |||||
/// <note type="warning"> | /// <note type="warning"> | ||||
/// Due to Discord's limitation, this method will only work for users that are in less than 10 guilds. | /// Due to Discord's limitation, this method will only work for users that are in less than 10 guilds. | ||||
/// </note> | /// </note> | ||||
@@ -317,8 +317,15 @@ namespace Discord.WebSocket | |||||
=> await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); | => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
=> Task.FromResult<IUser>(GetUser(id)); | |||||
async Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
{ | |||||
var user = GetUser(id); | |||||
if (user is not null || mode == CacheMode.CacheOnly) | |||||
return user; | |||||
return await Rest.GetUserAsync(id, options).ConfigureAwait(false); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | ||||
=> Task.FromResult<IUser>(GetUser(username, discriminator)); | => Task.FromResult<IUser>(GetUser(username, discriminator)); | ||||
@@ -533,8 +533,15 @@ namespace Discord.WebSocket | |||||
=> await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
=> Task.FromResult<IUser>(GetUser(id)); | |||||
async Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
{ | |||||
var user = GetUser(id); | |||||
if (user is not null || mode == CacheMode.CacheOnly) | |||||
return user; | |||||
return await Rest.GetUserAsync(id, options).ConfigureAwait(false); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | ||||
=> Task.FromResult<IUser>(GetUser(username, discriminator)); | => Task.FromResult<IUser>(GetUser(username, discriminator)); | ||||
@@ -78,6 +78,7 @@ namespace Discord.WebSocket | |||||
internal bool AlwaysDownloadDefaultStickers { get; private set; } | internal bool AlwaysDownloadDefaultStickers { get; private set; } | ||||
internal bool AlwaysResolveStickers { get; private set; } | internal bool AlwaysResolveStickers { get; private set; } | ||||
internal bool LogGatewayIntentWarnings { get; private set; } | internal bool LogGatewayIntentWarnings { get; private set; } | ||||
internal bool SuppressUnknownDispatchWarnings { get; private set; } | |||||
internal new DiscordSocketApiClient ApiClient => base.ApiClient; | internal new DiscordSocketApiClient ApiClient => base.ApiClient; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds; | public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds; | ||||
@@ -150,6 +151,7 @@ namespace Discord.WebSocket | |||||
AlwaysDownloadDefaultStickers = config.AlwaysDownloadDefaultStickers; | AlwaysDownloadDefaultStickers = config.AlwaysDownloadDefaultStickers; | ||||
AlwaysResolveStickers = config.AlwaysResolveStickers; | AlwaysResolveStickers = config.AlwaysResolveStickers; | ||||
LogGatewayIntentWarnings = config.LogGatewayIntentWarnings; | LogGatewayIntentWarnings = config.LogGatewayIntentWarnings; | ||||
SuppressUnknownDispatchWarnings = config.SuppressUnknownDispatchWarnings; | |||||
HandlerTimeout = config.HandlerTimeout; | HandlerTimeout = config.HandlerTimeout; | ||||
State = new ClientState(0, 0); | State = new ClientState(0, 0); | ||||
Rest = new DiscordSocketRestClient(config, ApiClient); | Rest = new DiscordSocketRestClient(config, ApiClient); | ||||
@@ -543,7 +545,7 @@ namespace Discord.WebSocket | |||||
if(model == null) | if(model == null) | ||||
return null; | return null; | ||||
if (model.GuildId.IsSpecified) | if (model.GuildId.IsSpecified) | ||||
{ | { | ||||
var guild = State.GetGuild(model.GuildId.Value); | var guild = State.GetGuild(model.GuildId.Value); | ||||
@@ -2128,7 +2130,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
await TimedInvokeAsync(_speakerRemoved, nameof(SpeakerRemoved), stage, guildUser); | await TimedInvokeAsync(_speakerRemoved, nameof(SpeakerRemoved), stage, guildUser); | ||||
} | } | ||||
} | |||||
} | |||||
} | } | ||||
await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, before, after).ConfigureAwait(false); | await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, before, after).ConfigureAwait(false); | ||||
@@ -2520,7 +2522,7 @@ namespace Discord.WebSocket | |||||
} | } | ||||
break; | break; | ||||
case "THREAD_MEMBERS_UPDATE": | |||||
case "THREAD_MEMBERS_UPDATE": | |||||
{ | { | ||||
await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBERS_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBERS_UPDATE)").ConfigureAwait(false); | ||||
@@ -2771,7 +2773,7 @@ namespace Discord.WebSocket | |||||
#region Others | #region Others | ||||
default: | default: | ||||
await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false); | |||||
if(!SuppressUnknownDispatchWarnings) await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false); | |||||
break; | break; | ||||
#endregion | #endregion | ||||
} | } | ||||
@@ -3113,7 +3115,14 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | async Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
=> mode == CacheMode.AllowDownload ? await GetUserAsync(id, options).ConfigureAwait(false) : GetUser(id); | |||||
{ | |||||
var user = GetUser(id); | |||||
if (user is not null || mode == CacheMode.CacheOnly) | |||||
return user; | |||||
return await Rest.GetUserAsync(id, options).ConfigureAwait(false); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | ||||
=> Task.FromResult<IUser>(GetUser(username, discriminator)); | => Task.FromResult<IUser>(GetUser(username, discriminator)); | ||||
@@ -188,6 +188,11 @@ namespace Discord.WebSocket | |||||
/// </summary> | /// </summary> | ||||
public bool LogGatewayIntentWarnings { get; set; } = true; | public bool LogGatewayIntentWarnings { get; set; } = true; | ||||
/// <summary> | |||||
/// Gets or sets whether or not Unknown Dispatch event messages should be logged. | |||||
/// </summary> | |||||
public bool SuppressUnknownDispatchWarnings { get; set; } = true; | |||||
/// <summary> | /// <summary> | ||||
/// Initializes a new instance of the <see cref="DiscordSocketConfig"/> class with the default configuration. | /// Initializes a new instance of the <see cref="DiscordSocketConfig"/> class with the default configuration. | ||||
/// </summary> | /// </summary> | ||||
@@ -18,83 +18,22 @@ namespace Discord.WebSocket | |||||
/// </returns> | /// </returns> | ||||
IReadOnlyCollection<SocketMessage> CachedMessages { get; } | IReadOnlyCollection<SocketMessage> CachedMessages { get; } | ||||
/// <summary> | |||||
/// Sends a message to this message channel. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendMessageAsync"/>. | |||||
/// Please visit its documentation for more details on this method. | |||||
/// </remarks> | |||||
/// <param name="text">The message to be sent.</param> | |||||
/// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | |||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <param name="allowedMentions"> | |||||
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||||
/// </param> | |||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param> | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
/// <param name="stickers">A collection of stickers to send with the message.</param> | |||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
/// <summary> | |||||
/// Sends a file to this message channel with an optional caption. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/>. | |||||
/// Please visit its documentation for more details on this method. | |||||
/// </remarks> | |||||
/// <param name="filePath">The file path of the file.</param> | |||||
/// <param name="text">The message to be sent.</param> | |||||
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich" /> <see cref="Embed" /> to be sent.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
/// <param name="allowedMentions"> | |||||
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||||
/// </param> | |||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param> | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
/// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
/// <summary> | |||||
/// Sends a file to this message channel with an optional caption. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/>. | |||||
/// Please visit its documentation for more details on this method. | |||||
/// </remarks> | |||||
/// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param> | |||||
/// <param name="filename">The name of the attachment.</param> | |||||
/// <param name="text">The message to be sent.</param> | |||||
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | |||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
/// <param name="allowedMentions"> | |||||
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||||
/// </param> | |||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param> | |||||
/// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
/// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null); | |||||
/// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, | |||||
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <inheritdoc cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <inheritdoc cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a cached message from this channel. | /// Gets a cached message from this channel. | ||||
@@ -4,6 +4,7 @@ using System.Collections.Immutable; | |||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Discord.Rest; | |||||
using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
@@ -64,21 +65,44 @@ namespace Discord.WebSocket | |||||
#endregion | #endregion | ||||
#region IGuildChannel | #region IGuildChannel | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||||
=> ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | |||||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, | |||||
RequestOptions options) | |||||
{ | |||||
return mode == CacheMode.AllowDownload | |||||
? ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options) | |||||
: ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
=> Task.FromResult<IGuildUser>(GetUser(id)); | |||||
async Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
{ | |||||
var user = GetUser(id); | |||||
if (user is not null || mode == CacheMode.CacheOnly) | |||||
return user; | |||||
return await ChannelHelper.GetUserAsync(this, Guild, Discord, id, options).ConfigureAwait(false); | |||||
} | |||||
#endregion | #endregion | ||||
#region IChannel | #region IChannel | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||||
{ | |||||
return mode == CacheMode.AllowDownload | |||||
? ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options) | |||||
: ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable(); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
=> Task.FromResult<IUser>(GetUser(id)); | |||||
async Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
{ | |||||
var user = GetUser(id); | |||||
if (user is not null || mode == CacheMode.CacheOnly) | |||||
return user; | |||||
return await ChannelHelper.GetUserAsync(this, Guild, Discord, id, options).ConfigureAwait(false); | |||||
} | |||||
#endregion | #endregion | ||||
} | } | ||||
} | } |
@@ -17,7 +17,7 @@ namespace Discord.WebSocket | |||||
/// <summary> | /// <summary> | ||||
/// Gets when the channel is created. | /// Gets when the channel is created. | ||||
/// </summary> | /// </summary> | ||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||||
public virtual DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a collection of users from the WebSocket cache. | /// Gets a collection of users from the WebSocket cache. | ||||
/// </summary> | /// </summary> | ||||
@@ -139,24 +139,48 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, | |||||
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, isSpoiler, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, | |||||
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, | |||||
components, stickers, options, isSpoiler, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, isSpoiler, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, | |||||
messageReference, components, stickers, options, isSpoiler, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, | |||||
messageReference, components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) | |||||
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds); | |||||
/// <exception cref="ArgumentException">The only valid <see cref="MessageFlags"/> are <see cref="MessageFlags.SuppressEmbeds"/> and <see cref="MessageFlags.None"/>.</exception> | |||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS = false, | |||||
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, | |||||
messageReference, components, stickers, options, embeds, flags); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | ||||
@@ -255,20 +279,37 @@ namespace Discord.WebSocket | |||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | ||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, | |||||
RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, | |||||
components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, | |||||
Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, | |||||
components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, | |||||
Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, | |||||
stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> attachments, string text, | |||||
bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, | |||||
MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, Embed[] embeds) | |||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, | |||||
AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, | |||||
ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
#endregion | #endregion | ||||
#region IChannel | #region IChannel | ||||