Browse Source

Merge branch 'dev' of https://github.com/discord-net/Discord.Net into feature/component-typeconverters

pull/2169/head
Cenngo 3 years ago
parent
commit
7571f93a5b
100 changed files with 1435 additions and 443 deletions
  1. +10
    -1
      .github/ISSUE_TEMPLATE/bugreport.yml
  2. +48
    -0
      CHANGELOG.md
  3. +1
    -1
      Discord.Net.targets
  4. +1
    -1
      docs/docfx.json
  5. +5
    -13
      docs/faq/basics/dependency-injection.md
  6. +26
    -12
      docs/faq/basics/getting-started.md
  7. BIN
      docs/faq/basics/images/link.png
  8. BIN
      docs/faq/basics/images/permissions.png
  9. BIN
      docs/faq/basics/images/scopes.png
  10. +0
    -0
      docs/faq/basics/samples/DI.cs
  11. +6
    -3
      docs/faq/basics/samples/missing-dep.cs
  12. +31
    -12
      docs/faq/int_framework/framework.md
  13. +14
    -35
      docs/faq/int_framework/general.md
  14. +0
    -0
      docs/faq/int_framework/images/scope.png
  15. +45
    -0
      docs/faq/int_framework/manual.md
  16. +6
    -0
      docs/faq/int_framework/samples/interactionsyncing.cs
  17. +8
    -0
      docs/faq/int_framework/samples/propertyinjection.cs
  18. +0
    -0
      docs/faq/int_framework/samples/registerint.cs
  19. +19
    -2
      docs/faq/misc/legacy.md
  20. +6
    -11
      docs/faq/text_commands/general.md
  21. +0
    -0
      docs/faq/text_commands/samples/Remainder.cs
  22. +0
    -0
      docs/faq/text_commands/samples/runmode-cmdattrib.cs
  23. +0
    -0
      docs/faq/text_commands/samples/runmode-cmdconfig.cs
  24. +13
    -9
      docs/faq/toc.yml
  25. +1
    -1
      docs/guides/int_basics/application-commands/slash-commands/choice-slash-command.md
  26. +2
    -2
      docs/guides/int_basics/application-commands/slash-commands/creating-slash-commands.md
  27. +1
    -1
      docs/guides/int_basics/message-components/advanced.md
  28. +5
    -5
      docs/guides/int_basics/message-components/text-input.md
  29. +15
    -0
      docs/guides/int_framework/intro.md
  30. +37
    -0
      docs/guides/int_framework/samples/intro/complexparams.cs
  31. +1
    -1
      docs/guides/int_framework/samples/intro/context.cs
  32. +5
    -3
      docs/guides/int_framework/samples/intro/groupattribute.cs
  33. +61
    -0
      docs/guides/other_libs/efcore.md
  34. BIN
      docs/guides/other_libs/images/serilog_output.png
  35. +36
    -0
      docs/guides/other_libs/samples/ConfiguringSerilog.cs
  36. +9
    -0
      docs/guides/other_libs/samples/DbContextDepInjection.cs
  37. +19
    -0
      docs/guides/other_libs/samples/DbContextSample.cs
  38. +20
    -0
      docs/guides/other_libs/samples/InteractionModuleDISample.cs
  39. +1
    -0
      docs/guides/other_libs/samples/LogDebugSample.cs
  40. +15
    -0
      docs/guides/other_libs/samples/ModifyLogMethod.cs
  41. +45
    -0
      docs/guides/other_libs/serilog.md
  42. +7
    -1
      docs/guides/toc.yml
  43. +11
    -1
      samples/InteractionFramework/ExampleEnum.cs
  44. +1
    -0
      src/Discord.Net.Commands/CommandService.cs
  45. +12
    -0
      src/Discord.Net.Core/CDN.cs
  46. +5
    -0
      src/Discord.Net.Core/DiscordErrorCode.cs
  47. +1
    -1
      src/Discord.Net.Core/Entities/Channels/IChannel.cs
  48. +10
    -5
      src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
  49. +17
    -0
      src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
  50. +5
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs
  51. +2
    -0
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  52. +13
    -0
      src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
  53. +3
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
  54. +19
    -4
      src/Discord.Net.Core/Entities/Messages/TimestampTag.cs
  55. +31
    -3
      src/Discord.Net.Core/Entities/Users/IGuildUser.cs
  56. +7
    -0
      src/Discord.Net.Core/Entities/Users/IVoiceState.cs
  57. +30
    -0
      src/Discord.Net.Interactions/Attributes/ComplexParameterAttribute.cs
  58. +10
    -0
      src/Discord.Net.Interactions/Attributes/ComplexParameterCtorAttribute.cs
  59. +25
    -0
      src/Discord.Net.Interactions/Attributes/EnumChoiceAttribute.cs
  60. +56
    -1
      src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
  61. +66
    -2
      src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs
  62. +2
    -1
      src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs
  63. +25
    -1
      src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs
  64. +28
    -2
      src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs
  65. +1
    -3
      src/Discord.Net.Interactions/InteractionService.cs
  66. +36
    -0
      src/Discord.Net.Interactions/Results/ParseResult.cs
  67. +5
    -2
      src/Discord.Net.Interactions/TypeConverters/SlashCommands/EnumConverter.cs
  68. +3
    -3
      src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
  69. +2
    -0
      src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs
  70. +6
    -0
      src/Discord.Net.Rest/API/Common/ThreadMetadata.cs
  71. +2
    -0
      src/Discord.Net.Rest/API/Common/VoiceState.cs
  72. +2
    -0
      src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs
  73. +3
    -0
      src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs
  74. +2
    -0
      src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs
  75. +1
    -1
      src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
  76. +47
    -11
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  77. +8
    -78
      src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs
  78. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
  79. +59
    -20
      src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
  80. +69
    -21
      src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
  81. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
  82. +60
    -25
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  83. +13
    -3
      src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs
  84. +10
    -2
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  85. +5
    -3
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  86. +8
    -0
      src/Discord.Net.Rest/Entities/Guilds/RestGuildEvent.cs
  87. +0
    -1
      src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
  88. +2
    -0
      src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs
  89. +13
    -1
      src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
  90. +8
    -0
      src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs
  91. +14
    -4
      src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs
  92. +7
    -11
      src/Discord.Net.Rest/Net/RateLimitInfo.cs
  93. +12
    -5
      src/Discord.Net.WebSocket/BaseSocketClient.cs
  94. +9
    -2
      src/Discord.Net.WebSocket/DiscordShardedClient.cs
  95. +14
    -5
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  96. +5
    -0
      src/Discord.Net.WebSocket/DiscordSocketConfig.cs
  97. +16
    -77
      src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs
  98. +31
    -7
      src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs
  99. +1
    -1
      src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs
  100. +61
    -20
      src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs

+ 10
- 1
.github/ISSUE_TEMPLATE/bugreport.yml View File

@@ -18,7 +18,8 @@ body:
attributes:
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.
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:
- label: I verified the issue was caused by Discord.Net.
required: true
@@ -75,3 +76,11 @@ body:
```
validations:
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

+ 48
- 0
CHANGELOG.md View File

@@ -1,5 +1,53 @@
# 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

### Added


+ 1
- 1
Discord.Net.targets View File

@@ -1,6 +1,6 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VersionPrefix>3.3.0</VersionPrefix>
<VersionPrefix>3.4.0</VersionPrefix>
<LangVersion>latest</LangVersion>
<Authors>Discord.Net Contributors</Authors>
<PackageTags>discord;discordapp</PackageTags>


+ 1
- 1
docs/docfx.json View File

@@ -60,7 +60,7 @@
"overwrite": "_overwrites/**/**.md",
"globalMetadata": {
"_appTitle": "Discord.Net Documentation",
"_appFooter": "Discord.Net (c) 2015-2022 3.3.0",
"_appFooter": "Discord.Net (c) 2015-2022 3.4.0",
"_enableSearch": true,
"_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg",
"_appFaviconPath": "favicon.ico"


docs/faq/commands/dependency-injection.md → docs/faq/basics/dependency-injection.md View File

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

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.

## 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
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
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.

@@ -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
[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,
dependency MyExternalDependency was not found.`, you may have
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`
in its constructor, the `DatabaseService` must be present in the
[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)]

[IServiceProvider]: xref:System.IServiceProvider
[CommandService]: xref:Discord.Commands.CommandService

+ 26
- 12
docs/faq/basics/getting-started.md View File

@@ -11,18 +11,32 @@ introduction to the Discord API ecosystem.

## 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.

![Scopes](images/scopes.png)

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.

![Permissions](images/permissions.png)

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.

![Invite](images/link.png)

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?



BIN
docs/faq/basics/images/link.png View File

Before After
Width: 1236  |  Height: 72  |  Size: 5.1 KiB

BIN
docs/faq/basics/images/permissions.png View File

Before After
Width: 1242  |  Height: 606  |  Size: 59 KiB

BIN
docs/faq/basics/images/scopes.png View File

Before After
Width: 1240  |  Height: 321  |  Size: 28 KiB

docs/faq/commands/samples/DI.cs → docs/faq/basics/samples/DI.cs View File


docs/faq/commands/samples/missing-dep.cs → docs/faq/basics/samples/missing-dep.cs View File

@@ -11,8 +11,8 @@ public class CommandHandler
public CommandHandler(DiscordSocketClient client)
{
_services = new ServiceCollection()
.AddService<CommandService>()
.AddService(client)
.AddSingleton<CommandService>()
.AddSingleton(client)
// We are missing DatabaseService!
.BuildServiceProvider();
}
@@ -25,5 +25,8 @@ public class CommandHandler
// registered in this instance of _services.
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
// ...

// The same approach applies to the interaction service.
// Make sure to resolve these issues!
}
}
}

docs/faq/commands/interaction.md → docs/faq/int_framework/framework.md View File

@@ -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?

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?

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?

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.

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.

## 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
[Interactions FAQ]: xref: FAQ.Basics.Interactions
[InteractionServiceConfig]: xref:Discord.Interactions.InteractionServiceConfig
[documentation about preconditions]: xref: Guides.ChatCommands.Preconditions

docs/faq/basics/interactions.md → docs/faq/int_framework/general.md View File

@@ -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?

@@ -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!'

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]
> 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?

@@ -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?

![Scope check](images/scope.png)

## 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?

If you are registering your commands for the first time, it is required to create them once.

docs/faq/basics/images/scope.png → docs/faq/int_framework/images/scope.png View File


+ 45
- 0
docs/faq/int_framework/manual.md View File

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

![Scope check](images/scope.png)

## 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.

+ 6
- 0
docs/faq/int_framework/samples/interactionsyncing.cs View File

@@ -0,0 +1,6 @@
DiscordSocketConfig config = new()
{
UseInteractionSnowflakeDate = false
};

DiscordSocketclient client = new(config);

+ 8
- 0
docs/faq/int_framework/samples/propertyinjection.cs View File

@@ -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; }
}

docs/faq/basics/samples/registerint.cs → docs/faq/int_framework/samples/registerint.cs View File


+ 19
- 2
docs/faq/misc/legacy.md View File

@@ -8,15 +8,32 @@ title: Questions about Legacy Versions
This section refers to legacy library-related questions that do not
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.

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
versions and will likely not be fixed in the version branch due to
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



docs/faq/commands/general.md → docs/faq/text_commands/general.md View File

@@ -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
@@ -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?

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
guild or channel (*e.g., `GuildPermission.Administrator`,
`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
[Preconditions Addons]: https://github.com/Joe4evr/Discord.Addons/tree/master/src/Discord.Addons.Preconditions

## Why am I getting an error about `Assembly.GetEntryAssembly`?


docs/faq/commands/samples/Remainder.cs → docs/faq/text_commands/samples/Remainder.cs View File


docs/faq/commands/samples/runmode-cmdattrib.cs → docs/faq/text_commands/samples/runmode-cmdattrib.cs View File


docs/faq/commands/samples/runmode-cmdconfig.cs → docs/faq/text_commands/samples/runmode-cmdconfig.cs View File


+ 13
- 9
docs/faq/toc.yml View File

@@ -6,15 +6,19 @@
topicUid: FAQ.Basics.BasicOp
- name: Client Basics
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
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
topicUid: FAQ.Legacy

+ 1
- 1
docs/guides/int_basics/application-commands/slash-commands/choice-slash-command.md View File

@@ -27,7 +27,7 @@ private async Task Client_Ready()
.AddChoice("Lovely", 4)
.AddChoice("Excellent!", 5)
.WithType(ApplicationCommandOptionType.Integer)
).Build();
);

try
{


+ 2
- 2
docs/guides/int_basics/application-commands/slash-commands/creating-slash-commands.md View File

@@ -70,14 +70,14 @@ public async Task Client_Ready()
// Let's do our global command
var globalCommand = new SlashCommandBuilder();
globalCommand.WithName("first-global-command");
globalCommand.WithDescription("This is my frist global slash command");
globalCommand.WithDescription("This is my first global slash command");

try
{
// Now that we have our builder, we can call the CreateApplicationCommandAsync method to make our slash command.
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());
// 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.


+ 1
- 1
docs/guides/int_basics/message-components/advanced.md View File

@@ -43,7 +43,7 @@ var components = new ComponentBuilder()
.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;
```



+ 5
- 5
docs/guides/int_basics/message-components/text-input.md View File

@@ -35,11 +35,11 @@ and min/max length of the input:
var tb = new TextInputBuilder()
.WithLabel("Labeled")
.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.");
```

![more advanced text input](images/image9.png)


+ 15
- 0
docs/guides/int_framework/intro.md View File

@@ -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].

#### 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

A valid User Command must have the following structure:


+ 37
- 0
docs/guides/int_framework/samples/intro/complexparams.cs View File

@@ -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
- 1
docs/guides/int_framework/samples/intro/context.cs View File

@@ -1,7 +1,7 @@
discordClient.ButtonExecuted += async (interaction) =>
{
var ctx = new SocketInteractionContext<SocketMessageComponent>(discordClient, interaction);
await _interactionService.ExecuteAsync(ctx, serviceProvider);
await _interactionService.ExecuteCommandAsync(ctx, serviceProvider);
};

public class MessageComponentModule : InteractionModuleBase<SocketInteractionContext<SocketMessageComponent>>


+ 5
- 3
docs/guides/int_framework/samples/intro/groupattribute.cs View File

@@ -1,16 +1,18 @@
[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
{
Cat,
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")]


+ 61
- 0
docs/guides/other_libs/efcore.md View File

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

BIN
docs/guides/other_libs/images/serilog_output.png View File

Before After
Width: 1456  |  Height: 141  |  Size: 40 KiB

+ 36
- 0
docs/guides/other_libs/samples/ConfiguringSerilog.cs View File

@@ -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);
}
}

+ 9
- 0
docs/guides/other_libs/samples/DbContextDepInjection.cs View File

@@ -0,0 +1,9 @@
private static ServiceProvider ConfigureServices()
{
return new ServiceCollection()
.AddDbContext<ApplicationDbContext>(
options => options.UseNpgsql("Your connection string")
)
[...]
.BuildServiceProvider();
}

+ 19
- 0
docs/guides/other_libs/samples/DbContextSample.cs View File

@@ -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; }
}

+ 20
- 0
docs/guides/other_libs/samples/InteractionModuleDISample.cs View File

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

...
}
}

+ 1
- 0
docs/guides/other_libs/samples/LogDebugSample.cs View File

@@ -0,0 +1 @@
Log.Debug("Your log message, with {Variables}!", 10); // This will output "[21:51:00 DBG] Your log message, with 10!"

+ 15
- 0
docs/guides/other_libs/samples/ModifyLogMethod.cs View File

@@ -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;
}

+ 45
- 0
docs/guides/other_libs/serilog.md View File

@@ -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
![Serilog output](images/serilog_output.png)

## 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.

+ 7
- 1
docs/guides/toc.yml View File

@@ -95,7 +95,7 @@
topicUid: Guides.MessageComponents.TextInputs
- name: Advanced Concepts
topicUid: Guides.MessageComponents.Advanced
- name: Modal Basics
- name: Modal Basics
items:
- name: Introduction
topicUid: Guides.Modals.Intro
@@ -109,6 +109,12 @@
topicUid: Guides.GuildEvents.GettingUsers
- name: Modifying Events
topicUid: Guides.GuildEvents.Modifying
- name: Working with other libraries
items:
- name: Serilog
topicUid: Guides.OtherLibs.Serilog
- name: EFCore
topicUid: Guides.OtherLibs.EFCore
- name: Emoji
topicUid: Guides.Emoji
- name: Voice


+ 11
- 1
samples/InteractionFramework/ExampleEnum.cs View File

@@ -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
{
public enum ExampleEnum
@@ -5,6 +13,8 @@ namespace InteractionFramework
First,
Second,
Third,
Fourth
Fourth,
[ChoiceDisplay("Twenty First")]
TwentyFirst
}
}

+ 1
- 0
src/Discord.Net.Commands/CommandService.cs View File

@@ -557,6 +557,7 @@ namespace Discord.Commands
if (matchResult.Pipeline is PreconditionResult preconditionResult)
{
await _commandExecutedEvent.InvokeAsync(matchResult.Match.Value.Command, context, preconditionResult).ConfigureAwait(false);
return preconditionResult;
}

return matchResult;


+ 12
- 0
src/Discord.Net.Core/CDN.cs View File

@@ -208,6 +208,18 @@ namespace Discord
public static string GetStickerUrl(ulong stickerId, StickerFormatType format = StickerFormatType.Png)
=> $"{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)
{
return format switch


+ 5
- 0
src/Discord.Net.Core/DiscordErrorCode.cs View File

@@ -96,9 +96,11 @@ namespace Discord
#endregion

#region General Request Errors (40XXX)
MaximumNumberOfEditsReached = 30046,
TokenUnauthorized = 40001,
InvalidVerification = 40002,
OpeningDMTooFast = 40003,
SendMessagesHasBeenTemporarilyDisabled = 40004,
RequestEntityTooLarge = 40005,
FeatureDisabled = 40006,
UserBanned = 40007,
@@ -108,6 +110,7 @@ namespace Discord
#endregion

#region Action Preconditions/Checks (50XXX)
InteractionHasAlreadyBeenAcknowledged = 40060,
MissingPermissions = 50001,
InvalidAccountType = 50002,
CannotExecuteForDM = 50003,
@@ -141,12 +144,14 @@ namespace Discord
InvalidFileUpload = 50046,
CannotSelfRedeemGift = 50054,
InvalidGuild = 50055,
InvalidMessageType = 50068,
PaymentSourceRequiredForGift = 50070,
CannotDeleteRequiredCommunityChannel = 50074,
InvalidSticker = 50081,
CannotExecuteOnArchivedThread = 50083,
InvalidThreadNotificationSettings = 50084,
BeforeValueEarlierThanThreadCreation = 50085,
CommunityServerChannelsMustBeTextChannels = 50086,
ServerLocaleUnavailable = 50095,
ServerRequiresMonetization = 50097,
ServerRequiresBoosts = 50101,


+ 1
- 1
src/Discord.Net.Core/Entities/Channels/IChannel.cs View File

@@ -21,7 +21,7 @@ namespace Discord
/// </summary>
/// <remarks>
/// <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
/// collection.
/// </note>


+ 10
- 5
src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs View File

@@ -31,11 +31,12 @@ namespace Discord
/// <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>
/// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </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>
/// Sends a file to this message channel with an optional caption.
/// </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="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="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </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>
/// Sends a file to this message channel with an optional caption.
/// </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="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="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </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>
/// Sends a file to this message channel with an optional caption.
/// </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="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="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </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>
/// Sends a collection of files to this message channel.
/// </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="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="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </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>
/// Gets a message from this message channel.


+ 17
- 0
src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs View File

@@ -48,6 +48,23 @@ namespace Discord
/// </summary>
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>
/// Joins the current thread.
/// </summary>


+ 5
- 0
src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs View File

@@ -54,5 +54,10 @@ namespace Discord
/// Gets or sets the status of the event.
/// </summary>
public Optional<GuildScheduledEventStatus> Status { get; set; }

/// <summary>
/// Gets or sets the banner image of the event.
/// </summary>
public Optional<Image?> CoverImage { get; set; }
}
}

+ 2
- 0
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -1105,6 +1105,7 @@ namespace Discord
/// </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="coverImage">The optional banner image for the event.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous create operation.
@@ -1118,6 +1119,7 @@ namespace Discord
DateTimeOffset? endTime = null,
ulong? channelId = null,
string location = null,
Image? coverImage = null,
RequestOptions options = null);

/// <summary>


+ 13
- 0
src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs View File

@@ -39,6 +39,11 @@ namespace Discord
/// </remarks>
string Description { get; }

/// <summary>
/// Gets the banner asset id of the event.
/// </summary>
string CoverImageId { get; }

/// <summary>
/// Gets the start time of the event.
/// </summary>
@@ -80,6 +85,14 @@ namespace Discord
/// </summary>
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>
/// Starts the event.
/// </summary>


+ 3
- 0
src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs View File

@@ -613,6 +613,9 @@ namespace Discord
if (!(string.IsNullOrEmpty(Url) ^ string.IsNullOrEmpty(CustomId)))
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 (string.IsNullOrEmpty(Url))


+ 19
- 4
src/Discord.Net.Core/Entities/Messages/TimestampTag.cs View File

@@ -15,7 +15,7 @@ namespace Discord
/// <summary>
/// Gets or sets the time for this timestamp tag.
/// </summary>
public DateTime Time { get; set; }
public DateTimeOffset Time { get; set; }

/// <summary>
/// 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>&lt;t:1625944201:f&gt;</code></returns>
public override string ToString()
{
return $"<t:{((DateTimeOffset)Time).ToUnixTimeSeconds()}:{(char)Style}>";
return $"<t:{Time.ToUnixTimeSeconds()}:{(char)Style}>";
}

/// <summary>
/// Creates a new timestamp tag with the specified datetime object.
/// Creates a new timestamp tag with the specified <see cref="DateTime"/> object.
/// </summary>
/// <param name="time">The time of this timestamp tag.</param>
/// <param name="style">The style for this timestamp tag.</param>
@@ -43,5 +43,20 @@ namespace Discord
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
};
}
}
}
}

+ 31
- 3
src/Discord.Net.Core/Entities/Users/IGuildUser.cs View File

@@ -18,6 +18,13 @@ namespace Discord
/// </returns>
DateTimeOffset? JoinedAt { get; }
/// <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.
/// </summary>
/// <returns>
@@ -25,7 +32,15 @@ namespace Discord
/// </returns>
string Nickname { get; }
/// <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>
/// <returns>
/// The users guild avatar hash if they have one; otherwise <see langword="null"/>.
@@ -119,16 +134,29 @@ namespace Discord
/// </summary>
/// <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
/// (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>
/// <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>
/// <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>
string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128);
/// <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.
/// </summary>
/// <param name="reason">The reason for the kick which will be recorded in the audit log.</param>


+ 7
- 0
src/Discord.Net.Core/Entities/Users/IVoiceState.cs View File

@@ -65,6 +65,13 @@ namespace Discord
/// </returns>
bool IsStreaming { get; }
/// <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.
/// </summary>
DateTimeOffset? RequestToSpeakTimestamp { get; }


+ 30
- 0
src/Discord.Net.Interactions/Attributes/ComplexParameterAttribute.cs View File

@@ -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;
}
}
}

+ 10
- 0
src/Discord.Net.Interactions/Attributes/ComplexParameterCtorAttribute.cs View File

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

+ 25
- 0
src/Discord.Net.Interactions/Attributes/EnumChoiceAttribute.cs View File

@@ -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;
}
}
}

+ 56
- 1
src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs View File

@@ -396,7 +396,6 @@ namespace Discord.Interactions.Builders
builder.Description = paramInfo.Name;
builder.IsRequired = !paramInfo.IsOptional;
builder.DefaultValue = paramInfo.DefaultValue;
builder.SetParameterType(paramType, services);

foreach (var attribute in attributes)
{
@@ -434,12 +433,32 @@ namespace Discord.Interactions.Builders
case MinValueAttribute minValue:
builder.MinValue = minValue.Value;
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:
builder.AddAttributes(attribute);
break;
}
}

builder.SetParameterType(paramType, services);

// Replace pascal casings with '-'
builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower();
}
@@ -613,5 +632,41 @@ namespace Discord.Interactions.Builders
propertyInfo.SetMethod?.IsStatic == false &&
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();
}
}
}
}

+ 66
- 2
src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Discord.Interactions.Builders
{
@@ -10,6 +11,7 @@ namespace Discord.Interactions.Builders
{
private readonly List<ParameterChoice> _choices = new();
private readonly List<ChannelType> _channelTypes = new();
private readonly List<SlashCommandParameterBuilder> _complexParameterFields = new();

/// <summary>
/// Gets or sets the description of this parameter.
@@ -36,6 +38,11 @@ namespace Discord.Interactions.Builders
/// </summary>
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>
/// Gets or sets whether this parameter should be configured for Autocomplete Interactions.
/// </summary>
@@ -46,6 +53,16 @@ namespace Discord.Interactions.Builders
/// </summary>
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>
/// Gets or sets the <see cref="IAutocompleteHandler"/> of this parameter.
/// </summary>
@@ -60,7 +77,14 @@ namespace Discord.Interactions.Builders
/// <param name="command">Parent command of this parameter.</param>
/// <param name="name">Name of this command.</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>
/// Sets <see cref="Description"/>.
@@ -168,7 +192,47 @@ namespace Discord.Interactions.Builders
public SlashCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null)
{
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;
}



+ 2
- 1
src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs View File

@@ -31,7 +31,7 @@ namespace Discord.Interactions
private readonly ExecuteCallback _action;
private readonly ILookup<string, PreconditionAttribute> _groupedPreconditions;

internal IReadOnlyDictionary<string, TParameter> _parameterDictionary;
internal IReadOnlyDictionary<string, TParameter> _parameterDictionary { get; }

/// <inheritdoc/>
public ModuleInfo Module { get; }
@@ -81,6 +81,7 @@ namespace Discord.Interactions

_action = builder.Callback;
_groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal);
_parameterDictionary = Parameters?.ToDictionary(x => x.Name, x => x).ToImmutableDictionary();
}

/// <inheritdoc/>


+ 25
- 1
src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs View File

@@ -13,6 +13,8 @@ namespace Discord.Interactions
/// </summary>
public class SlashCommandInfo : CommandInfo<SlashCommandParameterInfo>, IApplicationCommandInfo
{
internal IReadOnlyDictionary<string, SlashCommandParameterInfo> _flattenedParameterDictionary { get; }

/// <summary>
/// Gets the command description that will be displayed on Discord.
/// </summary>
@@ -30,11 +32,23 @@ namespace Discord.Interactions
/// <inheritdoc/>
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)
{
Description = builder.Description;
DefaultPermission = builder.DefaultPermission;
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/>
@@ -74,7 +88,7 @@ namespace Discord.Interactions
}
return await RunAsync(context, args, services).ConfigureAwait(false);
}
catch (Exception ex)
else
{
return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false);
}
@@ -127,5 +141,15 @@ namespace Discord.Interactions
else
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;
}
}
}

+ 28
- 2
src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs View File

@@ -1,13 +1,25 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

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>
/// Represents the parameter info class for <see cref="SlashCommandInfo"/> commands.
/// </summary>
public class SlashCommandParameterInfo : CommandParameterInfo
{
internal readonly ComplexParameterInitializer _complexParameterInitializer;

/// <inheritdoc/>
public new SlashCommandInfo Command => base.Command as SlashCommandInfo;

@@ -43,9 +55,14 @@ namespace Discord.Interactions
public bool IsAutocomplete { get; }

/// <summary>
/// Gets the Discord option type this parameter represents.
/// Gets whether this type should be treated as a complex parameter.
/// </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>
/// Gets the parameter choices of this Slash Application Command parameter.
@@ -57,6 +74,11 @@ namespace Discord.Interactions
/// </summary>
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)
{
TypeConverter = builder.TypeConverter;
@@ -64,9 +86,13 @@ namespace Discord.Interactions
Description = builder.Description;
MaxValue = builder.MaxValue;
MinValue = builder.MinValue;
IsComplexParameter = builder.IsComplexParameter;
IsAutocomplete = builder.Autocomplete;
Choices = builder.Choices.ToImmutableArray();
ChannelTypes = builder.ChannelTypes.ToImmutableArray();
ComplexParameterFields = builder.ComplexParameterFields?.Select(x => x.Build(command)).ToImmutableArray();

_complexParameterInitializer = builder.ComplexParameterInitializer;
}
}
}

+ 1
- 3
src/Discord.Net.Interactions/InteractionService.cs View File

@@ -765,9 +765,7 @@ namespace Discord.Interactions

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);
}
}


+ 36
- 0
src/Discord.Net.Interactions/Results/ParseResult.cs View File

@@ -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}";
}
}

+ 5
- 2
src/Discord.Net.Interactions/TypeConverters/SlashCommands/EnumConverter.cs View File

@@ -2,6 +2,7 @@ using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Discord.Interactions
@@ -27,12 +28,14 @@ namespace Discord.Interactions
var choices = new List<ApplicationCommandOptionChoiceProperties>();

foreach (var member in members)
{
var displayValue = member.GetCustomAttribute<ChoiceDisplayAttribute>()?.Name ?? member.Name;
choices.Add(new ApplicationCommandOptionChoiceProperties
{
Name = member.Name,
Name = displayValue,
Value = member.Name
});
}
properties.Choices = choices;
}
}


+ 3
- 3
src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs View File

@@ -13,7 +13,7 @@ namespace Discord.Interactions
{
Name = parameterInfo.Name,
Description = parameterInfo.Description,
Type = parameterInfo.DiscordOptionType,
Type = parameterInfo.DiscordOptionType.Value,
IsRequired = parameterInfo.IsRequired,
Choices = parameterInfo.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties
{
@@ -46,7 +46,7 @@ namespace Discord.Interactions
if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount)
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;
}
@@ -58,7 +58,7 @@ namespace Discord.Interactions
Description = commandInfo.Description,
Type = ApplicationCommandOptionType.SubCommand,
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)


+ 2
- 0
src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs View File

@@ -39,5 +39,7 @@ namespace Discord.API
public Optional<User> Creator { get; set; }
[JsonProperty("user_count")]
public Optional<int> UserCount { get; set; }
[JsonProperty("image")]
public string Image { get; set; }
}
}

+ 6
- 0
src/Discord.Net.Rest/API/Common/ThreadMetadata.cs View File

@@ -16,5 +16,11 @@ namespace Discord.API

[JsonProperty("locked")]
public Optional<bool> Locked { get; set; }

[JsonProperty("invitable")]
public Optional<bool> Invitable { get; set; }

[JsonProperty("create_timestamp")]
public Optional<DateTimeOffset> CreatedAt { get; set; }
}
}

+ 2
- 0
src/Discord.Net.Rest/API/Common/VoiceState.cs View File

@@ -28,6 +28,8 @@ namespace Discord.API
public bool Suppress { get; set; }
[JsonProperty("self_stream")]
public bool SelfStream { get; set; }
[JsonProperty("self_video")]
public bool SelfVideo { get; set; }
[JsonProperty("request_to_speak_timestamp")]
public Optional<DateTimeOffset?> RequestToSpeakTimestamp { get; set; }
}


+ 2
- 0
src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs View File

@@ -25,5 +25,7 @@ namespace Discord.API.Rest
public Optional<string> Description { get; set; }
[JsonProperty("entity_type")]
public GuildScheduledEventType Type { get; set; }
[JsonProperty("image")]
public Optional<Image> Image { get; set; }
}
}

+ 3
- 0
src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs View File

@@ -28,6 +28,9 @@ namespace Discord.API.Rest

[JsonProperty("sticker_ids")]
public Optional<ulong[]> Stickers { get; set; }
[JsonProperty("flags")]
public Optional<MessageFlags> Flags { get; set; }

public CreateMessageParams(string content)
{


+ 2
- 0
src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs View File

@@ -27,5 +27,7 @@ namespace Discord.API.Rest
public Optional<GuildScheduledEventType> Type { get; set; }
[JsonProperty("status")]
public Optional<GuildScheduledEventStatus> Status { get; set; }
[JsonProperty("image")]
public Optional<Image?> Image { get; set; }
}
}

+ 1
- 1
src/Discord.Net.Rest/API/Rest/UploadFileParams.cs View File

@@ -51,7 +51,7 @@ namespace Discord.API.Rest
if (Stickers.IsSpecified)
payload["sticker_ids"] = Stickers.Value;
if (Flags.IsSpecified)
payload["flags"] = Flags;
payload["flags"] = Flags.Value;

List<object> attachments = new();



+ 47
- 11
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

@@ -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="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,
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>();
if (embed != null)
@@ -298,6 +300,10 @@ namespace Discord.Rest
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)
{
IsTTS = isTTS,
@@ -305,7 +311,8 @@ namespace Discord.Rest
AllowedMentions = allowedMentions?.ToModel(),
MessageReference = messageReference?.ToModel(),
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);
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="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="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,
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);
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="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,
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))
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="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,
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,
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>();
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?.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.");
foreach(var attachment in attachments)
{
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)
{
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);
return RestUserMessage.Create(client, channel, client.CurrentUser, model);
}


+ 8
- 78
src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs View File

@@ -9,84 +9,14 @@ namespace Discord.Rest
/// </summary>
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>
/// Gets a message from this message channel.


+ 1
- 1
src/Discord.Net.Rest/Entities/Channels/RestChannel.cs View File

@@ -13,7 +13,7 @@ namespace Discord.Rest
{
#region RestChannel
/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
public virtual DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);

internal RestChannel(BaseDiscordClient discord, ulong id)
: base(discord, id)


+ 59
- 20
src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs View File

@@ -94,8 +94,12 @@ namespace Discord.Rest

/// <inheritdoc />
/// <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 />
/// <exception cref="ArgumentException">
@@ -122,22 +126,39 @@ namespace Discord.Rest
/// <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="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 />
/// <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 />
/// <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 />
/// <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 />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
@@ -219,20 +240,38 @@ namespace Discord.Rest
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).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)
=> 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 />
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 />
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 />
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 />
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

#region IChannel


+ 69
- 21
src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs View File

@@ -104,8 +104,12 @@ namespace Discord.Rest

/// <inheritdoc />
/// <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 />
/// <exception cref="ArgumentException">
@@ -132,20 +136,40 @@ namespace Discord.Rest
/// <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="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 />
/// <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 />
/// <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 />
/// <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 />
public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
@@ -197,17 +221,41 @@ namespace Discord.Rest
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> 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

#region IAudioChannel


+ 1
- 1
src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs View File

@@ -227,7 +227,7 @@ namespace Discord.Rest

/// <inheritdoc />
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 />
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IGuildUser>(null); //Overridden in Text/Voice


+ 60
- 25
src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs View File

@@ -103,8 +103,12 @@ namespace Discord.Rest

/// <inheritdoc />
/// <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 />
/// <exception cref="ArgumentException">
@@ -131,23 +135,42 @@ namespace Discord.Rest
/// <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="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 />
/// <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 />
/// <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 />
/// <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 />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
@@ -261,7 +284,7 @@ namespace Discord.Rest
/// The duration on which this thread archives after.
/// <para>
/// <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>.
/// </para>
/// </param>
@@ -332,24 +355,37 @@ namespace Discord.Rest
=> await GetPinnedMessagesAsync(options).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)
=> 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 />
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 />
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 />
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 />
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

#region IGuildChannel
@@ -364,10 +400,9 @@ namespace Discord.Rest
/// <inheritdoc />
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



+ 13
- 3
src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs View File

@@ -34,17 +34,26 @@ namespace Discord.Rest
/// <inheritdoc/>
public int MessageCount { get; private set; }

/// <inheritdoc/>
public bool? IsInvitable { get; private set; }

/// <inheritdoc cref="IThreadChannel.CreatedAt"/>
public override DateTimeOffset CreatedAt { get; }

/// <summary>
/// Gets the parent text channel id.
/// </summary>
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)
{
var entity = new RestThreadChannel(discord, guild, model.Id);
var entity = new RestThreadChannel(discord, guild, model.Id, model.ThreadMetadata.GetValueOrDefault()?.CreatedAt.GetValueOrDefault());
entity.Update(model);
return entity;
}
@@ -57,6 +66,7 @@ namespace Discord.Rest

if (model.ThreadMetadata.IsSpecified)
{
IsInvitable = model.ThreadMetadata.Value.Invitable.ToNullable();
IsArchived = model.ThreadMetadata.Value.Archived;
AutoArchiveDuration = model.ThreadMetadata.Value.AutoArchiveDuration;
ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp;


+ 10
- 2
src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs View File

@@ -799,7 +799,12 @@ namespace Discord.Rest
PrivacyLevel = args.PrivacyLevel,
StartTime = args.StartTime,
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)
@@ -839,6 +844,7 @@ namespace Discord.Rest
DateTimeOffset? endTime = null,
ulong? channelId = null,
string location = null,
Image? bannerImage = null,
RequestOptions options = null)
{
if(location != null)
@@ -864,6 +870,7 @@ namespace Discord.Rest
if (endTime != null && endTime <= startTime)
throw new ArgumentOutOfRangeException(nameof(endTime), $"{nameof(endTime)} cannot be before the start time");

var apiArgs = new CreateGuildScheduledEventParams()
{
ChannelId = channelId ?? Optional<ulong>.Unspecified,
@@ -872,7 +879,8 @@ namespace Discord.Rest
Name = name,
PrivacyLevel = privacyLevel,
StartTime = startTime,
Type = type
Type = type,
Image = bannerImage.HasValue ? bannerImage.Value.ToModel() : Optional<ImageModel>.Unspecified
};

if(location != null)


+ 5
- 3
src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs View File

@@ -1167,6 +1167,7 @@ namespace Discord.Rest
/// </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="coverImage">The optional banner image for the event.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous create operation.
@@ -1180,8 +1181,9 @@ namespace Discord.Rest
DateTimeOffset? endTime = null,
ulong? channelId = null,
string location = null,
Image? coverImage = 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

@@ -1198,8 +1200,8 @@ namespace Discord.Rest
IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers;

/// <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 />
async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions options)


+ 8
- 0
src/Discord.Net.Rest/Entities/Guilds/RestGuildEvent.cs View File

@@ -28,6 +28,9 @@ namespace Discord.Rest
/// <inheritdoc/>
public string Description { get; private set; }

/// <inheritdoc/>
public string CoverImageId { get; private set; }

/// <inheritdoc/>
public DateTimeOffset StartTime { get; private set; }

@@ -98,8 +101,13 @@ namespace Discord.Rest
EntityId = model.EntityId;
Location = model.EntityMetadata?.Location.GetValueOrDefault();
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/>
public Task StartAsync(RequestOptions options = null)
=> ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active);


+ 0
- 1
src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs View File

@@ -53,7 +53,6 @@ namespace Discord.Rest
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?.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
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)


+ 2
- 0
src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs View File

@@ -41,6 +41,8 @@ namespace Discord.Rest
/// <inheritdoc />
bool IVoiceState.IsStreaming => false;
/// <inheritdoc />
bool IVoiceState.IsVideoing => false;
/// <inheritdoc />
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
#endregion
}


+ 13
- 1
src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs View File

@@ -19,10 +19,13 @@ namespace Discord.Rest
private long? _timedOutTicks;
private long? _joinedAtTicks;
private ImmutableArray<ulong> _roleIds;

/// <inheritdoc />
public string DisplayName => Nickname ?? Username;
/// <inheritdoc />
public string Nickname { get; private set; }
/// <inheritdoc/>
public string DisplayAvatarId => GuildAvatarId ?? AvatarId;
/// <inheritdoc/>
public string GuildAvatarId { get; private set; }
internal IGuild Guild { get; private set; }
/// <inheritdoc />
@@ -182,6 +185,13 @@ namespace Discord.Rest
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)
=> CDN.GetGuildUserAvatarUrl(Id, GuildId, GuildAvatarId, size, format);
#endregion
@@ -213,6 +223,8 @@ namespace Discord.Rest
/// <inheritdoc />
bool IVoiceState.IsStreaming => false;
/// <inheritdoc />
bool IVoiceState.IsVideoing => false;
/// <inheritdoc />
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
#endregion
}


+ 8
- 0
src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs View File

@@ -52,10 +52,16 @@ namespace Discord.Rest
/// <inheritdoc />
DateTimeOffset? IGuildUser.JoinedAt => null;
/// <inheritdoc />
string IGuildUser.DisplayName => null;
/// <inheritdoc />
string IGuildUser.Nickname => null;
/// <inheritdoc/>
string IGuildUser.DisplayAvatarId => null;
/// <inheritdoc />
string IGuildUser.GuildAvatarId => null;
/// <inheritdoc />
string IGuildUser.GetDisplayAvatarUrl(ImageFormat format, ushort size) => null;
/// <inheritdoc />
string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => null;
/// <inheritdoc />
bool? IGuildUser.IsPending => null;
@@ -125,6 +131,8 @@ namespace Discord.Rest
/// <inheritdoc />
bool IVoiceState.IsStreaming => false;
/// <inheritdoc />
bool IVoiceState.IsVideoing => false;
/// <inheritdoc />
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
#endregion
}


+ 14
- 4
src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs View File

@@ -75,7 +75,6 @@ namespace Discord.Net.Queue
switch (response.StatusCode)
{
case (HttpStatusCode)429:
info.ReadRatelimitPayload(response.Stream);
if (info.IsGlobal)
{
#if DEBUG_LIMITS
@@ -88,7 +87,7 @@ namespace Discord.Net.Queue
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] (!) 429");
#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);
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)
return;
@@ -373,7 +372,18 @@ namespace Discord.Net.Queue
Debug.WriteLine($"[{id}] X-RateLimit-Remaining: " + 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
resetTick = DateTimeOffset.UtcNow.AddSeconds(info.RetryAfter.Value);


+ 7
- 11
src/Discord.Net.Rest/Net/RateLimitInfo.cs View File

@@ -61,22 +61,18 @@ namespace Discord.Net
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;
}
}
}

+ 12
- 5
src/Discord.Net.WebSocket/BaseSocketClient.cs View File

@@ -209,7 +209,7 @@ namespace Discord.WebSocket
/// Sets the <paramref name="activity"/> of the logged-in user.
/// </summary>
/// <remarks>
/// This method sets the <paramref name="activity"/> of the user.
/// This method sets the <paramref name="activity"/> of the user.
/// <note type="note">
/// Discord will only accept setting of name and the type of activity.
/// </note>
@@ -219,7 +219,7 @@ namespace Discord.WebSocket
/// </note>
/// <note type="warning">
/// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC
/// clients only.
/// clients only.
/// </note>
/// </remarks>
/// <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.
/// </summary>
/// <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">
/// Due to Discord's limitation, this method will only work for users that are in less than 10 guilds.
/// </note>
@@ -317,8 +317,15 @@ namespace Discord.WebSocket
=> await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false);

/// <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 />
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options)
=> Task.FromResult<IUser>(GetUser(username, discriminator));


+ 9
- 2
src/Discord.Net.WebSocket/DiscordShardedClient.cs View File

@@ -533,8 +533,15 @@ namespace Discord.WebSocket
=> await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false);

/// <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 />
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options)
=> Task.FromResult<IUser>(GetUser(username, discriminator));


+ 14
- 5
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -78,6 +78,7 @@ namespace Discord.WebSocket
internal bool AlwaysDownloadDefaultStickers { get; private set; }
internal bool AlwaysResolveStickers { get; private set; }
internal bool LogGatewayIntentWarnings { get; private set; }
internal bool SuppressUnknownDispatchWarnings { get; private set; }
internal new DiscordSocketApiClient ApiClient => base.ApiClient;
/// <inheritdoc />
public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds;
@@ -150,6 +151,7 @@ namespace Discord.WebSocket
AlwaysDownloadDefaultStickers = config.AlwaysDownloadDefaultStickers;
AlwaysResolveStickers = config.AlwaysResolveStickers;
LogGatewayIntentWarnings = config.LogGatewayIntentWarnings;
SuppressUnknownDispatchWarnings = config.SuppressUnknownDispatchWarnings;
HandlerTimeout = config.HandlerTimeout;
State = new ClientState(0, 0);
Rest = new DiscordSocketRestClient(config, ApiClient);
@@ -543,7 +545,7 @@ namespace Discord.WebSocket
if(model == null)
return null;

if (model.GuildId.IsSpecified)
{
var guild = State.GetGuild(model.GuildId.Value);
@@ -2128,7 +2130,7 @@ namespace Discord.WebSocket
{
await TimedInvokeAsync(_speakerRemoved, nameof(SpeakerRemoved), stage, guildUser);
}
}
}
}

await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, before, after).ConfigureAwait(false);
@@ -2520,7 +2522,7 @@ namespace Discord.WebSocket
}

break;
case "THREAD_MEMBERS_UPDATE":
case "THREAD_MEMBERS_UPDATE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBERS_UPDATE)").ConfigureAwait(false);

@@ -2771,7 +2773,7 @@ namespace Discord.WebSocket

#region Others
default:
await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false);
if(!SuppressUnknownDispatchWarnings) await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false);
break;
#endregion
}
@@ -3113,7 +3115,14 @@ namespace Discord.WebSocket

/// <inheritdoc />
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 />
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options)
=> Task.FromResult<IUser>(GetUser(username, discriminator));


+ 5
- 0
src/Discord.Net.WebSocket/DiscordSocketConfig.cs View File

@@ -188,6 +188,11 @@ namespace Discord.WebSocket
/// </summary>
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>
/// Initializes a new instance of the <see cref="DiscordSocketConfig"/> class with the default configuration.
/// </summary>


+ 16
- 77
src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs View File

@@ -18,83 +18,22 @@ namespace Discord.WebSocket
/// </returns>
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>
/// Gets a cached message from this channel.


+ 31
- 7
src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs View File

@@ -4,6 +4,7 @@ using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Discord.Rest;
using Model = Discord.API.Channel;

namespace Discord.WebSocket
@@ -64,21 +65,44 @@ namespace Discord.WebSocket
#endregion

#region IGuildChannel

/// <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 />
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

#region IChannel

/// <inheritdoc />
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 />
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
}
}

+ 1
- 1
src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs View File

@@ -17,7 +17,7 @@ namespace Discord.WebSocket
/// <summary>
/// Gets when the channel is created.
/// </summary>
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
public virtual DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
/// <summary>
/// Gets a collection of users from the WebSocket cache.
/// </summary>


+ 61
- 20
src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs View File

@@ -139,24 +139,48 @@ namespace Discord.WebSocket

/// <inheritdoc />
/// <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 />
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 />
/// <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 />
/// <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 />
/// <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 />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
@@ -255,20 +279,37 @@ namespace Discord.WebSocket
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).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)
=> 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 />
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 />
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 />
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 />
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

#region IChannel


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save