Browse Source

Merge branch 'release/3.x' of https://github.com/Discord-Net-Labs/Discord.Net-Labs into merger-labs

pull/1923/head
quin lynch 3 years ago
parent
commit
b293c3cf50
100 changed files with 2202 additions and 323 deletions
  1. +31
    -0
      docs/guides/guild_events/creating-guild-events.md
  2. +16
    -0
      docs/guides/guild_events/getting-event-users.md
  3. +41
    -0
      docs/guides/guild_events/intro.md
  4. +23
    -0
      docs/guides/guild_events/modifying-events.md
  5. +3
    -1
      docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md
  6. +1
    -1
      docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md
  7. +10
    -21
      docs/guides/toc.yml
  8. +19
    -0
      src/Discord.Net.Core/DiscordConfig.cs
  9. +197
    -0
      src/Discord.Net.Core/DiscordErrorCode.cs
  10. +53
    -0
      src/Discord.Net.Core/DiscordJsonError.cs
  11. +3
    -3
      src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs
  12. +23
    -0
      src/Discord.Net.Core/Entities/ApplicationFlags.cs
  13. +31
    -0
      src/Discord.Net.Core/Entities/ApplicationInstallParams.cs
  14. +58
    -0
      src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
  15. +0
    -3
      src/Discord.Net.Core/Entities/Channels/IStageChannel.cs
  16. +3
    -1
      src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
  17. +3
    -4
      src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs
  18. +4
    -0
      src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs
  19. +105
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs
  20. +46
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs
  21. +7
    -2
      src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs
  22. +25
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs
  23. +34
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs
  24. +34
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs
  25. +58
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs
  26. +62
    -3
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  27. +170
    -0
      src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
  28. +9
    -1
      src/Discord.Net.Core/Entities/Guilds/SystemChannelMessageDeny.cs
  29. +12
    -2
      src/Discord.Net.Core/Entities/IApplication.cs
  30. +11
    -0
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs
  31. +5
    -4
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs
  32. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs
  33. +6
    -8
      src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs
  34. +13
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs
  35. +13
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteractionData.cs
  36. +13
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs
  37. +13
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteractionData.cs
  38. +2
    -2
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs
  39. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs
  40. +3
    -3
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs
  41. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs
  42. +3
    -3
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
  43. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs
  44. +2
    -2
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs
  45. +18
    -8
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
  46. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs
  47. +6
    -10
      src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
  48. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs
  49. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs
  50. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs
  51. +62
    -108
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
  52. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs
  53. +18
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteraction.cs
  54. +25
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs
  55. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs
  56. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs
  57. +1
    -3
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
  58. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs
  59. +13
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteraction.cs
  60. +40
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteractionData.cs
  61. +13
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs
  62. +61
    -10
      src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
  63. +0
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs
  64. +83
    -0
      src/Discord.Net.Core/Entities/Messages/FileAttachment.cs
  65. +7
    -0
      src/Discord.Net.Core/Entities/Messages/MessageProperties.cs
  66. +5
    -1
      src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
  67. +21
    -1
      src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
  68. +7
    -0
      src/Discord.Net.Core/Entities/Roles/IRole.cs
  69. +1
    -22
      src/Discord.Net.Core/Entities/Users/IUser.cs
  70. +16
    -0
      src/Discord.Net.Core/Extensions/GuildExtensions.cs
  71. +32
    -0
      src/Discord.Net.Core/Extensions/ObjectExtensions.cs
  72. +3
    -1
      src/Discord.Net.Core/GatewayIntents.cs
  73. +6
    -51
      src/Discord.Net.Core/Net/ApplicationCommandException.cs
  74. +8
    -9
      src/Discord.Net.Core/Net/HttpException.cs
  75. +56
    -0
      src/Discord.Net.Core/Utils/Cacheable.cs
  76. +6
    -2
      src/Discord.Net.Rest/API/Common/Application.cs
  77. +10
    -0
      src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs
  78. +4
    -0
      src/Discord.Net.Rest/API/Common/Attachment.cs
  79. +20
    -0
      src/Discord.Net.Rest/API/Common/DiscordError.cs
  80. +12
    -0
      src/Discord.Net.Rest/API/Common/Error.cs
  81. +3
    -1
      src/Discord.Net.Rest/API/Common/Guild.cs
  82. +43
    -0
      src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs
  83. +15
    -0
      src/Discord.Net.Rest/API/Common/GuildScheduledEventEntityMetadata.cs
  84. +19
    -0
      src/Discord.Net.Rest/API/Common/GuildScheduledEventUser.cs
  85. +17
    -0
      src/Discord.Net.Rest/API/Common/InstallParams.cs
  86. +17
    -0
      src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs
  87. +3
    -1
      src/Discord.Net.Rest/API/Common/Role.cs
  88. +29
    -0
      src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs
  89. +2
    -0
      src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs
  90. +15
    -0
      src/Discord.Net.Rest/API/Rest/GetEventUsersParams.cs
  91. +2
    -0
      src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs
  92. +31
    -0
      src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs
  93. +2
    -0
      src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs
  94. +7
    -1
      src/Discord.Net.Rest/API/Rest/StartThreadParams.cs
  95. +29
    -12
      src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
  96. +3
    -0
      src/Discord.Net.Rest/BaseDiscordClient.cs
  97. +134
    -10
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  98. +72
    -2
      src/Discord.Net.Rest/DiscordRestClient.cs
  99. +19
    -2
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  100. +17
    -0
      src/Discord.Net.Rest/Entities/Channels/RestChannel.cs

+ 31
- 0
docs/guides/guild_events/creating-guild-events.md View File

@@ -0,0 +1,31 @@
---
uid: Guides.GuildEvents.Creating
title: Creating Guild Events
---

# Creating guild events

You can create new guild events by using the `CreateEventAsync` function on a guild.

### Parameters

| Name | Type | Summary |
| ------------- | --------------------------------- | ---------------------------------------------------------------------------- |
| name | `string` | Sets the name of the event. |
| startTime | `DateTimeOffset` | Sets the start time of the event. |
| type | `GuildScheduledEventType` | Sets the type of the event. |
| privacyLevel? | `GuildScheduledEventPrivacyLevel` | Sets the privacy level of the event |
| description? | `string` | Sets the description of the event. |
| endTime? | `DateTimeOffset?` | Sets the end time of the event. |
| channelId? | `ulong?` | Sets the channel id of the event, only valid on stage or voice channel types |
| location? | `string` | Sets the location of the event, only valid on external types |

Lets create a basic test event.

```cs
var guild = client.GetGuild(guildId);

var guildEvent = await guild.CreateEventAsync("test event", DateTimeOffset.UtcNow.AddDays(1), GuildScheduledEventType.External, endTime: DateTimeOffset.UtcNow.AddDays(2), location: "Space");
```

This code will create an event that lasts a day and starts tomorrow. It will be an external event thats in space.

+ 16
- 0
docs/guides/guild_events/getting-event-users.md View File

@@ -0,0 +1,16 @@
---
uid: Guides.GuildEvents.GettingUsers
title: Getting Guild Event Users
---

# Getting Event Users

You can get a collection of users who are currently interested in the event by calling `GetUsersAsync`. This method works like any other get users method as in it returns an async enumerable. This method also supports pagination by user id.

```cs
// get all users and flatten the result into one collection.
var users = await event.GetUsersAsync().FlattenAsync();

// get users around the 613425648685547541 id.
var aroundUsers = await event.GetUsersAsync(613425648685547541, Direction.Around).FlattenAsync();
```

+ 41
- 0
docs/guides/guild_events/intro.md View File

@@ -0,0 +1,41 @@
---
uid: Guides.GuildEvents.Intro
title: Introduction to Guild Events
---

# Guild Events

Guild events are a way to host events within a guild. They offer alot of features and flexibility.

## Getting started with guild events

You can access any events within a guild by calling `GetEventsAsync` on a guild.

```cs
var guildEvents = await guild.GetEventsAsync();
```

If your working with socket guilds you can just use the `Events` property:

```cs
var guildEvents = guild.Events;
```

There are also new gateway events that you can hook to receive guild scheduled events on.

```cs
// Fired when a guild event is cancelled.
client.GuildScheduledEventCancelled += ...

// Fired when a guild event is completed.
client.GuildScheduledEventCompleted += ...

// Fired when a guild event is started.
client.GuildScheduledEventStarted += ...

// Fired when a guild event is created.
client.GuildScheduledEventCreated += ...

// Fired when a guild event is updated.
client.GuildScheduledEventUpdated += ...
```

+ 23
- 0
docs/guides/guild_events/modifying-events.md View File

@@ -0,0 +1,23 @@
---
uid: Guides.GuildEvents.Modifying
title: Modifying Guild Events
---

# Modifying Events

You can modify events using the `ModifyAsync` method to modify the event, heres the properties you can modify:

| Name | Type | Description |
| ------------ | --------------------------------- | -------------------------------------------- |
| ChannelId | `ulong?` | Gets or sets the channel id of the event. |
| string | `string` | Gets or sets the location of this event. |
| Name | `string` | Gets or sets the name of the event. |
| PrivacyLevel | `GuildScheduledEventPrivacyLevel` | Gets or sets the privacy level of the event. |
| StartTime | `DateTimeOffset` | Gets or sets the start time of the event. |
| EndTime | `DateTimeOffset` | Gets or sets the end time of the event. |
| Description | `string` | Gets or sets the description of the event. |
| Type | `GuildScheduledEventType` | Gets or sets the type of the event. |
| Status | `GuildScheduledEventStatus` | Gets or sets the status of the event. |

> [!NOTE]
> All of these properties are optional.

+ 3
- 1
docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md View File

@@ -79,6 +79,8 @@ public async Task Client_Ready()

// With global commands we dont 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.
}
catch(ApplicationCommandException exception)
{
@@ -93,4 +95,4 @@ public async Task Client_Ready()
```

> [!NOTE]
> Slash commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register slash commands.
> Slash commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register slash commands. The global commands take up to an hour to register every time the CreateGlobalApplicationCommandAsync() is called for a given command.

+ 1
- 1
docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md View File

@@ -20,4 +20,4 @@ await command.RespondAsync(embed: embedBuiler.Build(), ephemeral: true);

Running the command now only shows the message to us!

![ephemeral command](images/ephemeral1.png)
![ephemeral command](images/ephemeral1.png)

+ 10
- 21
docs/guides/toc.yml View File

@@ -1,26 +1,15 @@
- name: Introduction
topicUid: Guides.Introduction
- name: Getting Started
- name: "Working with Guild Events"
items:
- name: Installation
topicUid: Guides.GettingStarted.Installation
items:
- name: Nightly Builds
topicUid: Guides.GettingStarted.Installation.Nightlies
- name: Your First Bot
topicUid: Guides.GettingStarted.FirstBot
- name: Terminology
topicUid: Guides.GettingStarted.Terminology
- name: Basic Concepts
items:
- name: Logging Data
topicUid: Guides.Concepts.Logging
- name: Working with Events
topicUid: Guides.Concepts.Events
- name: Managing Connections
topicUid: Guides.Concepts.ManageConnections
- name: Entities
topicUid: Guides.Concepts.Entities
- name: Introduction
topicUid: Guides.GuildEvents.Intro
- name: Creating Events
topicUid: Guides.GuildEvents.Creating
- name: Getting Event Users
topicUid: Guides.GuildEvents.GettingUsers
- name: Modifying Events
topicUid: Guides.GuildEvents.Modifying
- name: Working with Commands
items:
- name: Introduction
@@ -35,7 +24,7 @@
topicUid: Guides.Commands.DI
- name: Post-execution Handling
topicUid: Guides.Commands.PostExecution
- name: Working with Slash commands
- name: Working with Slash Commands
items:
- name: Introduction
topicUid: Guides.SlashCommands.Intro


+ 19
- 0
src/Discord.Net.Core/DiscordConfig.cs View File

@@ -94,6 +94,13 @@ namespace Discord
/// The maximum number of users that can be gotten per-batch.
/// </returns>
public const int MaxUsersPerBatch = 1000;
/// <summary>
/// Returns the max users allowed to be in a request for guild event users.
/// </summary>
/// <returns>
/// The maximum number of users that can be gotten per-batch.
/// </returns>
public const int MaxGuildEventUsersPerBatch = 100;
/// <summary>
/// Returns the max guilds allowed to be in a request.
/// </summary>
@@ -158,5 +165,17 @@ namespace Discord
/// clock. Your system will still need a stable clock.
/// </remarks>
public bool UseSystemClock { get; set; } = true;

/// <summary>
/// Gets or sets whether or not the internal experation check uses the system date
/// + snowflake date to check if an interaction can be responded to.
/// </summary>
/// <remarks>
/// If set to <see langword="false"/> then the CreatedAt property in an interaction
/// will be set to when it was received instead of the snowflakes date.
/// <br/>
/// <b>This will still require a stable clock on your system.</b>
/// </remarks>
public bool UseInteractionSnowflakeDate { get; set; } = true;
}
}

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

@@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a set of json error codes received by discord.
/// </summary>
public enum DiscordErrorCode
{
GeneralError = 0,

#region UnknownXYZ (10XXX)
UnknownAccount = 10001,
UnknownApplication = 10002,
UnknownChannel = 10003,
UnknownGuild = 10004,
UnknownIntegration = 10005,
UnknownInvite = 10006,
UnknownMember = 10007,
UnknownMessage = 10008,
UnknownPermissionOverwrite = 10009,
UnknownProvider = 10010,
UnknownRole = 10011,
UnknownToken = 10012,
UnknownUser = 10013,
UnknownEmoji = 10014,
UnknownWebhook = 10015,
UnknownWebhookService = 10016,
UnknownSession = 10020,
UnknownBan = 10026,
UnknownSKU = 10027,
UnknownStoreListing = 10028,
UnknownEntitlement = 10029,
UnknownBuild = 10030,
UnknownLobby = 10031,
UnknownBranch = 10032,
UnknownStoreDirectoryLayout = 10033,
UnknownRedistributable = 10036,
UnknownGiftCode = 10038,
UnknownStream = 10049,
UnknownPremiumServerSubscribeCooldown = 10050,
UnknownGuildTemplate = 10057,
UnknownDiscoverableServerCategory = 10059,
UnknownSticker = 10060,
UnknownInteraction = 10062,
UnknownApplicationCommand = 10063,
UnknownApplicationCommandPermissions = 10066,
UnknownStageInstance = 10067,
UnknownGuildMemberVerificationForm = 10068,
UnknownGuildWelcomeScreen = 10069,
UnknownGuildScheduledEvent = 10070,
UnknownGuildScheduledEventUser = 10071,
#endregion

#region General Actions (20XXX)
BotsCannotUse = 20001,
OnlyBotsCanUse = 20002,
CannotSendExplicitContent = 20009,
ApplicationActionUnauthorized = 20012,
ActionSlowmode = 20016,
OnlyOwnerAction = 20018,
AnnouncementEditRatelimit = 20022,
ChannelWriteRatelimit = 20028,
WordsNotAllowed = 20031,
GuildPremiumTooLow = 20035,
#endregion

#region Numeric Limits Reached (30XXX)
MaximumGuildsReached = 30001,
MaximumFriendsReached = 30002,
MaximumPinsReached = 30003,
MaximumRecipientsReached = 30004,
MaximumGuildRolesReached = 30005,
MaximumWebhooksReached = 30007,
MaximumEmojisReached = 30008,
MaximumReactionsReached = 30010,
MaximumGuildChannelsReached = 30013,
MaximumAttachmentsReached = 30015,
MaximumInvitesReached = 30016,
MaximumAnimatedEmojisReached = 30018,
MaximumServerMembersReached = 30019,
MaximumServerCategoriesReached = 30030,
GuildTemplateAlreadyExists = 30031,
MaximumThreadMembersReached = 30033,
MaximumBansForNonGuildMembersReached = 30035,
MaximumBanFetchesReached = 30037,
MaximumUncompleteGuildScheduledEvents = 30038,
MaximumStickersReached = 30039,
MaximumPruneRequestReached = 30040,
MaximumGuildWigitsReached = 30042,
#endregion

#region General Request Errors (40XXX)
TokenUnauthorized = 40001,
InvalidVerification = 40002,
OpeningDMTooFast = 40003,
RequestEntityTooLarge = 40005,
FeatureDisabled = 40006,
UserBanned = 40007,
TargetUserNotInVoice = 40032,
MessageAlreadyCrossposted = 40033,
ApplicationNameAlreadyExists = 40041,
#endregion

#region Action Preconditions/Checks (50XXX)
MissingPermissions = 50001,
InvalidAccountType = 50002,
CannotExecuteForDM = 50003,
GuildWigitDisabled = 50004,
CannotEditOtherUsersMessage = 50005,
CannotSendEmptyMessage = 50006,
CannotSendMessageToUser = 50007,
CannotSendMessageToVoiceChannel = 50008,
ChannelVerificationTooHight = 50009,
OAuth2ApplicationDoesntHaveBot = 50010,
OAuth2ApplicationLimitReached = 50011,
InvalidOAuth2State = 50012,
InsufficientPermissions = 50013,
InvalidAuthenticationToken = 50014,
NoteTooLong = 50015,
ProvidedMessageDeleteCountOutOfBounds = 50016,
InvalidPinChannel = 50019,
InvalidInvite = 50020,
CannotExecuteOnSystemMessage = 50021,
CannotExecuteOnChannelType = 50024,
InvalidOAuth2Token = 50025,
MissingOAuth2Scope = 50026,
InvalidWebhookToken = 50027,
InvalidRole = 50028,
InvalidRecipients = 50033,
BulkDeleteMessageTooOld = 50034,
InvalidFormBody = 50035,
InviteAcceptedForGuildThatBotIsntIn = 50036,
InvalidAPIVersion = 50041,
FileUploadTooBig = 50045,
InvalidFileUpload = 50046,
CannotSelfRedeemGift = 50054,
PaymentSourceRequiredForGift = 50070,
CannotDeleteRequiredCommunityChannel = 50074,
InvalidSticker = 50081,
CannotExecuteOnArchivedThread = 50083,
InvalidThreadNotificationSettings = 50084,
BeforeValueEarlierThanThreadCreation = 50085,
ServerLocaleUnavailable = 50095,
ServerRequiresMonetization = 50097,
ServerRequiresBoosts = 50101,

#endregion

#region 2FA (60XXX)
Requires2FA = 60003,
#endregion

#region User Searches (80XXX)
NoUsersWithTag = 80004,
#endregion

#region Reactions (90XXX)
ReactionBlocked = 90001,
#endregion

#region API Status (130XXX)
APIOverloaded = 130000,
#endregion

#region Stage Errors (150XXX)
StageAlreadyOpened = 150006,
#endregion

#region Reply and Thread Errors (160XXX)
CannotReplyWithoutReadMessageHistory = 160002,
MessageAlreadyContainsThread = 160004,
ThreadIsLocked = 160005,
MaximumActiveThreadsReached = 160006,
MaximumAnnouncementThreadsReached = 160007,
#endregion

#region Sticker Uploads (170XXX)
InvalidJSONLottie = 170001,
LottieCantContainRasters = 170002,
StickerMaximumFramerateExceeded = 170003,
StickerMaximumFrameCountExceeded = 170004,
LottieMaximumDimentionsExceeded = 170005,
StickerFramerateBoundsExceeed = 170006,
StickerAnimationDurationTooLong = 170007,
#endregion

#region Guild Scheduled Events
CannotUpdateFinishedEvent = 180000,
FailedStageCreation = 180002,
#endregion
}
}

+ 53
- 0
src/Discord.Net.Core/DiscordJsonError.cs View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a generic parsed json error received from discord after performing a rest request.
/// </summary>
public struct DiscordJsonError
{
/// <summary>
/// Gets the json path of the error.
/// </summary>
public string Path { get; }

/// <summary>
/// Gets a collection of errors associated with the specific property at the path.
/// </summary>
public IReadOnlyCollection<DiscordError> Errors { get; }

internal DiscordJsonError(string path, DiscordError[] errors)
{
Path = path;
Errors = errors.ToImmutableArray();
}
}

/// <summary>
/// Represents an error with a property.
/// </summary>
public struct DiscordError
{
/// <summary>
/// Gets the code of the error.
/// </summary>
public string Code { get; }

/// <summary>
/// Gets the message describing what went wrong.
/// </summary>
public string Message { get; }

internal DiscordError(string code, string message)
{
Code = code;
Message = message;
}
}
}

+ 3
- 3
src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs View File

@@ -37,14 +37,14 @@ namespace Discord
/// <summary>
/// Indicates that a user is playing an activity in a voice channel with friends.
/// </summary>
PARTY_PRIVACY_FRIENDS = 0b1000000,
PartyPrivacyFriends = 0b1000000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel.
/// </summary>
PARTY_PRIVACY_VOICE_CHANNEL = 0b10000000,
PartyPrivacyVoiceChannel = 0b10000000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel.
/// </summary>
EMBEDDED = 0b10000000
Embedded = 0b10000000
}
}

+ 23
- 0
src/Discord.Net.Core/Entities/ApplicationFlags.cs View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents public flags for an application.
/// </summary>
public enum ApplicationFlags
{
GatewayPresence = 1 << 12,
GatewayPresenceLimited = 1 << 13,
GatewayGuildMembers = 1 << 14,
GatewayGuildMembersLimited = 1 << 15,
VerificationPendingGuildLimit = 1 << 16,
Embedded = 1 << 17,
GatewayMessageContent = 1 << 18,
GatewayMessageContentLimited = 1 << 19
}
}

+ 31
- 0
src/Discord.Net.Core/Entities/ApplicationInstallParams.cs View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents install parameters for an application.
/// </summary>
public class ApplicationInstallParams
{
/// <summary>
/// Gets the scopes to install this application.
/// </summary>
public IReadOnlyCollection<string> Scopes { get; }

/// <summary>
/// Gets the default permissions to install this application
/// </summary>
public GuildPermission? Permission { get; }

internal ApplicationInstallParams(string[] scopes, GuildPermission? permission)
{
Scopes = scopes.ToImmutableArray();
Permission = permission;
}
}
}

+ 58
- 0
src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs View File

@@ -113,6 +113,64 @@ namespace Discord
/// 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 component = null, ISticker[] stickers = null, Embed[] embeds = null);
/// <summary>
/// Sends a file to this message channel with an optional caption.
/// </summary>
/// <remarks>
/// This method sends a file as if you are uploading an attachment directly from your Discord client.
/// <note>
/// If you wish to upload an image and have it embedded in a <see cref="Discord.EmbedType.Rich"/> embed,
/// you may upload the file and refer to the file with "attachment://filename.ext" in the
/// <see cref="Discord.EmbedBuilder.ImageUrl"/>. See the example section for its usage.
/// </note>
/// </remarks>
/// <param name="attachment">The attachment containing the file and description.</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="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="component">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>
Task<IUserMessage> SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null);
/// <summary>
/// Sends a collection of files to this message channel.
/// </summary>
/// <remarks>
/// This method sends files as if you are uploading attachments directly from your Discord client.
/// <note>
/// If you wish to upload an image and have it embedded in a <see cref="Discord.EmbedType.Rich"/> embed,
/// you may upload the file and refer to the file with "attachment://filename.ext" in the
/// <see cref="Discord.EmbedBuilder.ImageUrl"/>. See the example section for its usage.
/// </note>
/// </remarks>
/// <param name="attachments">A collection of attachments to upload.</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="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="component">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>
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 component = null, ISticker[] stickers = null, Embed[] embeds = null);

/// <summary>
/// Gets a message from this message channel.


+ 0
- 3
src/Discord.Net.Core/Entities/Channels/IStageChannel.cs View File

@@ -32,9 +32,6 @@ namespace Discord
/// <summary>
/// Gets whether or not the stage is live.
/// </summary>
/// <remarks>
/// If the stage isn't live then this property will be set to <see langword="null"/>.
/// </remarks>
bool IsLive { get; }

/// <summary>


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

@@ -141,11 +141,13 @@ namespace Discord
/// </para>
/// </param>
/// <param name="message">The message which to start the thread from.</param>
/// <param name="invitable">Whether non-moderators can add other non-moderators to a thread; only available when creating a private thread</param>
/// <param name="slowmode">The amount of seconds a user has to wait before sending another message (0-21600)</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous create operation. The task result contains a <see cref="IThreadChannel"/>
/// </returns>
Task<IThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay,
IMessage message = null, RequestOptions options = null);
IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null);
}
}

+ 3
- 4
src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs View File

@@ -6,13 +6,12 @@ namespace Discord
public enum StagePrivacyLevel
{
/// <summary>
/// The stage is a public stage.
/// The Stage instance is visible publicly, such as on Stage Discovery.
/// </summary>
Public = 1,

/// <summary>
/// The stage is non public and is only accessable from the guild.
/// The Stage instance is visible to only guild members.
/// </summary>
GuildOnly = 2,
GuildOnly = 2
}
}

+ 4
- 0
src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs View File

@@ -13,5 +13,9 @@ namespace Discord
/// Gets or sets the maximum number of users that can be present in a channel, or <c>null</c> if none.
/// </summary>
public Optional<int?> UserLimit { get; set; }
/// <summary>
/// Gets or sets the channel voice region id, automatic when set to <see langword="null"/>.
/// </summary>
public Optional<string> RTCRegion { get; set; }
}
}

+ 105
- 0
src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
[Flags]
public enum GuildFeature
{
/// <summary>
/// The guild has no features.
/// </summary>
None = 0,
/// <summary>
/// The guild has access to set an animated guild icon.
/// </summary>
AnimatedIcon = 1 << 0,
/// <summary>
/// The guild has access to set a guild banner image.
/// </summary>
Banner = 1 << 1,
/// <summary>
/// The guild has access to use commerce features (i.e. create store channels).
/// </summary>
Commerce = 1 << 2,
/// <summary>
/// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates.
/// </summary>
Community = 1 << 3,
/// <summary>
/// The guild is able to be discovered in the directory.
/// </summary>
Discoverable = 1 << 4,
/// <summary>
/// The guild is able to be featured in the directory.
/// </summary>
Featureable = 1 << 5,
/// <summary>
/// The guild has access to set an invite splash background.
/// </summary>
InviteSplash = 1 << 6,
/// <summary>
/// The guild has enabled <seealso href="https://discord.com/developers/docs/resources/guild#membership-screening-object">Membership Screening</seealso>.
/// </summary>
MemberVerificationGateEnabled = 1 << 7,
/// <summary>
/// The guild has enabled monetization.
/// </summary>
MonetizationEnabled = 1 << 8,
/// <summary>
/// The guild has increased custom sticker slots.
/// </summary>
MoreStickers = 1 << 9,
/// <summary>
/// The guild has access to create news channels.
/// </summary>
News = 1 << 10,
/// <summary>
/// The guild is partnered.
/// </summary>
Partnered = 1 << 11,
/// <summary>
/// The guild can be previewed before joining via Membership Screening or the directory.
/// </summary>
PreviewEnabled = 1 << 12,
/// <summary>
/// The guild has access to create private threads.
/// </summary>
PrivateThreads = 1 << 13,
/// <summary>
/// The guild is able to set role icons.
/// </summary>
RoleIcons = 1 << 14,
/// <summary>
/// The guild has access to the seven day archive time for threads.
/// </summary>
SevenDayThreadArchive = 1 << 15,
/// <summary>
/// The guild has access to the three day archive time for threads.
/// </summary>
ThreeDayThreadArchive = 1 << 16,
/// <summary>
/// The guild has enabled ticketed events.
/// </summary>
TicketedEventsEnabled = 1 << 17,
/// <summary>
/// The guild has access to set a vanity URL.
/// </summary>
VanityUrl = 1 << 18,
/// <summary>
/// The guild is verified.
/// </summary>
Verified = 1 << 19,
/// <summary>
/// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers).
/// </summary>
VIPRegions = 1 << 20,
/// <summary>
/// The guild has enabled the welcome screen.
/// </summary>
WelcomeScreenEnabled = 1 << 21,
}
}

+ 46
- 0
src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public class GuildFeatures
{
/// <summary>
/// Gets the flags of recognized features for this guild.
/// </summary>
public GuildFeature Value { get; }

/// <summary>
/// Gets a collection of experimental features for this guild.
/// </summary>
public IReadOnlyCollection<string> Experimental { get; }


internal GuildFeatures(GuildFeature value, string[] experimental)
{
Value = value;
Experimental = experimental.ToImmutableArray();
}

public bool HasFeature(GuildFeature feature)
=> Value.HasFlag(feature);
public bool HasFeature(string feature)
=> Experimental.Contains(feature);

internal void EnsureFeature(GuildFeature feature)
{
if (!HasFeature(feature))
{
var vals = Enum.GetValues(typeof(GuildFeature)).Cast<GuildFeature>();

var missingValues = vals.Where(x => feature.HasFlag(x) && !Value.HasFlag(x));

throw new InvalidOperationException($"Missing required guild feature{(missingValues.Count() > 1 ? "s" : "")} {string.Join(", ", missingValues.Select(x => x.ToString()))} in order to execute this operation.");
}
}
}
}

+ 7
- 2
src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs View File

@@ -85,8 +85,9 @@ namespace Discord
/// given that the <see cref="SystemChannelId"/> has also been set.
/// A value of <see cref="SystemChannelMessageDeny.GuildBoost"/> will deny guild boost messages from being sent, and allow all
/// other types of messages.
/// Refer to the extension methods <see cref="GuildExtensions.GetGuildBoostMessagesEnabled(IGuild)"/> and
/// <see cref="GuildExtensions.GetWelcomeMessagesEnabled(IGuild)"/> to check if these system channel message types
/// Refer to the extension methods <see cref="GuildExtensions.GetGuildBoostMessagesEnabled(IGuild)"/>,
/// <see cref="GuildExtensions.GetWelcomeMessagesEnabled(IGuild)"/>, <see cref="GuildExtensions.GetGuildSetupTipMessagesEnabled(IGuild)"/>,
/// and <see cref="GuildExtensions.GetGuildWelcomeMessageReplyEnabled(IGuild)"/> to check if these system channel message types
/// are enabled, without the need to manipulate the logic of the flag.
/// </remarks>
public Optional<SystemChannelMessageDeny> SystemChannelFlags { get; set; }
@@ -108,5 +109,9 @@ namespace Discord
/// the value of <see cref="PreferredCulture"/> will be unused.
/// </remarks>
public Optional<CultureInfo> PreferredCulture { get; set; }
/// <summary>
/// Gets or sets if the boost progress bar is enabled.
/// </summary>
public Optional<bool> IsBoostProgressBarEnabled { get; set; }
}
}

+ 25
- 0
src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents the privacy level of a guild scheduled event.
/// </summary>
public enum GuildScheduledEventPrivacyLevel
{
/// <summary>
/// The scheduled event is public and available in discovery.
/// </summary>
[Obsolete("This event type isn't supported yet! check back later.", true)]
Public = 1,

/// <summary>
/// The scheduled event is only accessible to guild members.
/// </summary>
Private = 2,
}
}

+ 34
- 0
src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents the status of a guild event.
/// </summary>
public enum GuildScheduledEventStatus
{
/// <summary>
/// The event is scheduled for a set time.
/// </summary>
Scheduled = 1,

/// <summary>
/// The event has started.
/// </summary>
Active = 2,

/// <summary>
/// The event was completed.
/// </summary>
Completed = 3,

/// <summary>
/// The event was canceled.
/// </summary>
Cancelled = 4,
}
}

+ 34
- 0
src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents the type of a guild scheduled event.
/// </summary>
public enum GuildScheduledEventType
{
/// <summary>
/// The event doesn't have a set type.
/// </summary>
None = 0,

/// <summary>
/// The event is set in a stage channel.
/// </summary>
Stage = 1,

/// <summary>
/// The event is set in a voice channel.
/// </summary>
Voice = 2,

/// <summary>
/// The event is set for somewhere externally from discord.
/// </summary>
External = 3,
}
}

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

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Provides properties that are used to modify an <see cref="IGuildScheduledEvent" /> with the specified changes.
/// </summary>
public class GuildScheduledEventsProperties
{
/// <summary>
/// Gets or sets the channel id of the event.
/// </summary>
public Optional<ulong?> ChannelId { get; set; }

/// <summary>
/// Gets or sets the location of this event.
/// </summary>
public Optional<string> Location { get; set; }

/// <summary>
/// Gets or sets the name of the event.
/// </summary>
public Optional<string> Name { get; set; }

/// <summary>
/// Gets or sets the privacy level of the event.
/// </summary>
public Optional<GuildScheduledEventPrivacyLevel> PrivacyLevel { get; set; }

/// <summary>
/// Gets or sets the start time of the event.
/// </summary>
public Optional<DateTimeOffset> StartTime { get; set; }
/// <summary>
/// Gets or sets the end time of the event.
/// </summary>
public Optional<DateTimeOffset> EndTime { get; set; }

/// <summary>
/// Gets or sets the description of the event.
/// </summary>
public Optional<string> Description { get; set; }

/// <summary>
/// Gets or sets the type of the event.
/// </summary>
public Optional<GuildScheduledEventType> Type { get; set; }

/// <summary>
/// Gets or sets the status of the event.
/// </summary>
public Optional<GuildScheduledEventStatus> Status { get; set; }
}
}

+ 62
- 3
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -207,12 +207,12 @@ namespace Discord
/// </returns>
IReadOnlyCollection<ICustomSticker> Stickers { get; }
/// <summary>
/// Gets a collection of all extra features added to this guild.
/// Gets the features for this guild.
/// </summary>
/// <returns>
/// A read-only collection of enabled features in this guild.
/// A flags enum containing all the features for the guild.
/// </returns>
IReadOnlyCollection<string> Features { get; }
GuildFeatures Features { get; }
/// <summary>
/// Gets a collection of all roles in this guild.
/// </summary>
@@ -339,6 +339,13 @@ namespace Discord
/// The preferred culture information of this guild.
/// </returns>
CultureInfo PreferredCulture { get; }
/// <summary>
/// Gets whether the guild has the boost progress bar enabled.
/// </summary>
/// <returns>
/// <see langword="true"/> if the boost progress bar is enabled; otherwise <see langword="false"/>.
/// </returns>
bool IsBoostProgressBarEnabled { get; }

/// <summary>
/// Modifies this guild.
@@ -1049,6 +1056,58 @@ namespace Discord
/// </returns>
Task DeleteStickerAsync(ICustomSticker sticker, RequestOptions options = null);

/// <summary>
/// Gets a event within this guild.
/// </summary>
/// <param name="id">The id of the event.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation.
/// </returns>
Task<IGuildScheduledEvent> GetEventAsync(ulong id, RequestOptions options = null);

/// <summary>
/// Gets a collection of events within this guild.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation.
/// </returns>
Task<IReadOnlyCollection<IGuildScheduledEvent>> GetEventsAsync(RequestOptions options = null);

/// <summary>
/// Creates an event within this guild.
/// </summary>
/// <param name="name">The name of the event.</param>
/// <param name="privacyLevel">The privacy level of the event.</param>
/// <param name="startTime">The start time of the event.</param>
/// <param name="type">The type of the event.</param>
/// <param name="description">The description of the event.</param>
/// <param name="endTime">The end time of the event.</param>
/// <param name="channelId">
/// The channel id of the event.
/// <remarks>
/// The event must have a type of <see cref="GuildScheduledEventType.Stage"/> or <see cref="GuildScheduledEventType.Voice"/>
/// in order to use this property.
/// </remarks>
/// </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="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous create operation.
/// </returns>
Task<IGuildScheduledEvent> CreateEventAsync(
string name,
DateTimeOffset startTime,
GuildScheduledEventType type,
GuildScheduledEventPrivacyLevel privacyLevel = GuildScheduledEventPrivacyLevel.Private,
string description = null,
DateTimeOffset? endTime = null,
ulong? channelId = null,
string location = null,
RequestOptions options = null);

/// <summary>
/// Gets this guilds application commands.
/// </summary>


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

@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a generic guild scheduled event.
/// </summary>
public interface IGuildScheduledEvent : IEntity<ulong>
{
/// <summary>
/// Gets the guild this event is scheduled in.
/// </summary>
IGuild Guild { get; }

/// <summary>
/// Gets the optional channel id where this event will be hosted.
/// </summary>
ulong? ChannelId { get; }

/// <summary>
/// Gets the user who created the event.
/// </summary>
IUser Creator { get; }

/// <summary>
/// Gets the name of the event.
/// </summary>
string Name { get; }

/// <summary>
/// Gets the description of the event.
/// </summary>
/// <remarks>
/// This field is <see langword="null"/> when the event doesn't have a discription.
/// </remarks>
string Description { get; }

/// <summary>
/// Gets the start time of the event.
/// </summary>
DateTimeOffset StartTime { get; }

/// <summary>
/// Gets the optional end time of the event.
/// </summary>
DateTimeOffset? EndTime { get; }

/// <summary>
/// Gets the privacy level of the event.
/// </summary>
GuildScheduledEventPrivacyLevel PrivacyLevel { get; }

/// <summary>
/// Gets the status of the event.
/// </summary>
GuildScheduledEventStatus Status { get; }

/// <summary>
/// Gets the type of the event.
/// </summary>
GuildScheduledEventType Type { get; }

/// <summary>
/// Gets the optional entity id of the event. The "entity" of the event
/// can be a stage instance event as is seperate from <see cref="ChannelId"/>.
/// </summary>
ulong? EntityId { get; }

/// <summary>
/// Gets the location of the event if the <see cref="Type"/> is external.
/// </summary>
string Location { get; }

/// <summary>
/// Gets the user count of the event.
/// </summary>
int? UserCount { get; }

/// <summary>
/// Starts the event.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous start operation.
/// </returns>
Task StartAsync(RequestOptions options = null);
/// <summary>
/// Ends or canceles the event.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous end operation.
/// </returns>
Task EndAsync(RequestOptions options = null);

/// <summary>
/// Modifies the guild event.
/// </summary>
/// <param name="func">The delegate containing the properties to modify the event with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
Task ModifyAsync(Action<GuildScheduledEventsProperties> func, RequestOptions options = null);

/// <summary>
/// Deletes the current event.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous delete operation.
/// </returns>
Task DeleteAsync(RequestOptions options = null);

/// <summary>
/// Gets a collection of N users interested in the event.
/// </summary>
/// <remarks>
/// <note type="important">
/// 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>
/// This method will attempt to fetch all users that are interested in the event.
/// The library will attempt to split up the requests according to and <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>.
/// In other words, if there are 300 users, and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant
/// is <c>100</c>, the request will be split into 3 individual requests; thus returning 3 individual asynchronous
/// responses, hence the need of flattening.
/// </remarks>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// Paged collection of users.
/// </returns>
IAsyncEnumerable<IReadOnlyCollection<IUser>> GetUsersAsync(RequestOptions options = null);

/// <summary>
/// Gets a collection of N users interested in the event.
/// </summary>
/// <remarks>
/// <note type="important">
/// The returned collection is an asynchronous enumerable object; one must call
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual users as a
/// collection.
/// </note>
/// <note type="warning">
/// Do not fetch too many users at once! This may cause unwanted preemptive rate limit or even actual
/// rate limit, causing your bot to freeze!
/// </note>
/// This method will attempt to fetch the number of users specified under <paramref name="limit"/> around
/// the user <paramref name="fromUserId"/> depending on the <paramref name="dir"/>. The library will
/// attempt to split up the requests according to your <paramref name="limit"/> and
/// <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>. In other words, should the user request 500 users,
/// and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant is <c>100</c>, the request will
/// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need
/// of flattening.
/// </remarks>
/// <param name="fromUserId">The ID of the starting user to get the users from.</param>
/// <param name="dir">The direction of the users to be gotten from.</param>
/// <param name="limit">The numbers of users to be gotten from.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// Paged collection of users.
/// </returns>
IAsyncEnumerable<IReadOnlyCollection<IUser>> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null);
}
}

+ 9
- 1
src/Discord.Net.Core/Entities/Guilds/SystemChannelMessageDeny.cs View File

@@ -17,6 +17,14 @@ namespace Discord
/// <summary>
/// Deny the messages that are sent when a user boosts the guild.
/// </summary>
GuildBoost = 0b10
GuildBoost = 0b10,
/// <summary>
/// Deny the messages that are related to guild setup.
/// </summary>
GuildSetupTip = 0b100,
/// <summary>
/// Deny the reply with sticker button on welcome messages.
/// </summary>
WelcomeMessageReply = 0b1000
}
}

+ 12
- 2
src/Discord.Net.Core/Entities/IApplication.cs View File

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

namespace Discord
{
/// <summary>
@@ -16,8 +18,16 @@ namespace Discord
/// <summary>
/// Gets the RPC origins of the application.
/// </summary>
string[] RPCOrigins { get; }
ulong Flags { get; }
IReadOnlyCollection<string> RPCOrigins { get; }
ApplicationFlags Flags { get; }
/// <summary>
/// Gets a collection of install parameters for this application.
/// </summary>
ApplicationInstallParams InstallParams { get; }
/// <summary>
/// Gets a collection of tags related to the application.
/// </summary>
IReadOnlyCollection<string> Tags { get; }
/// <summary>
/// Gets the icon URL of the application.
/// </summary>


+ 11
- 0
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs View File

@@ -66,6 +66,17 @@ namespace Discord
/// Gets or sets whether or not this option supports autocomplete.
/// </summary>
public bool IsAutocomplete { get; set; }

/// <summary>
/// Gets or sets the smallest number value the user can input.
/// </summary>
public double? MinValue { get; set; }

/// <summary>
/// Gets or sets the largest number value the user can input.
/// </summary>
public double? MaxValue { get; set; }

/// <summary>
/// Gets or sets the choices for string and int types for the user to pick from.
/// </summary>


+ 5
- 4
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs View File

@@ -9,6 +9,7 @@ namespace Discord
{
private string _name;
private object _value;

/// <summary>
/// Gets or sets the name of this choice.
/// </summary>
@@ -24,9 +25,9 @@ namespace Discord
}

/// <summary>
/// Gets or sets the value of this choice.
/// Gets the value of this choice.
/// <note type="warning">
/// Discord only accepts int, string, and doubles as the input.
/// Discord only accepts int, double/floats, and string as the input.
/// </note>
/// </summary>
public object Value
@@ -34,8 +35,8 @@ namespace Discord
get => _value;
set
{
if (value != null && value is not int && value is not string && value is not double)
throw new ArgumentException("The value of a choice must be a string, int, or double!");
if (value != null && value is not string && !value.IsNumericType())
throw new ArgumentException("The value of a choice must be a string or a numeric type!");
_value = value;
}
}


+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs View File

@@ -6,7 +6,7 @@ namespace Discord
public class AutocompleteOption
{
/// <summary>
/// Gets the type of this option
/// Gets the type of this option.
/// </summary>
public ApplicationCommandOptionType Type { get; }



+ 6
- 8
src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs View File

@@ -45,15 +45,13 @@ namespace Discord
public object Value
{
get => _value;
set => _value = value switch
set
{
string str => str,
int integer => integer,
long lng => lng,
double number => number,
null => throw new ArgumentNullException(nameof(value), $"{nameof(Value)} cannot be null."),
_ => throw new ArgumentException($"Type {value.GetType().Name} cannot be set as a value! Only string, int, and double allowed!")
};
if (value is not string && !value.IsNumericType())
throw new ArgumentException($"{nameof(value)} must be a numeric type or a string!");

_value = value;
}
}

/// <summary>


+ 13
- 0
src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs View File

@@ -0,0 +1,13 @@
namespace Discord
{
/// <summary>
/// Represents a Message Command interaction.
/// </summary>
public interface IMessageCommandInteraction : IDiscordInteraction
{
/// <summary>
/// Gets the data associated with this interaction.
/// </summary>
new IMessageCommandInteractionData Data { get; }
}
}

+ 13
- 0
src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteractionData.cs View File

@@ -0,0 +1,13 @@
namespace Discord
{
/// <summary>
/// Represents the data tied with the <see cref="IMessageCommandInteraction"/> interaction.
/// </summary>
public interface IMessageCommandInteractionData : IApplicationCommandInteractionData
{
/// <summary>
/// Gets the message associated with this message command.
/// </summary>
IMessage Message { get; }
}
}

+ 13
- 0
src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs View File

@@ -0,0 +1,13 @@
namespace Discord
{
/// <summary>
/// Represents a User Command interaction.
/// </summary>
public interface IUserCommandInteraction : IDiscordInteraction
{
/// <summary>
/// Gets the data associated with this interaction.
/// </summary>
new IUserCommandInteractionData Data { get; }
}
}

+ 13
- 0
src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteractionData.cs View File

@@ -0,0 +1,13 @@
namespace Discord
{
/// <summary>
/// Represents the data tied with the <see cref="IUserCommandInteraction"/> interaction.
/// </summary>
public interface IUserCommandInteractionData : IApplicationCommandInteractionData
{
/// <summary>
/// Gets the user who this command targets.
/// </summary>
IUser User { get; }
}
}

src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs → src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs View File

@@ -19,7 +19,7 @@ namespace Discord
set
{
Preconditions.NotNullOrEmpty(value, nameof(Name));
Preconditions.AtLeast(value.Length, 3, nameof(Name));
Preconditions.AtLeast(value.Length, 1, nameof(Name));
Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name));

_name = value;
@@ -68,7 +68,7 @@ namespace Discord
/// </summary>
/// <param name="isDefaultPermission">The default permission value to set.</param>
/// <returns>The current builder.</returns>
public MessageCommandBuilder WithDefaultPermission (bool isDefaultPermission)
public MessageCommandBuilder WithDefaultPermission(bool isDefaultPermission)
{
IsDefaultPermission = isDefaultPermission;
return this;

src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs → src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs View File


src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs → src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs View File

@@ -19,7 +19,7 @@ namespace Discord
set
{
Preconditions.NotNullOrEmpty(value, nameof(Name));
Preconditions.AtLeast(value.Length, 3, nameof(Name));
Preconditions.AtLeast(value.Length, 1, nameof(Name));
Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name));

_name = value;
@@ -27,7 +27,7 @@ namespace Discord
}

/// <summary>
/// Gets or sets whether the command is enabled by default when the app is added to a guild
/// Gets or sets whether the command is enabled by default when the app is added to a guild.
/// </summary>
public bool IsDefaultPermission { get; set; } = true;

@@ -66,7 +66,7 @@ namespace Discord
/// </summary>
/// <param name="isDefaultPermission">The default permission value to set.</param>
/// <returns>The current builder.</returns>
public UserCommandBuilder WithDefaultPermission (bool isDefaultPermission)
public UserCommandBuilder WithDefaultPermission(bool isDefaultPermission)
{
IsDefaultPermission = isDefaultPermission;
return this;

src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs → src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs View File


+ 3
- 3
src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs View File

@@ -15,7 +15,7 @@ namespace Discord
ulong ApplicationId { get; }

/// <summary>
/// Gets the type of the command
/// Gets the type of the command.
/// </summary>
ApplicationCommandType Type { get; }

@@ -30,12 +30,12 @@ namespace Discord
string Description { get; }

/// <summary>
/// Gets whether or not the command is enabled by default when the app is added to a guild.
/// Gets whether the command is enabled by default when the app is added to a guild.
/// </summary>
bool IsDefaultPermission { get; }

/// <summary>
/// Gets the options for this application command.
/// Gets a collection of options for this application command.
/// </summary>
IReadOnlyCollection<IApplicationCommandOption> Options { get; }



+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs View File

@@ -5,7 +5,7 @@ namespace Discord
/// <summary>
/// Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/>.
/// </summary>
public interface IApplicationCommandInteractionData
public interface IApplicationCommandInteractionData : IDiscordInteractionData
{
/// <summary>
/// Gets the snowflake id of this command.


+ 2
- 2
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Represents a option group for a command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondataoption"/>.
/// Represents a option group for a command.
/// </summary>
public interface IApplicationCommandInteractionDataOption
{
@@ -15,7 +15,7 @@ namespace Discord
/// <summary>
/// Gets the value of the pair.
/// <note>
/// This objects type can be any one of the option types in <see cref="ApplicationCommandOptionType"/>
/// This objects type can be any one of the option types in <see cref="ApplicationCommandOptionType"/>.
/// </note>
/// </summary>
object Value { get; }


+ 18
- 8
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Options for the <see cref="IApplicationCommand"/>, see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoption">The docs</see>.
/// Options for the <see cref="IApplicationCommand"/>.
/// </summary>
public interface IApplicationCommandOption
{
@@ -13,37 +13,47 @@ namespace Discord
ApplicationCommandOptionType Type { get; }

/// <summary>
/// Gets the name of this command option, 1-32 character name.
/// Gets the name of this command option.
/// </summary>
string Name { get; }

/// <summary>
/// Gets the discription of this command option, 1-100 character description.
/// Gets the description of this command option.
/// </summary>
string Description { get; }

/// <summary>
/// Gets the first required option for the user to complete--only one option can be default.
/// Gets whether or not this is the first required option for the user to complete.
/// </summary>
bool? IsDefault { get; }

/// <summary>
/// Gets if the parameter is required or optional, default is <see langword="false"/>.
/// Gets whether or not the parameter is required or optional.
/// </summary>
bool? IsRequired { get; }

/// <summary>
/// Gets a collection of choices for the user to pick from.
/// Gets the smallest number value the user can input.
/// </summary>
double? MinValue { get; }

/// <summary>
/// Gets the largest number value the user can input.
/// </summary>
double? MaxValue { get; }

/// <summary>
/// Gets the choices for string and int types for the user to pick from.
/// </summary>
IReadOnlyCollection<IApplicationCommandOptionChoice> Choices { get; }

/// <summary>
/// Gets the nested options of this option.
/// Gets the sub-options for this command option.
/// </summary>
IReadOnlyCollection<IApplicationCommandOption> Options { get; }

/// <summary>
/// The allowed channel types for this option.
/// Gets the allowed channel types for this option.
/// </summary>
IReadOnlyCollection<ChannelType> ChannelTypes { get; }
}


+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs View File

@@ -6,7 +6,7 @@ namespace Discord
public interface IApplicationCommandOptionChoice
{
/// <summary>
/// Gets the name of this choice.
/// Gets the choice name.
/// </summary>
string Name { get; }



+ 6
- 10
src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs View File

@@ -4,36 +4,32 @@ using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// Represents a discord interaction
/// <para>
/// An interaction is the base "thing" that is sent when a user invokes a command, and is the same for Slash Commands
/// and other future interaction types. see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction"/>.
/// </para>
/// Represents a discord interaction.
/// </summary>
public interface IDiscordInteraction : ISnowflakeEntity
{
/// <summary>
/// The id of the interaction.
/// Gets the id of the interaction.
/// </summary>
new ulong Id { get; }

/// <summary>
/// The type of this <see cref="IDiscordInteraction"/>.
/// Gets the type of this <see cref="IDiscordInteraction"/>.
/// </summary>
InteractionType Type { get; }

/// <summary>
/// Represents the data sent within this interaction.
/// Gets the data sent within this interaction.
/// </summary>
IDiscordInteractionData Data { get; }

/// <summary>
/// A continuation token for responding to the interaction.
/// Gets the continuation token for responding to the interaction.
/// </summary>
string Token { get; }

/// <summary>
/// read-only property, always 1.
/// Gets the version of the interaction, always 1.
/// </summary>
int Version { get; }



src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs View File

@@ -27,7 +27,7 @@ namespace Discord
public string CustomId { get; }

/// <summary>
/// Gets the URL for a <see cref="ButtonStyle.Link"/> button.
/// Gets the URL for a <see cref="ButtonStyle.Link"/> button.
/// </summary>
/// <remarks>
/// You cannot have a button with a <b>URL</b> and a <b>CustomId</b>.

src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonStyle.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs View File

@@ -31,14 +31,14 @@ namespace Discord
set
{
if (value == null)
throw new ArgumentNullException(paramName: nameof(ActionRows), message: "Cannot set an component builder's components collection to null.");
throw new ArgumentNullException(nameof(value), $"{nameof(ActionRows)} cannot be null.");
if (value.Count > MaxActionRowCount)
throw new ArgumentException(message: $"Action row count must be less than or equal to {MaxActionRowCount}.", paramName: nameof(ActionRows));
throw new ArgumentOutOfRangeException(nameof(value), $"Action row count must be less than or equal to {MaxActionRowCount}.");
_actionRows = value;
}
}

private List<ActionRowBuilder> _actionRows { get; set; }
private List<ActionRowBuilder> _actionRows;

/// <summary>
/// Creates a new builder from a message.
@@ -56,7 +56,7 @@ namespace Discord
public static ComponentBuilder FromComponents(IReadOnlyCollection<IMessageComponent> components)
{
var builder = new ComponentBuilder();
for(int i = 0; i != components.Count; i++)
for (int i = 0; i != components.Count; i++)
{
var component = components.ElementAt(i);
builder.AddComponent(component, i);
@@ -118,7 +118,7 @@ namespace Discord
public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0)
{
Preconditions.LessThan(row, MaxActionRowCount, nameof(row));
if (menu.Options.Distinct().Count() != menu.Options.Count())
if (menu.Options.Distinct().Count() != menu.Options.Count)
throw new InvalidOperationException("Please make sure that there is no duplicates values.");

var builtMenu = menu.Build();
@@ -218,7 +218,7 @@ namespace Discord
else
{
ActionRowBuilder actionRow;
if(_actionRows.Count > row)
if (_actionRows.Count > row)
actionRow = _actionRows.ElementAt(row);
else
{
@@ -244,10 +244,9 @@ namespace Discord
/// <returns>A <see cref="MessageComponent"/> that can be sent with <see cref="IMessageChannel.SendMessageAsync"/>.</returns>
public MessageComponent Build()
{
if (_actionRows != null)
return new MessageComponent(_actionRows.Select(x => x.Build()).ToList());
else
return MessageComponent.Empty;
return _actionRows != null
? new MessageComponent(_actionRows.Select(x => x.Build()).ToList())
: MessageComponent.Empty;
}
}

@@ -272,19 +271,18 @@ namespace Discord
set
{
if (value == null)
throw new ArgumentNullException(message: "Action row components cannot be null!", paramName: nameof(Components));

if (value.Count <= 0)
throw new ArgumentException(message: "There must be at least 1 component in a row", paramName: nameof(Components));

if (value.Count > MaxChildCount)
throw new ArgumentException(message: $"Action row can only contain {MaxChildCount} child components!", paramName: nameof(Components));
throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null.");

_components = value;
_components = value.Count switch
{
0 => throw new ArgumentOutOfRangeException(nameof(value), "There must be at least 1 component in a row."),
> MaxChildCount => throw new ArgumentOutOfRangeException(nameof(value), $"Action row can only contain {MaxChildCount} child components!"),
_ => value
};
}
}

private List<IMessageComponent> _components { get; set; } = new List<IMessageComponent>();
private List<IMessageComponent> _components = new List<IMessageComponent>();

/// <summary>
/// Adds a list of components to the current row.
@@ -359,18 +357,12 @@ namespace Discord
public string Label
{
get => _label;
set
set => _label = value?.Length switch
{
if (value != null)
{
if (value.Length > MaxButtonLabelLength)
throw new ArgumentException($"Button label must be {MaxButtonLabelLength} characters or less!", paramName: nameof(Label));
if (value.Length < 1)
throw new ArgumentException("Button label must be 1 character or more!", paramName: nameof(Label));
}

_label = value;
}
> MaxButtonLabelLength => throw new ArgumentOutOfRangeException(nameof(value), $"Label length must be less or equal to {MaxButtonLabelLength}."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Label length must be at least 1."),
_ => value
};
}

/// <summary>
@@ -381,17 +373,12 @@ namespace Discord
public string CustomId
{
get => _customId;
set
set => _customId = value?.Length switch
{
if (value != null)
{
if (value.Length > ComponentBuilder.MaxCustomIdLength)
throw new ArgumentException($"Custom Id must be {ComponentBuilder.MaxCustomIdLength} characters or less!", paramName: nameof(CustomId));
if (value.Length < 1)
throw new ArgumentException("Custom Id must be 1 character or more!", paramName: nameof(CustomId));
}
_customId = value;
}
> ComponentBuilder.MaxCustomIdLength => throw new ArgumentOutOfRangeException(nameof(value), $"Custom Id length must be less or equal to {ComponentBuilder.MaxCustomIdLength}."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Custom Id length must be at least 1."),
_ => value
};
}

/// <summary>
@@ -414,7 +401,6 @@ namespace Discord
/// </summary>
public bool IsDisabled { get; set; }


private string _label;
private string _customId;

@@ -594,8 +580,7 @@ namespace Discord
{
if (string.IsNullOrEmpty(Url))
throw new InvalidOperationException("Link buttons must have a link associated with them");
else
UrlValidation.Validate(Url);
UrlValidation.ValidateButton(Url);
}
else if (string.IsNullOrEmpty(CustomId))
throw new InvalidOperationException("Non-link buttons must have a custom id associated with them");
@@ -632,17 +617,12 @@ namespace Discord
public string CustomId
{
get => _customId;
set
set => _customId = value?.Length switch
{
if (value != null)
{
if (value.Length > ComponentBuilder.MaxCustomIdLength)
throw new ArgumentException($"Custom Id must be {ComponentBuilder.MaxCustomIdLength} characters or less!", paramName: nameof(CustomId));
if (value.Length < 1)
throw new ArgumentException("Custom Id must be 1 character or more!", paramName: nameof(CustomId));
}
_customId = value;
}
> ComponentBuilder.MaxCustomIdLength => throw new ArgumentOutOfRangeException(nameof(value), $"Custom Id length must be less or equal to {ComponentBuilder.MaxCustomIdLength}."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Custom Id length must be at least 1."),
_ => value
};
}

/// <summary>
@@ -653,18 +633,12 @@ namespace Discord
public string Placeholder
{
get => _placeholder;
set
set => _placeholder = value?.Length switch
{
if (value != null)
{
if (value.Length > MaxPlaceholderLength)
throw new ArgumentException($"The placeholder must be {MaxPlaceholderLength} characters or less!", paramName: nameof(Placeholder));
if (value.Length < 1)
throw new ArgumentException("The placeholder must be 1 character or more!", paramName: nameof(Placeholder));
}

_placeholder = value;
}
> MaxPlaceholderLength => throw new ArgumentOutOfRangeException(nameof(value), $"Placeholder length must be less or equal to {MaxPlaceholderLength}."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Placeholder length must be at least 1."),
_ => value
};
}

/// <summary>
@@ -676,7 +650,7 @@ namespace Discord
get => _minValues;
set
{
Preconditions.LessThan(value, MaxValuesCount, nameof(MinValues));
Preconditions.AtMost(value, MaxValuesCount, nameof(MinValues));
_minValues = value;
}
}
@@ -690,7 +664,7 @@ namespace Discord
get => _maxValues;
set
{
Preconditions.LessThan(value, MaxValuesCount, nameof(MaxValues));
Preconditions.AtMost(value, MaxValuesCount, nameof(MaxValues));
_maxValues = value;
}
}
@@ -706,9 +680,9 @@ namespace Discord
set
{
if (value != null)
Preconditions.LessThan(value.Count, MaxOptionCount, nameof(Options));
Preconditions.AtMost(value.Count, MaxOptionCount, nameof(Options));
else
throw new ArgumentNullException(nameof(value));
throw new ArgumentNullException(nameof(value), $"{nameof(Options)} cannot be null.");

_options = value;
}
@@ -858,14 +832,14 @@ namespace Discord
/// <param name="value">The value of this option.</param>
/// <param name="description">The description of this option.</param>
/// <param name="emote">The emote of this option.</param>
/// <param name="default">Render this option as selected by default or not.</param>
/// <param name="isDefault">Render this option as selected by default or not.</param>
/// <exception cref="InvalidOperationException">Options count reached <see cref="MaxOptionCount"/>.</exception>
/// <returns>
/// The current builder.
/// </returns>
public SelectMenuBuilder AddOption(string label, string value, string description = null, IEmote emote = null, bool? @default = null)
public SelectMenuBuilder AddOption(string label, string value, string description = null, IEmote emote = null, bool? isDefault = null)
{
AddOption(new SelectMenuOptionBuilder(label, value, description, emote, @default));
AddOption(new SelectMenuOptionBuilder(label, value, description, emote, isDefault));
return this;
}

@@ -908,7 +882,7 @@ namespace Discord
/// The maximum length of a <see cref="SelectMenuOption.Description"/>.
/// </summary>
public const int MaxDescriptionLength = 100;
/// <summary>
/// The maximum length of a <see cref="SelectMenuOption.Value"/>.
/// </summary>
@@ -922,42 +896,28 @@ namespace Discord
public string Label
{
get => _label;
set
set => _label = value?.Length switch
{
if (value != null)
{
if (value.Length > MaxSelectLabelLength)
throw new ArgumentException($"Select option label must be {MaxSelectLabelLength} characters or less!", paramName: nameof(Label));
if (value.Length < 1)
throw new ArgumentException("Select option label must be 1 character or more!", paramName: nameof(Label));
}

_label = value;
}
> MaxSelectLabelLength => throw new ArgumentOutOfRangeException(nameof(value), $"Label length must be less or equal to {MaxSelectLabelLength}."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Label length must be at least 1."),
_ => value
};
}

/// <summary>
/// Gets or sets the custom id of the current select menu.
/// Gets or sets the value of the current select menu.
/// </summary>
/// <exception cref="ArgumentException" accessor="set"><see cref="Value"/> length exceeds <see cref="MaxSelectValueLength"/>.</exception>
/// <exception cref="ArgumentException" accessor="set"><see cref="Value"/> length subceeds 1.</exception>
public string Value
{
get => _value;
set
set => _value = value?.Length switch
{
if (value != null)
{
if (value.Length > MaxSelectValueLength)
throw new ArgumentException($"Select option value must be {MaxSelectValueLength} characters or less!", paramName: nameof(Label));
if (value.Length < 1)
throw new ArgumentException("Select option value must be 1 character or more!", paramName: nameof(Label));
}
else
throw new ArgumentException("Select option value must not be null or empty!", paramName: nameof(Label));

_value = value;
}
> MaxSelectValueLength => throw new ArgumentOutOfRangeException(nameof(value), $"Value length must be less or equal to {MaxSelectValueLength}."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Value length must be at least 1."),
_ => value
};
}

/// <summary>
@@ -968,18 +928,12 @@ namespace Discord
public string Description
{
get => _description;
set
set => _description = value?.Length switch
{
if (value != null)
{
if (value.Length > MaxDescriptionLength)
throw new ArgumentException($"The description must be {MaxDescriptionLength} characters or less!", paramName: nameof(Label));
if (value.Length < 1)
throw new ArgumentException("The description must be 1 character or more!", paramName: nameof(Label));
}

_description = value;
}
> MaxDescriptionLength => throw new ArgumentOutOfRangeException(nameof(value), $"Description length must be less or equal to {MaxDescriptionLength}."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be at least 1."),
_ => value
};
}

/// <summary>

src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs View File


+ 18
- 0
src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteraction.cs View File

@@ -0,0 +1,18 @@
namespace Discord
{
/// <summary>
/// Represents an interaction type for Message Components.
/// </summary>
public interface IComponentInteraction : IDiscordInteraction
{
/// <summary>
/// Gets the data received with this interaction, contains the button that was clicked.
/// </summary>
new IComponentInteractionData Data { get; }

/// <summary>
/// Gets the message that contained the trigger for this interaction.
/// </summary>
IUserMessage Message { get; }
}
}

+ 25
- 0
src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;

namespace Discord
{
/// <summary>
/// Represents the data sent with the <see cref="IComponentInteraction"/>.
/// </summary>
public interface IComponentInteractionData : IDiscordInteractionData
{
/// <summary>
/// Gets the components Custom Id that was clicked.
/// </summary>
string CustomId { get; }

/// <summary>
/// Gets the type of the component clicked.
/// </summary>
ComponentType Type { get; }

/// <summary>
/// Gets the value(s) of a <see cref="SelectMenuComponent"/> interaction response.
/// </summary>
IReadOnlyCollection<string> Values { get; }
}
}

src/Discord.Net.Core/Entities/Interactions/Message Components/IMessageComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs View File


src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuComponent.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs View File

@@ -11,9 +11,7 @@ namespace Discord
/// <inheritdoc/>
public ComponentType Type => ComponentType.SelectMenu;

/// <summary>
/// Gets the custom id of this Select menu that will be sent with a <see cref="IDiscordInteraction"/>.
/// </summary>
/// <inheritdoc/>
public string CustomId { get; }

/// <summary>

src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs → src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs View File


+ 13
- 0
src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteraction.cs View File

@@ -0,0 +1,13 @@
namespace Discord
{
/// <summary>
/// Represents a <see cref="InteractionType.ApplicationCommandAutocomplete"/>.
/// </summary>
public interface IAutocompleteInteraction : IDiscordInteraction
{
/// <summary>
/// Gets the autocomplete data of this interaction.
/// </summary>
new IAutocompleteInteractionData Data { get; }
}
}

+ 40
- 0
src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteractionData.cs View File

@@ -0,0 +1,40 @@
using System.Collections.Generic;

namespace Discord
{
/// <summary>
/// Represents data for a slash commands autocomplete interaction.
/// </summary>
public interface IAutocompleteInteractionData : IDiscordInteractionData
{
/// <summary>
/// Gets the name of the invoked command.
/// </summary>
string CommandName { get; }

/// <summary>
/// Gets the id of the invoked command.
/// </summary>
ulong CommandId { get; }

/// <summary>
/// Gets the type of the invoked command.
/// </summary>
ApplicationCommandType Type { get; }

/// <summary>
/// Gets the version of the invoked command.
/// </summary>
ulong Version { get; }

/// <summary>
/// Gets the current autocomplete option that is actively being filled out.
/// </summary>
AutocompleteOption Current { get; }

/// <summary>
/// Gets a collection of all the other options the executing users has filled out.
/// </summary>
IReadOnlyCollection<AutocompleteOption> Options { get; }
}
}

+ 13
- 0
src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs View File

@@ -0,0 +1,13 @@
namespace Discord
{
/// <summary>
/// Represents a slash command interaction.
/// </summary>
public interface ISlashCommandInteraction : IDiscordInteraction
{
/// <summary>
/// Gets the data associated with this interaction.
/// </summary>
new IApplicationCommandInteractionData Data { get; }
}
}

src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandBuilder.cs → src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs View File

@@ -99,7 +99,7 @@ namespace Discord
{
var options = new List<ApplicationCommandOptionProperties>();

Options.ForEach(x => options.Add(x.Build()));
Options.OrderByDescending(x => x.IsRequired ?? false).ToList().ForEach(x => options.Add(x.Build()));

props.Options = options;
}
@@ -154,10 +154,12 @@ namespace Discord
/// <param name="options">The options of the option to add.</param>
/// <param name="channelTypes">The allowed channel types for this option.</param>
/// <param name="choices">The choices of this option.</param>
/// <param name="minValue">The smallest number value the user can input.</param>
/// <param name="maxValue">The largest number value the user can input.</param>
/// <returns>The current builder.</returns>
public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type,
string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, List<SlashCommandOptionBuilder> options = null,
List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices)
string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices)
{
// Make sure the name matches the requirements from discord
Preconditions.NotNullOrEmpty(name, nameof(name));
@@ -188,7 +190,9 @@ namespace Discord
Type = type,
IsAutocomplete = isAutocomplete,
Choices = (choices ?? Array.Empty<ApplicationCommandOptionChoiceProperties>()).ToList(),
ChannelTypes = channelTypes
ChannelTypes = channelTypes,
MinValue = minValue,
MaxValue = maxValue,
};

return AddOption(option);
@@ -310,6 +314,16 @@ namespace Discord
/// </summary>
public bool IsAutocomplete { get; set; }

/// <summary>
/// Gets or sets the smallest number value the user can input.
/// </summary>
public double? MinValue { get; set; }

/// <summary>
/// Gets or sets the largest number value the user can input.
/// </summary>
public double? MaxValue { get; set; }

/// <summary>
/// Gets or sets the choices for string and int types for the user to pick from.
/// </summary>
@@ -332,6 +346,7 @@ namespace Discord
public ApplicationCommandOptionProperties Build()
{
bool isSubType = Type == ApplicationCommandOptionType.SubCommandGroup;
bool isIntType = Type == ApplicationCommandOptionType.Integer;

if (isSubType && (Options == null || !Options.Any()))
throw new InvalidOperationException("SubCommands/SubCommandGroups must have at least one option");
@@ -339,6 +354,12 @@ namespace Discord
if (!isSubType && Options != null && Options.Any() && Type != ApplicationCommandOptionType.SubCommand)
throw new InvalidOperationException($"Cannot have options on {Type} type");

if (isIntType && MinValue != null && MinValue % 1 != 0)
throw new InvalidOperationException("MinValue cannot have decimals on Integer command options.");

if (isIntType && MaxValue != null && MaxValue % 1 != 0)
throw new InvalidOperationException("MaxValue cannot have decimals on Integer command options.");

return new ApplicationCommandOptionProperties
{
Name = Name,
@@ -346,10 +367,14 @@ namespace Discord
IsDefault = IsDefault,
IsRequired = IsRequired,
Type = Type,
Options = Options?.Count > 0 ? Options.Select(x => x.Build()).ToList() : new List<ApplicationCommandOptionProperties>(),
Options = Options?.Count > 0
? Options.OrderByDescending(x => x.IsRequired ?? false).Select(x => x.Build()).ToList()
: new List<ApplicationCommandOptionProperties>(),
Choices = Choices,
IsAutocomplete = IsAutocomplete,
ChannelTypes = ChannelTypes
ChannelTypes = ChannelTypes,
MinValue = MinValue,
MaxValue = MaxValue
};
}

@@ -365,10 +390,12 @@ namespace Discord
/// <param name="options">The options of the option to add.</param>
/// <param name="channelTypes">The allowed channel types for this option.</param>
/// <param name="choices">The choices of this option.</param>
/// <param name="minValue">The smallest number value the user can input.</param>
/// <param name="maxValue">The largest number value the user can input.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder AddOption(string name, ApplicationCommandOptionType type,
string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, List<SlashCommandOptionBuilder> options = null,
List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices)
string description, bool? required = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices)
{
// Make sure the name matches the requirements from discord
Preconditions.NotNullOrEmpty(name, nameof(name));
@@ -393,9 +420,11 @@ namespace Discord
{
Name = name,
Description = description,
IsRequired = isRequired,
IsAutocomplete = isAutocomplete,
IsRequired = required,
IsDefault = isDefault,
IsAutocomplete = isAutocomplete,
MinValue = minValue,
MaxValue = maxValue,
Options = options,
Type = type,
Choices = (choices ?? Array.Empty<ApplicationCommandOptionChoiceProperties>()).ToList(),
@@ -575,6 +604,28 @@ namespace Discord
return this;
}

/// <summary>
/// Sets the current builders min value field.
/// </summary>
/// <param name="value">The value to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithMinValue(double value)
{
MinValue = value;
return this;
}
/// <summary>
/// Sets the current builders max value field.
/// </summary>
/// <param name="value">The value to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithMaxValue(double value)
{
MaxValue = value;
return this;
}

/// <summary>
/// Sets the current type of this builder.
/// </summary>

src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandProperties.cs → src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs View File


+ 83
- 0
src/Discord.Net.Core/Entities/Messages/FileAttachment.cs View File

@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public struct FileAttachment : IDisposable
{
public string FileName { get; set; }
public string Description { get; set; }
public bool IsSpoiler { get; set; }

#pragma warning disable IDISP008
public Stream Stream { get; }
#pragma warning restore IDISP008

private bool _isDisposed;

/// <summary>
/// Creates a file attachment from a stream.
/// </summary>
/// <param name="stream">The stream to create the attachment from.</param>
/// <param name="fileName">The name of the attachment.</param>
/// <param name="description">The description of the attachment.</param>
public FileAttachment(Stream stream, string fileName, string description = null, bool isSpoiler = false)
{
_isDisposed = false;
FileName = fileName;
Description = description;
Stream = stream;
IsSpoiler = isSpoiler;
}

/// <summary>
/// Create the file attachment from a file path.
/// </summary>
/// <remarks>
/// This file path is NOT validated and is passed directly into a
/// <see cref="File.OpenRead"/>.
/// </remarks>
/// <param name="path">The path to the file.</param>
/// <exception cref="System.ArgumentException">
/// <paramref name="path" /> is a zero-length string, contains only white space, or contains one or more invalid
/// characters as defined by <see cref="Path.GetInvalidPathChars"/>.
/// </exception>
/// <exception cref="System.ArgumentNullException"><paramref name="path" /> is <c>null</c>.</exception>
/// <exception cref="PathTooLongException">
/// The specified path, file name, or both exceed the system-defined maximum length. For example, on
/// Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260
/// characters.
/// </exception>
/// <exception cref="System.NotSupportedException"><paramref name="path" /> is in an invalid format.</exception>
/// <exception cref="DirectoryNotFoundException">
/// The specified <paramref name="path"/> is invalid, (for example, it is on an unmapped drive).
/// </exception>
/// <exception cref="System.UnauthorizedAccessException">
/// <paramref name="path" /> specified a directory.-or- The caller does not have the required permission.
/// </exception>
/// <exception cref="FileNotFoundException">The file specified in <paramref name="path" /> was not found.
/// </exception>
/// <exception cref="IOException">An I/O error occurred while opening the file. </exception>
public FileAttachment(string path, string description = null, bool isSpoiler = false)
{
_isDisposed = false;
Stream = File.OpenRead(path);
FileName = Path.GetFileName(path);
Description = description;
IsSpoiler = isSpoiler;
}

public void Dispose()
{
if (!_isDisposed)
{
Stream?.Dispose();
_isDisposed = true;
}
}
}
}

+ 7
- 0
src/Discord.Net.Core/Entities/Messages/MessageProperties.cs View File

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

namespace Discord
{
/// <summary>
@@ -48,5 +50,10 @@ namespace Discord
/// Gets or sets the allowed mentions of the message.
/// </summary>
public Optional<AllowedMentions> AllowedMentions { get; set; }

/// <summary>
/// Gets or sets the attachments for the message.
/// </summary>
public Optional<IEnumerable<FileAttachment>> Attachments { get; set; }
}
}

+ 5
- 1
src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs View File

@@ -180,10 +180,14 @@ namespace Discord
/// </summary>
UseApplicationCommands = 0x80_00_00_00,
/// <summary>
/// Allows for requesting to speak in stage channels. <i>(This permission is under active development and may be changed or removed.)</i>.
/// Allows for requesting to speak in stage channels.
/// </summary>
RequestToSpeak = 0x01_00_00_00_00,
/// <summary>
/// Allows for creating, editing, and deleting guild scheduled events.
/// </summary>
ManageEvents = 0x02_00_00_00_00,
/// <summary>
/// Allows for deleting and archiving threads, and viewing all private threads.
/// </summary>
/// <remarks>


+ 21
- 1
src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs View File

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

namespace Discord
{
@@ -87,6 +88,8 @@ namespace Discord
public bool UseApplicationCommands => Permissions.GetValue(RawValue, GuildPermission.UseApplicationCommands);
/// <summary> If <c>true</c>, a user may request to speak in stage channels. </summary>
public bool RequestToSpeak => Permissions.GetValue(RawValue, GuildPermission.RequestToSpeak);
/// <summary> If <c>true</c>, a user may create, edit, and delete events. </summary>
public bool ManageEvents => Permissions.GetValue(RawValue, GuildPermission.ManageEvents);
/// <summary> If <c>true</c>, a user may manage threads in this guild. </summary>
public bool ManageThreads => Permissions.GetValue(RawValue, GuildPermission.ManageThreads);
/// <summary> If <c>true</c>, a user may create public threads in this guild. </summary>
@@ -140,6 +143,7 @@ namespace Discord
bool? manageEmojisAndStickers = null,
bool? useApplicationCommands = null,
bool? requestToSpeak = null,
bool? manageEvents = null,
bool? manageThreads = null,
bool? createPublicThreads = null,
bool? createPrivateThreads = null,
@@ -182,6 +186,7 @@ namespace Discord
Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers);
Permissions.SetValue(ref value, useApplicationCommands, GuildPermission.UseApplicationCommands);
Permissions.SetValue(ref value, requestToSpeak, GuildPermission.RequestToSpeak);
Permissions.SetValue(ref value, manageEvents, GuildPermission.ManageEvents);
Permissions.SetValue(ref value, manageThreads, GuildPermission.ManageThreads);
Permissions.SetValue(ref value, createPublicThreads, GuildPermission.CreatePublicThreads);
Permissions.SetValue(ref value, createPrivateThreads, GuildPermission.CreatePrivateThreads);
@@ -227,6 +232,7 @@ namespace Discord
bool manageEmojisAndStickers = false,
bool useApplicationCommands = false,
bool requestToSpeak = false,
bool manageEvents = false,
bool manageThreads = false,
bool createPublicThreads = false,
bool createPrivateThreads = false,
@@ -267,6 +273,7 @@ namespace Discord
manageEmojisAndStickers: manageEmojisAndStickers,
useApplicationCommands: useApplicationCommands,
requestToSpeak: requestToSpeak,
manageEvents: manageEvents,
manageThreads: manageThreads,
createPublicThreads: createPublicThreads,
createPrivateThreads: createPrivateThreads,
@@ -310,6 +317,7 @@ namespace Discord
bool? manageEmojisAndStickers = null,
bool? useApplicationCommands = null,
bool? requestToSpeak = null,
bool? manageEvents = null,
bool? manageThreads = null,
bool? createPublicThreads = null,
bool? createPrivateThreads = null,
@@ -320,7 +328,7 @@ namespace Discord
viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles,
readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers,
useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers,
useApplicationCommands, requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads,
useApplicationCommands, requestToSpeak, manageEvents, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads,
startEmbeddedActivities);

/// <summary>
@@ -351,6 +359,18 @@ namespace Discord
return perms;
}

internal void Ensure(GuildPermission permissions)
{
if (!Has(permissions))
{
var vals = Enum.GetValues(typeof(GuildPermission)).Cast<GuildPermission>();
var currentValues = RawValue;
var missingValues = vals.Where(x => permissions.HasFlag(x) && !Permissions.GetValue(currentValues, x));

throw new InvalidOperationException($"Missing required guild permission{(missingValues.Count() > 1 ? "s" : "")} {string.Join(", ", missingValues.Select(x => x.ToString()))} in order to execute this operation.");
}
}

public override string ToString() => RawValue.ToString();
private string DebuggerDisplay => $"{string.Join(", ", ToList())}";
}


+ 7
- 0
src/Discord.Net.Core/Entities/Roles/IRole.cs View File

@@ -59,6 +59,13 @@ namespace Discord
/// </returns>
string Icon { get; }
/// <summary>
/// Gets the unicode emoji of this role.
/// </summary>
/// <remarks>
/// This field is mutually exclusive with <see cref="Icon"/>, either icon is set or emoji is set.
/// </remarks>
Emoji Emoji { get; }
/// <summary>
/// Gets the permissions granted to members of this role.
/// </summary>
/// <returns>


+ 1
- 22
src/Discord.Net.Core/Entities/Users/IUser.cs View File

@@ -10,18 +10,7 @@ namespace Discord
/// <summary>
/// Gets the identifier of this user's avatar.
/// </summary>
string AvatarId { get; }
/// <summary>
/// Gets the identifier of this user's banner.
/// </summary>
string BannerId { get; }
/// <summary>
/// Gets the user's banner color.
/// </summary>
/// <returns>
/// A <see cref="Color"/> struct representing the accent color of this user's banner.
/// </returns>
Color? AccentColor { get; }
string AvatarId { get; }
/// <summary>
/// Gets the avatar URL for this user.
/// </summary>
@@ -46,16 +35,6 @@ namespace Discord
/// </returns>
string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128);
/// <summary>
/// Gets the banner URL for this user.
/// </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.
/// </param>
/// <returns>
/// A string representing the user's avatar URL; <c>null</c> if the user does not have an banner in place.
/// </returns>
string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256);
/// <summary>
/// Gets the default avatar URL for this user.
/// </summary>
/// <remarks>


+ 16
- 0
src/Discord.Net.Core/Extensions/GuildExtensions.cs View File

@@ -20,5 +20,21 @@ namespace Discord
/// <returns> A <c>bool</c> indicating if the guild boost messages are enabled in the system channel. </returns>
public static bool GetGuildBoostMessagesEnabled(this IGuild guild)
=> !guild.SystemChannelFlags.HasFlag(SystemChannelMessageDeny.GuildBoost);

/// <summary>
/// Gets if guild setup system messages are enabled.
/// </summary>
/// <param name="guild"> The guild to check. </param>
/// <returns> A <c>bool</c> indicating if the guild setup messages are enabled in the system channel. </returns>
public static bool GetGuildSetupTipMessagesEnabled(this IGuild guild)
=> !guild.SystemChannelFlags.HasFlag(SystemChannelMessageDeny.GuildSetupTip);

/// <summary>
/// Gets if guild welcome messages have a reply with sticker button.
/// </summary>
/// <param name="guild"> The guild to check. </param>
/// <returns> A <c>bool</c> indicating if the guild welcome messages have a reply with sticker button. </returns>
public static bool GetGuildWelcomeMessageReplyEnabled(this IGuild guild)
=> !guild.SystemChannelFlags.HasFlag(SystemChannelMessageDeny.WelcomeMessageReply);
}
}

+ 32
- 0
src/Discord.Net.Core/Extensions/ObjectExtensions.cs View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
internal static class ObjectExtensions
{
public static bool IsNumericType(this object o)
{
switch (Type.GetTypeCode(o.GetType()))
{
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return true;
default:
return false;
}
}
}
}

+ 3
- 1
src/Discord.Net.Core/GatewayIntents.cs View File

@@ -39,13 +39,15 @@ namespace Discord
DirectMessageReactions = 1 << 13,
/// <summary> This intent includes TYPING_START </summary>
DirectMessageTyping = 1 << 14,
/// <summary> This intent includes GUILD_SCHEDULED_EVENT_CREATE, GUILD_SCHEDULED_EVENT_UPDATE, GUILD_SCHEDULED_EVENT_DELETE, GUILD_SCHEDULED_EVENT_USER_ADD, GUILD_SCHEDULED_EVENT_USER_REMOVE </summary>
GuildScheduledEvents = 1 << 16,
/// <summary>
/// This intent includes all but <see cref="GuildMembers"/> and <see cref="GuildPresences"/>
/// which are privileged and must be enabled in the Developer Portal.
/// </summary>
AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites |
GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages |
DirectMessageReactions | DirectMessageTyping,
DirectMessageReactions | DirectMessageTyping | GuildScheduledEvents,
/// <summary>
/// This intent includes all of them, including privileged ones.
/// </summary>


+ 6
- 51
src/Discord.Net.Core/Net/ApplicationCommandException.cs View File

@@ -1,60 +1,15 @@
using System;
using System.Linq;

namespace Discord.Net
{
public class ApplicationCommandException : Exception
[Obsolete("Please use HttpException instead of this. Will be removed in next major version.", false)]
public class ApplicationCommandException : HttpException
{
/// <summary>
/// Gets the JSON error code returned by Discord.
/// </summary>
/// <returns>
/// A
/// <see href="https://discord.com/developers/docs/topics/opcodes-and-status-codes#json">JSON error code</see>
/// from Discord, or <c>null</c> if none.
/// </returns>
public int? DiscordCode { get; }

/// <summary>
/// Gets the reason of the exception.
/// </summary>
public string Reason { get; }

/// <summary>
/// Gets the request object used to send the request.
/// </summary>
public IRequest Request { get; }

/// <summary>
/// The error object returned from discord.
/// </summary>
/// <remarks>
/// Note: This object can be null if discord didn't provide it.
/// </remarks>
public object Error { get; }

/// <summary>
/// The request json used to create the application command. This is useful for checking your commands for any format errors.
/// </summary>
public string RequestJson { get; }

/// <summary>
/// The underlying <see cref="HttpException"/> that caused this exception to be thrown.
/// </summary>
public HttpException InnerHttpException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationCommandException" /> class.
/// </summary>
/// <param name="requestJson"></param>
/// <param name="httpError"></param>
public ApplicationCommandException(string requestJson, HttpException httpError)
: base("The application command failed to be created!", httpError)
public ApplicationCommandException(HttpException httpError)
: base(httpError.HttpCode, httpError.Request, httpError.DiscordCode, httpError.Reason, httpError.Errors.ToArray())
{
Request = httpError.Request;
DiscordCode = httpError.DiscordCode;
Reason = httpError.Reason;
Error = httpError.Error;
RequestJson = requestJson;
InnerHttpException = httpError;
}
}
}

+ 8
- 9
src/Discord.Net.Core/Net/HttpException.cs View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Net;

namespace Discord.Net
@@ -25,7 +27,7 @@ namespace Discord.Net
/// <see href="https://discord.com/developers/docs/topics/opcodes-and-status-codes#json">JSON error code</see>
/// from Discord, or <c>null</c> if none.
/// </returns>
public int? DiscordCode { get; }
public DiscordErrorCode? DiscordCode { get; }
/// <summary>
/// Gets the reason of the exception.
/// </summary>
@@ -35,12 +37,9 @@ namespace Discord.Net
/// </summary>
public IRequest Request { get; }
/// <summary>
/// The error object returned from discord.
/// Gets a collection of json errors describing what went wrong with the request.
/// </summary>
/// <remarks>
/// Note: This object can be null if discord didn't provide it.
/// </remarks>
public object Error { get; }
public IReadOnlyCollection<DiscordJsonError> Errors { get; }

/// <summary>
/// Initializes a new instance of the <see cref="HttpException" /> class.
@@ -49,14 +48,14 @@ namespace Discord.Net
/// <param name="request">The request that was sent prior to the exception.</param>
/// <param name="discordCode">The Discord status code returned.</param>
/// <param name="reason">The reason behind the exception.</param>
public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null, object errors = null)
: base(CreateMessage(httpCode, discordCode, reason))
public HttpException(HttpStatusCode httpCode, IRequest request, DiscordErrorCode? discordCode = null, string reason = null, DiscordJsonError[] errors = null)
: base(CreateMessage(httpCode, (int?)discordCode, reason))
{
HttpCode = httpCode;
Request = request;
DiscordCode = discordCode;
Reason = reason;
Error = errors;
Errors = errors?.ToImmutableArray() ?? ImmutableArray<DiscordJsonError>.Empty;
}

private static string CreateMessage(HttpStatusCode httpCode, int? discordCode = null, string reason = null)


+ 56
- 0
src/Discord.Net.Core/Utils/Cacheable.cs View File

@@ -63,4 +63,60 @@ namespace Discord
/// </returns>
public async Task<TEntity> GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync().ConfigureAwait(false);
}
public struct Cacheable<TCachedEntity, TDownloadableEntity, TRelationship, TId>
where TCachedEntity : IEntity<TId>, TRelationship
where TDownloadableEntity : IEntity<TId>, TRelationship
where TId : IEquatable<TId>
{
/// <summary>
/// Gets whether this entity is cached.
/// </summary>
public bool HasValue { get; }
/// <summary>
/// Gets the ID of this entity.
/// </summary>
public TId Id { get; }
/// <summary>
/// Gets the entity if it could be pulled from cache.
/// </summary>
/// <remarks>
/// This value is not guaranteed to be set; in cases where the entity cannot be pulled from cache, it is
/// <c>null</c>.
/// </remarks>
public TCachedEntity Value { get; }
private Func<Task<TDownloadableEntity>> DownloadFunc { get; }

internal Cacheable(TCachedEntity value, TId id, bool hasValue, Func<Task<TDownloadableEntity>> downloadFunc)
{
Value = value;
Id = id;
HasValue = hasValue;
DownloadFunc = downloadFunc;
}

/// <summary>
/// Downloads this entity.
/// </summary>
/// <exception cref="Discord.Net.HttpException">Thrown when used from a user account.</exception>
/// <exception cref="NullReferenceException">Thrown when the message is deleted.</exception>
/// <returns>
/// A task that represents the asynchronous download operation. The task result contains the downloaded
/// entity.
/// </returns>
public async Task<TDownloadableEntity> DownloadAsync()
{
return await DownloadFunc().ConfigureAwait(false);
}

/// <summary>
/// Returns the cached entity if it exists; otherwise downloads it.
/// </summary>
/// <exception cref="Discord.Net.HttpException">Thrown when used from a user account.</exception>
/// <exception cref="NullReferenceException">Thrown when the message is deleted and is not in cache.</exception>
/// <returns>
/// A task that represents the asynchronous operation that attempts to get the message via cache or to
/// download the message. The task result contains the downloaded entity.
/// </returns>
public async Task<TRelationship> GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync().ConfigureAwait(false);
}
}

+ 6
- 2
src/Discord.Net.Rest/API/Common/Application.cs View File

@@ -7,7 +7,7 @@ namespace Discord.API
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("rpc_origins")]
public string[] RPCOrigins { get; set; }
public Optional<string[]> RPCOrigins { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("id")]
@@ -18,12 +18,16 @@ namespace Discord.API
public bool IsBotPublic { get; set; }
[JsonProperty("bot_require_code_grant")]
public bool BotRequiresCodeGrant { get; set; }
[JsonProperty("install_params")]
public Optional<InstallParams> InstallParams { get; set; }

[JsonProperty("team")]
public Team Team { get; set; }

[JsonProperty("flags"), Int53]
public Optional<ulong> Flags { get; set; }
public Optional<ApplicationFlags> Flags { get; set; }
[JsonProperty("owner")]
public Optional<User> Owner { get; set; }
public Optional<string[]> Tags { get; set; }
}
}

+ 10
- 0
src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs View File

@@ -29,6 +29,12 @@ namespace Discord.API
[JsonProperty("autocomplete")]
public Optional<bool> Autocomplete { get; set; }

[JsonProperty("min_value")]
public Optional<double> MinValue { get; set; }

[JsonProperty("max_value")]
public Optional<double> MaxValue { get; set; }

[JsonProperty("channel_types")]
public Optional<ChannelType[]> ChannelTypes { get; set; }

@@ -48,6 +54,8 @@ namespace Discord.API

Required = cmd.IsRequired ?? Optional<bool>.Unspecified;
Default = cmd.IsDefault ?? Optional<bool>.Unspecified;
MinValue = cmd.MinValue ?? Optional<double>.Unspecified;
MaxValue = cmd.MaxValue ?? Optional<double>.Unspecified;

Name = cmd.Name;
Type = cmd.Type;
@@ -66,6 +74,8 @@ namespace Discord.API
Required = option.IsRequired ?? Optional<bool>.Unspecified;

Default = option.IsDefault ?? Optional<bool>.Unspecified;
MinValue = option.MinValue ?? Optional<double>.Unspecified;
MaxValue = option.MaxValue ?? Optional<double>.Unspecified;

ChannelTypes = option.ChannelTypes?.ToArray() ?? Optional<ChannelType[]>.Unspecified;



+ 4
- 0
src/Discord.Net.Rest/API/Common/Attachment.cs View File

@@ -8,6 +8,10 @@ namespace Discord.API
public ulong Id { get; set; }
[JsonProperty("filename")]
public string Filename { get; set; }
[JsonProperty("description")]
public Optional<string> Description { get; set; }
[JsonProperty("content_type")]
public Optional<string> ContentType { get; set; }
[JsonProperty("size")]
public int Size { get; set; }
[JsonProperty("url")]


+ 20
- 0
src/Discord.Net.Rest/API/Common/DiscordError.cs View File

@@ -0,0 +1,20 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
[JsonConverter(typeof(Discord.Net.Converters.DiscordErrorConverter))]
internal class DiscordError
{
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("code")]
public DiscordErrorCode Code { get; set; }
[JsonProperty("errors")]
public Optional<ErrorDetails[]> Errors { get; set; }
}
}

+ 12
- 0
src/Discord.Net.Rest/API/Common/Error.cs View File

@@ -0,0 +1,12 @@
using Newtonsoft.Json;

namespace Discord.API
{
internal class Error
{
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
}

+ 3
- 1
src/Discord.Net.Rest/API/Common/Guild.cs View File

@@ -35,7 +35,7 @@ namespace Discord.API
[JsonProperty("emojis")]
public Emoji[] Emojis { get; set; }
[JsonProperty("features")]
public string[] Features { get; set; }
public GuildFeatures Features { get; set; }
[JsonProperty("mfa_level")]
public MfaLevel MfaLevel { get; set; }
[JsonProperty("application_id")]
@@ -81,5 +81,7 @@ namespace Discord.API
public NsfwLevel NsfwLevel { get; set; }
[JsonProperty("stickers")]
public Sticker[] Stickers { get; set; }
[JsonProperty("premium_progress_bar_enabled")]
public Optional<bool> IsBoostProgressBarEnabled { get; set; }
}
}

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

@@ -0,0 +1,43 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
internal class GuildScheduledEvent
{
[JsonProperty("id")]
public ulong Id { get; set; }
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
[JsonProperty("channel_id")]
public Optional<ulong?> ChannelId { get; set; }
[JsonProperty("creator_id")]
public Optional<ulong> CreatorId { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public Optional<string> Description { get; set; }
[JsonProperty("scheduled_start_time")]
public DateTimeOffset ScheduledStartTime { get; set; }
[JsonProperty("scheduled_end_time")]
public DateTimeOffset? ScheduledEndTime { get; set; }
[JsonProperty("privacy_level")]
public GuildScheduledEventPrivacyLevel PrivacyLevel { get; set; }
[JsonProperty("status")]
public GuildScheduledEventStatus Status { get; set; }
[JsonProperty("entity_type")]
public GuildScheduledEventType EntityType { get; set; }
[JsonProperty("entity_id")]
public ulong? EntityId { get; set; }
[JsonProperty("entity_metadata")]
public GuildScheduledEventEntityMetadata EntityMetadata { get; set; }
[JsonProperty("creator")]
public Optional<User> Creator { get; set; }
[JsonProperty("user_count")]
public Optional<int> UserCount { get; set; }
}
}

+ 15
- 0
src/Discord.Net.Rest/API/Common/GuildScheduledEventEntityMetadata.cs View File

@@ -0,0 +1,15 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
internal class GuildScheduledEventEntityMetadata
{
[JsonProperty("location")]
public Optional<string> Location { get; set; }
}
}

+ 19
- 0
src/Discord.Net.Rest/API/Common/GuildScheduledEventUser.cs View File

@@ -0,0 +1,19 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
internal class GuildScheduledEventUser
{
[JsonProperty("user")]
public User User { get; set; }
[JsonProperty("member")]
public Optional<GuildMember> Member { get; set; }
[JsonProperty("guild_scheduled_event_id")]
public ulong GuildScheduledEventId { get; set; }
}
}

+ 17
- 0
src/Discord.Net.Rest/API/Common/InstallParams.cs View File

@@ -0,0 +1,17 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
internal class InstallParams
{
[JsonProperty("scopes")]
public string[] Scopes { get; set; }
[JsonProperty("permissions")]
public ulong Permission { get; set; }
}
}

+ 17
- 0
src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs View File

@@ -0,0 +1,17 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
internal class ErrorDetails
{
[JsonProperty("name")]
public Optional<string> Name { get; set; }
[JsonProperty("errors")]
public Error[] Errors { get; set; }
}
}

+ 3
- 1
src/Discord.Net.Rest/API/Common/Role.cs View File

@@ -9,7 +9,9 @@ namespace Discord.API
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("icon")]
public string Icon { get; set; }
public Optional<string> Icon { get; set; }
[JsonProperty("unicode_emoji")]
public Optional<string> Emoji { get; set; }
[JsonProperty("color")]
public uint Color { get; set; }
[JsonProperty("hoist")]


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

@@ -0,0 +1,29 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API.Rest
{
internal class CreateGuildScheduledEventParams
{
[JsonProperty("channel_id")]
public Optional<ulong> ChannelId { get; set; }
[JsonProperty("entity_metadata")]
public Optional<GuildScheduledEventEntityMetadata> EntityMetadata { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("privacy_level")]
public GuildScheduledEventPrivacyLevel PrivacyLevel { get; set; }
[JsonProperty("scheduled_start_time")]
public DateTimeOffset StartTime { get; set; }
[JsonProperty("scheduled_end_time")]
public Optional<DateTimeOffset> EndTime { get; set; }
[JsonProperty("description")]
public Optional<string> Description { get; set; }
[JsonProperty("entity_type")]
public GuildScheduledEventType Type { get; set; }
}
}

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

@@ -68,6 +68,8 @@ namespace Discord.API.Rest
payload["embeds"] = Embeds.Value;
if (AllowedMentions.IsSpecified)
payload["allowed_mentions"] = AllowedMentions.Value;
if (Components.IsSpecified)
payload["components"] = Components.Value;

var json = new StringBuilder();
using (var text = new StringWriter(json))


+ 15
- 0
src/Discord.Net.Rest/API/Rest/GetEventUsersParams.cs View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API.Rest
{
internal class GetEventUsersParams
{
public Optional<int> Limit { get; set; }
public Optional<Direction> RelativeDirection { get; set; }
public Optional<ulong> RelativeUserId { get; set; }
}
}

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

@@ -35,5 +35,7 @@ namespace Discord.API.Rest
public Optional<SystemChannelMessageDeny> SystemChannelFlags { get; set; }
[JsonProperty("preferred_locale")]
public string PreferredLocale { get; set; }
[JsonProperty("premium_progress_bar_enabled")]
public Optional<bool> IsBoostProgressBarEnabled { get; set; }
}
}

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

@@ -0,0 +1,31 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API.Rest
{
internal class ModifyGuildScheduledEventParams
{
[JsonProperty("channel_id")]
public Optional<ulong?> ChannelId { get; set; }
[JsonProperty("entity_metadata")]
public Optional<GuildScheduledEventEntityMetadata> EntityMetadata { get; set; }
[JsonProperty("name")]
public Optional<string> Name { get; set; }
[JsonProperty("privacy_level")]
public Optional<GuildScheduledEventPrivacyLevel> PrivacyLevel { get; set; }
[JsonProperty("scheduled_start_time")]
public Optional<DateTimeOffset> StartTime { get; set; }
[JsonProperty("scheduled_end_time")]
public Optional<DateTimeOffset> EndTime { get; set; }
[JsonProperty("description")]
public Optional<string> Description { get; set; }
[JsonProperty("entity_type")]
public Optional<GuildScheduledEventType> Type { get; set; }
[JsonProperty("status")]
public Optional<GuildScheduledEventStatus> Status { get; set; }
}
}

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

@@ -9,5 +9,7 @@ namespace Discord.API.Rest
public Optional<int> Bitrate { get; set; }
[JsonProperty("user_limit")]
public Optional<int> UserLimit { get; set; }
[JsonProperty("rtc_region")]
public Optional<string> RTCRegion { get; set; }
}
}

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

@@ -11,6 +11,12 @@ namespace Discord.API.Rest
public ThreadArchiveDuration Duration { get; set; }

[JsonProperty("type")]
public Optional<ThreadType> Type { get; set; }
public ThreadType Type { get; set; }

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

[JsonProperty("rate_limit_per_user")]
public Optional<int?> Ratelimit { get; set; }
}
}

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

@@ -3,6 +3,7 @@ using Discord.Net.Rest;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Discord.API.Rest
@@ -11,9 +12,8 @@ namespace Discord.API.Rest
{
private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };

public Stream File { get; }
public FileAttachment[] Files { get; }

public Optional<string> Filename { get; set; }
public Optional<string> Content { get; set; }
public Optional<string> Nonce { get; set; }
public Optional<bool> IsTTS { get; set; }
@@ -21,22 +21,18 @@ namespace Discord.API.Rest
public Optional<AllowedMentions> AllowedMentions { get; set; }
public Optional<MessageReference> MessageReference { get; set; }
public Optional<ActionRowComponent[]> MessageComponent { get; set; }
public Optional<MessageFlags?> Flags { get; set; }
public Optional<ulong[]> Stickers { get; set; }
public bool IsSpoiler { get; set; } = false;

public UploadFileParams(Stream file)
public UploadFileParams(params Discord.FileAttachment[] attachments)
{
File = file;
Files = attachments;
}

public IReadOnlyDictionary<string, object> ToDictionary()
{
var d = new Dictionary<string, object>();
var filename = Filename.GetValueOrDefault("unknown.dat");
if (IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix))
filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix);
d["file"] = new MultipartFile(File, filename);

var payload = new Dictionary<string, object>();
if (Content.IsSpecified)
payload["content"] = Content.Value;
@@ -50,12 +46,33 @@ namespace Discord.API.Rest
payload["allowed_mentions"] = AllowedMentions.Value;
if (MessageComponent.IsSpecified)
payload["components"] = MessageComponent.Value;
if (IsSpoiler)
payload["hasSpoiler"] = IsSpoiler.ToString();
if (MessageReference.IsSpecified)
payload["message_reference"] = MessageReference.Value;
if (Stickers.IsSpecified)
payload["sticker_ids"] = Stickers.Value;
if (Flags.IsSpecified)
payload["flags"] = Flags;

List<object> attachments = new();

for(int n = 0; n != Files.Length; n++)
{
var attachment = Files[n];

var filename = attachment.FileName ?? "unknown.dat";
if (attachment.IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix))
filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix);
d[$"files[{n}]"] = new MultipartFile(attachment.Stream, filename);

attachments.Add(new
{
id = (ulong)n,
filename = filename,
description = attachment.Description ?? Optional<string>.Unspecified
});
}

payload["attachments"] = attachments;

var json = new StringBuilder();
using (var text = new StringWriter(json))


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

@@ -35,6 +35,7 @@ namespace Discord.Rest
public ISelfUser CurrentUser { get; protected set; }
/// <inheritdoc />
public TokenType TokenType => ApiClient.AuthTokenType;
internal bool UseInteractionSnowflakeDate { get; private set; }

/// <summary> Creates a new REST-only Discord client. </summary>
internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client)
@@ -47,6 +48,8 @@ namespace Discord.Rest
_restLogger = LogManager.CreateLogger("Rest");
_isFirstLogin = config.DisplayInitialLog;

UseInteractionSnowflakeDate = config.UseInteractionSnowflakeDate;

ApiClient.RequestQueue.RateLimitTriggered += async (id, info, endpoint) =>
{
if (info == null)


+ 134
- 10
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -339,9 +339,9 @@ namespace Discord.API
Preconditions.NotNull(args, nameof(args));
Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate));
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));
Preconditions.LessThan(args.Name.Length, 100, nameof(args.Name));
Preconditions.AtMost(args.Name.Length, 100, nameof(args.Name));
if (args.Topic.IsSpecified)
Preconditions.LessThan(args.Topic.Value.Length, 1024, nameof(args.Name));
Preconditions.AtMost(args.Topic.Value.Length, 1024, nameof(args.Name));

options = RequestOptions.CreateOrClone(options);

@@ -375,7 +375,7 @@ namespace Discord.API
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));

if(args.Name.IsSpecified)
Preconditions.LessThan(args.Name.Value.Length, 100, nameof(args.Name));
Preconditions.AtMost(args.Name.Value.Length, 100, nameof(args.Name));

options = RequestOptions.CreateOrClone(options);

@@ -391,9 +391,9 @@ namespace Discord.API
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));

if(args.Name.IsSpecified)
Preconditions.LessThan(args.Name.Value.Length, 100, nameof(args.Name));
Preconditions.AtMost(args.Name.Value.Length, 100, nameof(args.Name));
if(args.Topic.IsSpecified)
Preconditions.LessThan(args.Topic.Value.Length, 1024, nameof(args.Name));
Preconditions.AtMost(args.Topic.Value.Length, 1024, nameof(args.Name));

Preconditions.AtLeast(args.SlowModeInterval, 0, nameof(args.SlowModeInterval));
Preconditions.AtMost(args.SlowModeInterval, 21600, nameof(args.SlowModeInterval));
@@ -525,7 +525,19 @@ namespace Discord.API

var bucket = new BucketIds(channelId: channelId);

return await SendAsync<ThreadMember[]>("GET", () => $"channels/{channelId}/thread-members", bucket, options: options);
return await SendAsync<ThreadMember[]>("GET", () => $"channels/{channelId}/thread-members", bucket, options: options).ConfigureAwait(false);
}

public async Task<ThreadMember> GetThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
Preconditions.NotEqual(userId, 0, nameof(userId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(channelId: channelId);

return await SendAsync<ThreadMember>("GET", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false);
}

public async Task<ChannelThreads> GetActiveThreadsAsync(ulong channelId, RequestOptions options = null)
@@ -902,6 +914,19 @@ namespace Discord.API
var ids = new BucketIds(channelId: channelId);
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}

public async Task<Message> ModifyMessageAsync(ulong channelId, ulong messageId, Rest.UploadFileParams args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
Preconditions.NotEqual(messageId, 0, nameof(messageId));
Preconditions.NotNull(args, nameof(args));
if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(channelId: channelId);
return await SendMultipartAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
#endregion

#region Stickers, Reactions, Crosspost, and Acks
@@ -1162,7 +1187,7 @@ namespace Discord.API
{
Preconditions.NotNull(command, nameof(command));
Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name));
Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name));
Preconditions.AtLeast(command.Name.Length, 1, nameof(command.Name));

if (command.Type == ApplicationCommandType.Slash)
{
@@ -1233,7 +1258,7 @@ namespace Discord.API
{
Preconditions.NotNull(command, nameof(command));
Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name));
Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name));
Preconditions.AtLeast(command.Name.Length, 1, nameof(command.Name));

if (command.Type == ApplicationCommandType.Slash)
{
@@ -1884,6 +1909,105 @@ namespace Discord.API
}
#endregion

#region Guild Events

public async Task<GuildScheduledEvent[]> ListGuildScheduledEventsAsync(ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);
return await SendAsync<GuildScheduledEvent[]>("GET", () => $"guilds/{guildId}/scheduled-events?with_user_count=true", ids, options: options).ConfigureAwait(false);
}

public async Task<GuildScheduledEvent> GetGuildScheduledEventAsync(ulong eventId, ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(eventId, 0, nameof(eventId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);

return await NullifyNotFound<GuildScheduledEvent>(SendAsync<GuildScheduledEvent>("GET", () => $"guilds/{guildId}/scheduled-events/{eventId}?with_user_count=true", ids, options: options)).ConfigureAwait(false);
}

public async Task<GuildScheduledEvent> CreateGuildScheduledEventAsync(CreateGuildScheduledEventParams args, ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotNull(args, nameof(args));

options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);

return await SendJsonAsync<GuildScheduledEvent>("POST", () => $"guilds/{guildId}/scheduled-events", args, ids, options: options).ConfigureAwait(false);
}

public async Task<GuildScheduledEvent> ModifyGuildScheduledEventAsync(ModifyGuildScheduledEventParams args, ulong eventId, ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(eventId, 0, nameof(eventId));
Preconditions.NotNull(args, nameof(args));

options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);

return await SendJsonAsync<GuildScheduledEvent>("PATCH", () => $"guilds/{guildId}/scheduled-events/{eventId}", args, ids, options: options).ConfigureAwait(false);
}

public async Task DeleteGuildScheduledEventAsync(ulong eventId, ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(eventId, 0, nameof(eventId));

options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);

await SendAsync("DELETE", () => $"guilds/{guildId}/scheduled-events/{eventId}", ids, options: options).ConfigureAwait(false);
}

public async Task<GuildScheduledEventUser[]> GetGuildScheduledEventUsersAsync(ulong eventId, ulong guildId, int limit = 100, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(eventId, 0, nameof(eventId));

options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);

return await SendAsync<GuildScheduledEventUser[]>("GET", () => $"guilds/{guildId}/scheduled-events/{eventId}/users?limit={limit}&with_member=true", ids, options: options).ConfigureAwait(false);
}

public async Task<GuildScheduledEventUser[]> GetGuildScheduledEventUsersAsync(ulong eventId, ulong guildId, GetEventUsersParams args, RequestOptions options = null)
{
Preconditions.NotEqual(eventId, 0, nameof(eventId));
Preconditions.NotNull(args, nameof(args));
Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit));
Preconditions.AtMost(args.Limit, DiscordConfig.MaxMessagesPerBatch, nameof(args.Limit));
options = RequestOptions.CreateOrClone(options);

int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxGuildEventUsersPerBatch);
ulong? relativeId = args.RelativeUserId.IsSpecified ? args.RelativeUserId.Value : (ulong?)null;
var relativeDir = args.RelativeDirection.GetValueOrDefault(Direction.Before) switch
{
Direction.After => "after",
Direction.Around => "around",
_ => "before",
};
var ids = new BucketIds(guildId: guildId);
Expression<Func<string>> endpoint;
if (relativeId != null)
endpoint = () => $"guilds/{guildId}/scheduled-events/{eventId}/users?with_member=true&limit={limit}&{relativeDir}={relativeId}";
else
endpoint = () => $"guilds/{guildId}/scheduled-events/{eventId}/users?with_member=true&limit={limit}";

return await SendAsync<GuildScheduledEventUser[]>("GET", endpoint, ids, options: options).ConfigureAwait(false);
}

#endregion

#region Users
public async Task<User> GetUserAsync(ulong userId, RequestOptions options = null)
{
@@ -2111,7 +2235,7 @@ namespace Discord.API
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
throw new ApplicationCommandException(x);
}
}

@@ -2125,7 +2249,7 @@ namespace Discord.API
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
throw new ApplicationCommandException(x);
}

throw;


+ 72
- 2
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -1,8 +1,13 @@
//using Discord.Rest.Entities.Interactions;
using Discord.Net;
using Discord.Net.Converters;
using Discord.Net.ED25519;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Rest
@@ -14,12 +19,13 @@ namespace Discord.Rest
{
#region DiscordRestClient
private RestApplication _applicationInfo;
internal static JsonSerializer Serializer = new JsonSerializer() { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Include };

/// <summary>
/// Gets the logged-in user.
/// </summary>
public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; }
/// <inheritdoc />
public DiscordRestClient() : this(new DiscordRestConfig()) { }
/// <summary>
@@ -31,7 +37,7 @@ namespace Discord.Rest
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { }

private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, useSystemClock: config.UseSystemClock);
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock);

internal override void Dispose(bool disposing)
{
@@ -60,6 +66,70 @@ namespace Discord.Rest
return Task.Delay(0);
}

#region Rest interactions

public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body)
=> IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body)
{
var key = HexConverter.HexToByteArray(publicKey);
var sig = HexConverter.HexToByteArray(signature);
var tsp = Encoding.UTF8.GetBytes(timestamp);

var message = new List<byte>();
message.AddRange(tsp);
message.AddRange(body);

return IsValidHttpInteraction(key, sig, message.ToArray());
}

private bool IsValidHttpInteraction(byte[] publicKey, byte[] signature, byte[] message)
{
return Ed25519.Verify(signature, message, publicKey);
}

/// <summary>
/// Creates a <see cref="RestInteraction"/> from a http message.
/// </summary>
/// <param name="publicKey">The public key of your application</param>
/// <param name="signature">The signature sent with the interaction.</param>
/// <param name="timestamp">The timestamp sent with the interaction.</param>
/// <param name="body">The body of the http message.</param>
/// <returns>
/// A <see cref="RestInteraction"/> that represents the incoming http interaction.
/// </returns>
/// <exception cref="BadSignatureException">Thrown when the signature doesn't match the public key.</exception>
public Task<RestInteraction> ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body)
=> ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));

/// <summary>
/// Creates a <see cref="RestInteraction"/> from a http message.
/// </summary>
/// <param name="publicKey">The public key of your application</param>
/// <param name="signature">The signature sent with the interaction.</param>
/// <param name="timestamp">The timestamp sent with the interaction.</param>
/// <param name="body">The body of the http message.</param>
/// <returns>
/// A <see cref="RestInteraction"/> that represents the incoming http interaction.
/// </returns>
/// <exception cref="BadSignatureException">Thrown when the signature doesn't match the public key.</exception>
public async Task<RestInteraction> ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body)
{
if (!IsValidHttpInteraction(publicKey, signature, timestamp, body))
{
throw new BadSignatureException();
}

using (var textReader = new StringReader(Encoding.UTF8.GetString(body)))
using (var jsonReader = new JsonTextReader(textReader))
{
var model = Serializer.Deserialize<API.Interaction>(jsonReader);
return await RestInteraction.CreateAsync(this, model);
}
}

#endregion

public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null)
{
return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false);


+ 19
- 2
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

@@ -77,6 +77,7 @@ namespace Discord.Rest
{
Bitrate = args.Bitrate,
Name = args.Name,
RTCRegion = args.RTCRegion,
Position = args.Position,
CategoryId = args.CategoryId,
UserLimit = args.UserLimit.IsSpecified ? (args.UserLimit.Value ?? 0) : Optional.Create<int>(),
@@ -343,8 +344,19 @@ namespace Discord.Rest
}

/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client,
public static Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client,
Stream stream, string filename, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, RequestOptions options, bool isSpoiler, Embed[] embeds)
{
return SendFileAsync(channel, client, new FileAttachment(stream, filename, isSpoiler: isSpoiler), text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds);
}

/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public static Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client,
FileAttachment attachment, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, RequestOptions options, Embed[] embeds)
=> SendFilesAsync(channel, client, new[] { attachment }, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds);

public static async Task<RestUserMessage> SendFilesAsync(IMessageChannel channel, BaseDiscordClient client,
IEnumerable<FileAttachment> attachments, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, RequestOptions options, Embed[] embeds)
{
embeds ??= Array.Empty<Embed>();
if (embed != null)
@@ -355,6 +367,11 @@ namespace Discord.Rest
Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
Preconditions.NotNullOrEmpty(filename, nameof(filename), "File Name must not be empty or null");

foreach(var attachment in attachments)
{
Preconditions.NotNullOrEmpty(attachment.FileName, nameof(attachment.FileName), "File Name must not be empty or null");
}

// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
{
@@ -376,7 +393,7 @@ namespace Discord.Rest
Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed.");
}

var args = new UploadFileParams(stream) { Filename = filename, 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 = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, IsSpoiler = isSpoiler, 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 = component?.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 model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false);
return RestUserMessage.Create(client, channel, client.CurrentUser, model);
}


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

@@ -37,6 +37,23 @@ namespace Discord.Rest
_ => new RestChannel(discord, model.Id),
};
}
internal static RestChannel Create(BaseDiscordClient discord, Model model, IGuild guild)
{
return model.Type switch
{
ChannelType.News or
ChannelType.Text or
ChannelType.Voice or
ChannelType.Stage or
ChannelType.NewsThread or
ChannelType.PrivateThread or
ChannelType.PublicThread
=> RestGuildChannel.Create(discord, guild, model),
ChannelType.DM or ChannelType.Group => CreatePrivate(discord, model) as RestChannel,
ChannelType.Category => RestCategoryChannel.Create(discord, guild, model),
_ => new RestChannel(discord, model.Id),
};
}
/// <exception cref="InvalidOperationException">Unexpected channel type.</exception>
internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model)
{


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

Loading…
Cancel
Save