Merge Labs 3.X into dev (#1923)

* meta: bump version

* Null or empty fix (#176)

* Add components and stickers to ReplyAsync extension

* Fixed null or empty

* Changed Label to Description

* -||-

Co-authored-by: quin lynch <lynchquin@gmail.com>

* More regions (#177)

* Preconditions

* ChannelHelper

* RestDMChannel

* RestGroupChannel

* RestBan

* RestGroupUser

* EntityExtensions

* DiscordSocketClient

* DiscordSocketClient

* Discord.net.core.xml fix (#178)

* Changed Label to Description

* Added Discord- .MessageComponent .ISticker[]

,Discord.MessageComponent,Discord.ISticker[] to ReplyAsync

* Remove references to labs

* Update Discord.Net.sln

* Added SendMessagesInThreads and StartEmbeddedActivities. (#175)

* Added SendMessagesInThreads and StartEmbeddedActivities.

Adjusted owner perms.
Change UsePublicThreads -> CreatePublicThreads
Change UsePrivateThreads -> CreatePrivateThreads

* removed extra ///

* Added UsePublicThreads and UsePrivateThreads back with Obsolete Attribute

* removed 'false' from Obsolete Attribute

* Squashed commit of the following:

commit dca41a348e36a9b4e7006ef3a76377eb32aad276
Author: quin lynch <lynchquin@gmail.com>
Date:   Thu Sep 23 07:02:19 2021 -0300

    Autocomplete commands

* meta: xml. closes #171

* Revert user agent and $device to dnet

* meta: bump version

* meta: bump vers

* Fix sticker args

* Grammer fix (#179)

* Made IVoiceChannel mentionable

* Embeds array for send message async (#181)

* meta: bump version

* meta: bump vers

* Fix sticker args

* Grammer fix (#179)

* Added embeds for SendMessageAsync

* [JsonProperty("embed")] forgot to remove this

 public Optional<Embed> Embed { get; set; }

* It has been done as requested.

* Changed the old way of handeling single embeds

* Moved embeds param and added options param

* xmls

Co-authored-by: quin lynch <lynchquin@gmail.com>

* Fix thread permissions (#183)

* Update GuildPermissionsTests.cs

* Update GuildPermissions.cs

* Use compound assignment (#186)

* Used compound assignment

* -||-

* -||-

* Remove unnecessary suppression (#188)

* Inlined variable declarations (#185)

* Fixed some warnings (#184)

* Fixed some warnings

* Another fixed warning

* Changed the SSendFileAsync to SendFileAsync

* Removed para AlwaysAcknowledgeInteractions

* Moved it back to the previous version

* Added periods to the end like quin requested!! :((

Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com>

* Object initialization can be simplified fixed (#189)

* Conditional-expression-simplification (#193)

* Capitlazation fixes (#192)

* Removed-this. (#191)

* Use 'switch' expression (#187)

* Use 'switch' expression

* Reverted it to the old switch case

* Fixed-compiler-error (#194)

* Submitting updates to include new permissions. (#195)

* Submitting updates to include new permissions.

* Make old permissions obsolete and update tests

Co-authored-by: quin lynch <lynchquin@gmail.com>

* Update azure-pipelines.yml

* Update azure-pipelines.yml

* Update azure-pipelines.yml

* Add support for long in autocomplete option

* Add support for sending files with multiple embeds (#196)

* Add support for sending files with multiple embeds

* Simplify prepending single embed to embed array

* Consistency for embeds endpoints (#197)

* Changed the way of handling prepending of embeds.

For consistency.

* reformatted the summary

* Revert pipeline

* Fix duplicate merge conflicts

* Changed minimum slash command name length to 1 per Discord API docs (#198)

* Channel endpoints requirements correction (#199)

* Added some requirements to channels for topic

* Changed check from NotNullOrEmpty to NotNullOrEmpty

* Added some requirements to channels for name

Preconditions.LessThan

* Formatting of file

* Added restriction for description not being null (#200)

* Update azure-pipelines.yml

* Update deploy.yml

* Remove version tag from proj

* Update deploy.yml

* Removed versions from project files

* Removed style of the nuget badge and added logo (#201)

The style was not properly added to it and the plastic version does not look good with the discord badge.
I thought it would look better with a logo

* Fix Type not being set in SocketApplicationCommand

* Remove useless GuildId property

* meta: update XML

* Add Autocomplete to SlashCommandOptionBuilder

* Added autocomplete in SlashCommandOptionBuilder. (#206)

Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com>

* Fix duplicate autocomplete

* Fix #208

* Fix sub commands being interpreted as a parameter for autocomplete

* Fix exposed optional

* Support the discord:// protocol in buttons (#207)

* Update UrlValidation.cs

* Update ComponentBuilder.cs

* Add docs and better error messages.

* Fix wonky intentation

* Add competing activity status type (#205)

* Update GuildPermissionsTests.cs

* Update GuildPermissions.cs

* Add competing status type

* Add Icons to IRole (#204)

* Added icon field to IRole

* Added GetGuildRoleIconUrl()

* Added Clean Content Function (#174)

* Added Clean Content Function

* Fixed Spelling problems and bad var handling

* Add StripMarkDown Method

* Clean Content Expanded (#212)

* Implement CleanContent In IMessage & RestMessage

* Update Spelling and Documentation

* Add SanatizeMessage to MessageHelper and Refactor Rest and Socket Message

* Add event for autocomplete interaction (#214)

* Spelling corrections (#215)

* Remove null collections

* Followup with file async warnings (#216)

* Changed from NotNullOrWhitespace to NotNullOrEmpty

* Added NotNullOrEmpty on filename

* Added system to interpret from the path

* Added a check for if it contains a period

* It has been done, how ever it will break stuff

* Changed to use ??= how ever still added error check

* Added space under check

* Changed from with a period to valid file extension

* Added checks for SendFileAsync

* Removed filename != null &&

* Add channel types in application command options. (#217)

* add channel types in application command options

* Indent Docs

* Stage instance audit logs as well as thread audit log type

* Update azure-pipelines.yml

* Update azure-pipelines.yml

* Fix system messages not including mentioned users. Added ContextMenuCommand message type

* Remove file extension check (#218)

* Fix NRE in modify guild channel

* Fix 429's not being accounted for in ratelimit updates

* meta: add net5 framework

Co-Authored-By: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com>

* Proper doc logos (#221)

* Update GuildPermissionsTests.cs

* Update GuildPermissions.cs

* Add competing activity status type

* logo changes

* logo text as path

* add missing logo

* Update package logo and favicon

* Update docfx references

* Remove XML files and use original pipeline format

* Remove console writeline

* Remove Console.WriteLine

* Remove useless log

* Rename Available sticker field to IsAvailable

* Rename Available to IsAvailable in stickers

* Add summary indent for role members

* Add summary indent to SocketInvite

* Rename DefaultPermission to IsDefaultPermission

* Rename Default to IsDefault and Required to IsRequired in IApplicationCommandOption

* Rename Default and Required to IsDefault and IsRequired in IApplicationCommandOption. Rename DefaultPermission to IsDefaultPermission in IApplicationCommand

* Remove extra white spaces

* Renamed Joined, Archived, and Locked to HasJoined, IsArchived, and IsLocked

* Rename Live and DiscoverableDisabled to IsDiscoverableDisabled and IsLive in IStageChannel

* Remove newline

* Add indent to summaries

* Remove unnecessary json serializer field

* Fix ToEntity for roletags incorrectly using IsPremiumSubscriber

* Update RestChannel for new channel types

* Fix different rest channels not deserializing properly

* fully qualify internal for UrlValidation and add indent to summary

* Add missing periods to InteractionResponseType

* Fix summary in IApplicationCommandOptionChoice

* Update IApplicationCommandOption summaries

* Update IApplicationCommandInteractionDataOption summaries

* Update IApplicationCommandInteractionData summaries

* Update IApplicationCommand summaries

* Update ApplicationCommandType summaries

* rename DefaultPermission to IsDefaultPermission in ApplicationCommandProperties

* update ApplicationCommandOptionChoiceProperties summaries

* Rename Default, Required, and Autocomplete to IsDefault, IsRequired, and IsAutocomplete in ApplicationCommandOptionProperties

* Update SlashCommandProperties summaries

* update SlashCommandBuilder boolean field names, summaries, and choice parameters

* Update SelectMenuOption summaries, Rename Default to IsDefault in SelectMenuOption

* update SelectMenuComponent summaries. Rename Disabled to IsDisabled in SelectMenuComponent

* update ComponentBuilder summaries and boolean fields.

* Update ButtonComponent summaries and boolean fields

* update ActionRowComponent summaries

* Update UserCommandBuilder

* Update MessageCommandBuilder summaries and boolean properties

* Update IGuild summary

* Update IGuild summaries

* Update StagePrivacyLevel summary

* update IThreadChannel summaries

* Update IStageChannel summaries

* Refactor summaries and boolean property names

* General cleanup (#223)

* General cleanup

* Add Async suffix to SendAutocompleteResult

* Fix more formatting

* Fix unused RequestOptions in GetActiveThreadsAsync

* Add message to ArgumentNullException

* Ephemeral attachments

* Add missing jsonproperty attribute

* Add IMessage.Interaction

* Update attachment checks for embed urls

* meta: bump version

* Remove old package configs and update image

* Update package logos

* Fix logo reference for azure

* Deprecate old package definitions in favor for target file

* Deprecate old package definitions in favor for target file

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

* Update package ids

* Fix url validation

* meta: bump version

* Fix assignment of UserMentions (#233)

* Fix CleanContent (#231)

* Fix SocketSlashCommandData access modifier. (#237)

Fixes #229

* Update README with better header (#232)

* Update README with better header

Adds HTML elements that implement the main logo & improve the redirection tag positions.

* Resolving border issue in light-mode

* Update sponsor section

* Implement checks for interaction respond times and multiple interaction responses. closes #236, #235

* Add response check to socket auto complete

* meta: bump versions

* Fix #239

* meta: bump version

* meta: update logo

* meta: bump versions

* Revert received at time, confirmed by discord staff to be accurate

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

Update requested changes of obsolete and references to labs.

Added `Interaction` to `IMessage`
Fixed grammar
Fixed bugs relating to interactions.

* Update docs

* Update CHANGELOG.md

* meta: docs building

* Update docs.yml

* Update docs.yml

* Fix docfx version

* Update docs.yml

* Update docs.bat

* Rename docs repo for clone

* update docfx version

* Update docs.bat

* Update docfx version

* Remove docs from pipeline

* FAQ revamped, metadata updated (#241)

* FAQ revamped, metadata updated

* Update FAQ.md

* Update README.md

* Docs index improvement

* Fix InvalidOperationException in modify channel

* feature: guild avatars, closes #238

* feature: modify role icons

* meta: changelog

* meta: bump version

* Update README.md

* Fix non value type options not being included in autocomplete

* Add new activity flags (#254)

* Add new activity flags

* Add missing commas

* Added support for GUILD_JOIN_REQUEST_DELETE event (#253)

Fixes #247

* Adding BotHTTPInteraction user flag (#252)

* animated guild banner support (#255)

* Docs work (WIP) (#242)

* Main page work

* Metadata logo dir

* More main page edits

* Naming change

* Dnet guide entries pruned

* Add student hub guild directory channel (#256)

* animated guild banner support

* Add guild directory channel

* Fix followup with file overwrite having incorrect parameter locations

* Merge labs 3.x

* Update GUILD_JOIN_REQUEST_DELETE event

* Update head.tmpl.partial

* Removed BannerId and AccentColor  (#260)

* Removed BannerId property, GetBannerURL method, and AccentColor property from IUser and socket entities.

* Fixed errors in IUser.cs

* Added back summary for GetAvatarUrl method in IUser.cs

* Support Guild Boost Progress Bars (#262)

* Support Guild Boost Progress Bars

* Update SocketChannel.cs

* Fix non-optional and unnecessary values.

* Spelling

* Reordering and consistency.

* Remove log for reconnect

* Add missing flags to SystemChannelMessageDeny (#267)

* Fix labs reference in analyzer project and provider project

* Rename new activity flags

* Guild feature revamp and smart gateway intent checks

* Get thread user implementation

* Amend creating slash command guide (#269)

* Adding BotHTTPInteraction user flag

* Added comments explaining the Global command create stipulations.

* Fix numeric type check for options

* Add state checking to ConnectionManager.StartAsync (#272)

* initial interface changes

* Multi file upload + attachment editing

* meta: bump versions

* Update CHANGELOG.md

* Update CHANGELOG.md

* Support Min and Max values on ApplicationCommandOptions (#273)

* Support Min and Max values on ApplicationCommandOptions

* Support decimal min/max values

* Docs imrpovments + use ToNullable

* Logomark, doc settings edit (#258)

* Logomark, doc settings edit

* Replace standard logo

* Bumping docfx plugins to latest release

* Bump version metadata

* Logo svg fix

* Change default sticker behavior and add AlwaysResolveSticker to the config

* Implement rest based interactions. Added ED25519 checks. Updated summaries.

* Update package logo

* Automatically fix ordering of optional command options (#276)

* auto fix optional command option order

* clean up indentation

* Fix maximum number of Select Menu Options (#282)

As of https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure the maximum number of options is 25, not less than 25. Hopefully the change catches all necessary locations

* Add voice region to modify voice channels

* Update summaries on rest interactions

* Interaction Specific Interfaces (#283)

* added interaction specific interfaces

* fix build error

* implement change requests

* Update application

* Add Guild Scheduled Events (#279)

* guild events initial

* sharded events

* Add new gateway intents and fix bugs

* More work on new changes to guild events

* Update guild scheduled events

* Added events to extended guild and add event start event

* Update preconditions

* Implement breaking changes guild guild events. Add guild event permissions

* Update tests and change privacy level requirements

* Update summaries and add docs for guild events

* meta: bump version

* Increment meta version (#285)

* Increment meta version

* Update docfx.json

* Fix #289 and add configureawaits to rest based interactions

* meta: bump version

* Add GUILD_SCHEDULED_EVENT_USER_ADD and GUILD_SCHEDULED_EVENT_USER_REMOVE (#287)

* Remove newline

* Fix autocomplete result value

* meta: bump versions

* Add `GuildScheduledEventUserAdd` and `GuildScheduledEventUserRemove` to sharded client

* Make RestUserCommand public (#292)

* Fix Components not showing on FUWF (#288) (#293)

Adds Components to Payload JSON Generation

* Implement smarter rest resolvable interaction data. Fixes #294

* Add UseInteractionSnowflakeDate to config #286

* Implement Better Discord Errors (#291)

* Initial error parsing

* Implement better errors

* Add missing error codes

* Add voice disconnect opcodes

* Remove unused class, add summaries to discordjsonerror, and remove public constructor of slash command properties

* Add error code summary

* Update error message summary

* Update src/Discord.Net.Core/DiscordJsonError.cs

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

* Update src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

* Fix autocomplete result value

Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>

* Change the minimum length of slash commands to 1 (#284)

* Change the minimum length of slash commands to 1. This is the correct value according to the docs and it has been changed after user feedback.

* Fix the limit in 3 other places

Co-authored-by: quin lynch <lynchquin@gmail.com>

* Add new thread creation properties

* Add role emoji. Fixes #295

* Fix mocked text channel

* Fix precondition checks. Closes #281

* Initial fix (#297)

* meta: bump version

* Update from release/3.x

* Remove more labs references

* Remove doc file for Discord.Net.Analyzers

Co-authored-by: Simon Hjorthøj <sh2@live.dk>
Co-authored-by: drobbins329 <drobbins329@gmail.com>
Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com>
Co-authored-by: d4n3436 <dan3436@hotmail.com>
Co-authored-by: Will <WilliamWelsh@users.noreply.github.com>
Co-authored-by: Eugene Garbuzov <kkxo.mail@gmail.com>
Co-authored-by: CottageDwellingCat <80918250+CottageDwellingCat@users.noreply.github.com>
Co-authored-by: Emily <89871431+emillly-b@users.noreply.github.com>
Co-authored-by: marens101 <marens101@gmail.com>
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
Co-authored-by: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Co-authored-by: Bill <billchirico@gmail.com>
Co-authored-by: Liege72 <65319395+Liege72@users.noreply.github.com>
Co-authored-by: Floowey <floowey@gmx.at>
Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com>
Co-authored-by: exsersewo <exsersewo@systemexit.co.uk>
Co-authored-by: Dennis Fischer <fischer_dennis@live.de>
This commit is contained in:
Quin Lynch
2021-11-23 09:58:05 -04:00
committed by GitHub
parent 3395700720
commit 933ea42eaa
591 changed files with 34402 additions and 1465 deletions

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" />
<PropertyGroup>
<AssemblyName>Discord.Net.Analyzers</AssemblyName>
@@ -7,7 +7,7 @@
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.3.1" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.11.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Discord.Net.Commands\Discord.Net.Commands.csproj" />

View File

@@ -16,7 +16,7 @@ namespace Discord.Commands
/// <code language="cs">
/// [Command("stats")]
/// [Alias("stat", "info")]
/// public async Task GetStatsAsync(IUser user)
/// public <see langword="async"/> Task GetStatsAsync(IUser user)
/// {
/// // ...pull stats
/// }

View File

@@ -7,6 +7,7 @@ namespace Discord.Commands.Builders
{
public class CommandBuilder
{
#region CommandBuilder
private readonly List<PreconditionAttribute> _preconditions;
private readonly List<ParameterBuilder> _parameters;
private readonly List<Attribute> _attributes;
@@ -27,8 +28,9 @@ namespace Discord.Commands.Builders
public IReadOnlyList<ParameterBuilder> Parameters => _parameters;
public IReadOnlyList<Attribute> Attributes => _attributes;
public IReadOnlyList<string> Aliases => _aliases;
#endregion
//Automatic
#region Automatic
internal CommandBuilder(ModuleBuilder module)
{
Module = module;
@@ -38,7 +40,9 @@ namespace Discord.Commands.Builders
_attributes = new List<Attribute>();
_aliases = new List<string>();
}
//User-defined
#endregion
#region User-defined
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> callback)
: this(module)
{
@@ -132,7 +136,7 @@ namespace Discord.Commands.Builders
var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple);
if ((firstMultipleParam != null) && (firstMultipleParam != lastParam))
throw new InvalidOperationException($"Only the last parameter in a command may have the Multiple flag. Parameter: {firstMultipleParam.Name} in {PrimaryAlias}");
var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder);
if ((firstRemainderParam != null) && (firstRemainderParam != lastParam))
throw new InvalidOperationException($"Only the last parameter in a command may have the Remainder flag. Parameter: {firstRemainderParam.Name} in {PrimaryAlias}");
@@ -140,5 +144,6 @@ namespace Discord.Commands.Builders
return new CommandInfo(this, info, service);
}
#endregion
}
}

View File

@@ -7,6 +7,7 @@ namespace Discord.Commands.Builders
{
public class ModuleBuilder
{
#region ModuleBuilder
private readonly List<CommandBuilder> _commands;
private readonly List<ModuleBuilder> _submodules;
private readonly List<PreconditionAttribute> _preconditions;
@@ -27,8 +28,9 @@ namespace Discord.Commands.Builders
public IReadOnlyList<string> Aliases => _aliases;
internal TypeInfo TypeInfo { get; set; }
#endregion
//Automatic
#region Automatic
internal ModuleBuilder(CommandService service, ModuleBuilder parent)
{
Service = service;
@@ -40,7 +42,9 @@ namespace Discord.Commands.Builders
_attributes = new List<Attribute>();
_aliases = new List<string>();
}
//User-defined
#endregion
#region User-defined
internal ModuleBuilder(CommandService service, ModuleBuilder parent, string primaryAlias)
: this(service, parent)
{
@@ -132,5 +136,6 @@ namespace Discord.Commands.Builders
public ModuleInfo Build(CommandService service, IServiceProvider services) => BuildImpl(service, services);
internal ModuleInfo Build(CommandService service, IServiceProvider services, ModuleInfo parent) => BuildImpl(service, services, parent);
#endregion
}
}

View File

@@ -116,7 +116,7 @@ namespace Discord.Commands
builder.AddAliases(alias.Aliases);
break;
case GroupAttribute group:
builder.Name = builder.Name ?? group.Prefix;
builder.Name ??= group.Prefix;
builder.Group = group.Prefix;
builder.AddAliases(group.Prefix);
break;
@@ -158,7 +158,7 @@ namespace Discord.Commands
case CommandAttribute command:
builder.AddAliases(command.Text);
builder.RunMode = command.RunMode;
builder.Name = builder.Name ?? command.Text;
builder.Name ??= command.Text;
builder.IgnoreExtraArgs = command.IgnoreExtraArgs ?? service._ignoreExtraArgs;
break;
case NameAttribute name:
@@ -291,7 +291,7 @@ namespace Discord.Commands
return reader;
}
//We dont have a cached type reader, create one
//We don't have a cached type reader, create one
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, services);
service.AddTypeReader(paramType, reader, false);

View File

@@ -8,6 +8,7 @@ namespace Discord.Commands.Builders
{
public class ParameterBuilder
{
#region ParameterBuilder
private readonly List<ParameterPreconditionAttribute> _preconditions;
private readonly List<Attribute> _attributes;
@@ -24,8 +25,9 @@ namespace Discord.Commands.Builders
public IReadOnlyList<ParameterPreconditionAttribute> Preconditions => _preconditions;
public IReadOnlyList<Attribute> Attributes => _attributes;
#endregion
//Automatic
#region Automatic
internal ParameterBuilder(CommandBuilder command)
{
_preconditions = new List<ParameterPreconditionAttribute>();
@@ -33,7 +35,9 @@ namespace Discord.Commands.Builders
Command = command;
}
//User-defined
#endregion
#region User-defined
internal ParameterBuilder(CommandBuilder command, string name, Type type)
: this(command)
{
@@ -127,10 +131,11 @@ namespace Discord.Commands.Builders
internal ParameterInfo Build(CommandInfo info)
{
if ((TypeReader ?? (TypeReader = GetReader(ParameterType))) == null)
if ((TypeReader ??= GetReader(ParameterType)) == null)
throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified");
return new ParameterInfo(this, info, Command.Module.Service);
}
#endregion
}
}

View File

@@ -29,6 +29,7 @@ namespace Discord.Commands
/// </remarks>
public class CommandService : IDisposable
{
#region CommandService
/// <summary>
/// Occurs when a command-related information is received.
/// </summary>
@@ -131,8 +132,9 @@ namespace Discord.Commands
entityTypeReaders.Add((typeof(IUser), typeof(UserTypeReader<>)));
_entityTypeReaders = entityTypeReaders.ToImmutable();
}
#endregion
//Modules
#region Modules
public async Task<ModuleInfo> CreateModuleAsync(string primaryAlias, Action<ModuleBuilder> buildFunc)
{
await _moduleLock.WaitAsync().ConfigureAwait(false);
@@ -187,7 +189,7 @@ namespace Discord.Commands
/// </returns>
public async Task<ModuleInfo> AddModuleAsync(Type type, IServiceProvider services)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;
await _moduleLock.WaitAsync().ConfigureAwait(false);
try
@@ -222,7 +224,7 @@ namespace Discord.Commands
/// </returns>
public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly, IServiceProvider services)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;
await _moduleLock.WaitAsync().ConfigureAwait(false);
try
@@ -322,8 +324,9 @@ namespace Discord.Commands
return true;
}
#endregion
//Type Readers
#region Type Readers
/// <summary>
/// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
/// type.
@@ -448,8 +451,9 @@ namespace Discord.Commands
}
return null;
}
#endregion
//Execution
#region Execution
/// <summary>
/// Searches for the command.
/// </summary>
@@ -503,7 +507,7 @@ namespace Discord.Commands
/// </returns>
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;
var searchResult = Search(input);
if (!searchResult.IsSuccess)
@@ -598,11 +602,13 @@ namespace Discord.Commands
//If we get this far, at least one parse was successful. Execute the most likely overload.
var chosenOverload = successfulParses[0];
var result = await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false);
if (!result.IsSuccess && !(result is RuntimeResult || result is ExecuteResult)) // succesful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deffered execution)
if (!result.IsSuccess && !(result is RuntimeResult || result is ExecuteResult)) // successful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deferred execution)
await _commandExecutedEvent.InvokeAsync(chosenOverload.Key.Command, context, result);
return result;
}
#endregion
#region Dispose
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
@@ -620,5 +626,6 @@ namespace Discord.Commands
{
Dispose(true);
}
#endregion
}
}

View File

@@ -12,4 +12,4 @@
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -51,8 +51,7 @@ namespace Discord.Commands
if (endPos == -1) return false;
if (text.Length < endPos + 2 || text[endPos + 1] != ' ') return false; //Must end in "> "
ulong userId;
if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out userId)) return false;
if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out ulong userId)) return false;
if (userId == user.Id)
{
argPos = endPos + 2;

View File

@@ -123,7 +123,7 @@ namespace Discord.Commands
public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;
async Task<PreconditionResult> CheckGroups(IEnumerable<PreconditionAttribute> preconditions, string type)
{
@@ -164,7 +164,7 @@ namespace Discord.Commands
public async Task<ParseResult> ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;
if (!searchResult.IsSuccess)
return ParseResult.FromError(searchResult);
@@ -201,7 +201,7 @@ namespace Discord.Commands
}
public async Task<IResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;
try
{

View File

@@ -75,7 +75,7 @@ namespace Discord.Commands
public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, object arg, IServiceProvider services = null)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;
foreach (var precondition in Preconditions)
{
@@ -89,7 +89,7 @@ namespace Discord.Commands
public async Task<TypeReaderResult> ParseAsync(ICommandContext context, string input, IServiceProvider services = null)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;
return await _reader.ReadAsync(context, input, services).ConfigureAwait(false);
}

View File

@@ -16,6 +16,7 @@ namespace Discord.Commands
public abstract class ModuleBase<T> : IModuleBase
where T : class, ICommandContext
{
#region ModuleBase
/// <summary>
/// The underlying context of the command.
/// </summary>
@@ -35,10 +36,14 @@ namespace Discord.Commands
/// Specifies if notifications are sent for mentioned users and roles in the <paramref name="message"/>.
/// If <c>null</c>, all mentioned roles and users will be notified.
/// </param>
/// <param name="options">The request options for this <see langword="async"/> request.</param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
/// <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>
protected virtual async Task<IUserMessage> ReplyAsync(string message = 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)
{
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false);
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false);
}
/// <summary>
/// The method to execute before executing the command.
@@ -63,8 +68,9 @@ namespace Discord.Commands
protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder)
{
}
#endregion
//IModuleBase
#region IModuleBase
void IModuleBase.SetContext(ICommandContext context)
{
var newValue = context as T;
@@ -73,5 +79,6 @@ namespace Discord.Commands
void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command);
void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command);
void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder);
#endregion
}
}

View File

@@ -8,7 +8,7 @@ namespace Discord.Commands
public enum RunMode
{
/// <summary>
/// The default behaviour set in <see cref="CommandServiceConfig"/>.
/// The default behavior set in <see cref="CommandServiceConfig"/>.
/// </summary>
Default,
/// <summary>

View File

@@ -46,6 +46,32 @@ namespace Discord
string extension = FormatToExtension(format, avatarId);
return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}";
}
public static string GetGuildUserAvatarUrl(ulong userId, ulong guildId, string avatarId, ushort size, ImageFormat format)
{
if (avatarId == null)
return null;
string extension = FormatToExtension(format, avatarId);
return $"{DiscordConfig.CDNUrl}guilds/{guildId}/users/{userId}/avatars/{avatarId}.{extension}?size={size}";
}
/// <summary>
/// Returns a user banner URL.
/// </summary>
/// <param name="userId">The user snowflake identifier.</param>
/// <param name="bannerId">The banner identifier.</param>
/// <param name="size">The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048.</param>
/// <param name="format">The format to return.</param>
/// <returns>
/// A URL pointing to the user's banner in the specified size.
/// </returns>
public static string GetUserBannerUrl(ulong userId, string bannerId, ushort size, ImageFormat format)
{
if (bannerId == null)
return null;
string extension = FormatToExtension(format, bannerId);
return $"{DiscordConfig.CDNUrl}banners/{userId}/{bannerId}.{extension}?size={size}";
}
/// <summary>
/// Returns the default user avatar URL.
/// </summary>
@@ -68,6 +94,16 @@ namespace Discord
public static string GetGuildIconUrl(ulong guildId, string iconId)
=> iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null;
/// <summary>
/// Returns a guild role's icon URL.
/// </summary>
/// <param name="roleId">The role identifier.</param>
/// <param name="roleHash">The icon hash.</param>
/// <returns>
/// A URL pointing to the guild role's icon.
/// </returns>
public static string GetGuildRoleIconUrl(ulong roleId, string roleHash)
=> roleHash != null ? $"{DiscordConfig.CDNUrl}role-icons/{roleId}/{roleHash}.png" : null;
/// <summary>
/// Returns a guild splash URL.
/// </summary>
/// <param name="guildId">The guild snowflake identifier.</param>
@@ -103,15 +139,17 @@ namespace Discord
/// </summary>
/// <param name="guildId">The guild snowflake identifier.</param>
/// <param name="bannerId">The banner image identifier.</param>
/// <param name="format">The format to return.</param>
/// <param name="size">The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048 inclusive.</param>
/// <returns>
/// A URL pointing to the guild's banner image.
/// </returns>
public static string GetGuildBannerUrl(ulong guildId, string bannerId, ushort? size = null)
public static string GetGuildBannerUrl(ulong guildId, string bannerId, ImageFormat format, ushort? size = null)
{
if (!string.IsNullOrEmpty(bannerId))
return $"{DiscordConfig.CDNUrl}banners/{guildId}/{bannerId}.jpg" + (size.HasValue ? $"?size={size}" : string.Empty);
return null;
if (string.IsNullOrEmpty(bannerId))
return null;
string extension = FormatToExtension(format, bannerId);
return $"{DiscordConfig.CDNUrl}banners/{guildId}/{bannerId}.{extension}" + (size.HasValue ? $"?size={size}" : string.Empty);
}
/// <summary>
/// Returns an emoji URL.
@@ -159,23 +197,39 @@ namespace Discord
public static string GetSpotifyDirectUrl(string trackId)
=> $"https://open.spotify.com/track/{trackId}";
/// <summary>
/// Gets a stickers url based off the id and format.
/// </summary>
/// <param name="stickerId">The id of the sticker.</param>
/// <param name="format">The format of the sticker.</param>
/// <returns>
/// A URL to the sticker.
/// </returns>
public static string GetStickerUrl(ulong stickerId, StickerFormatType format = StickerFormatType.Png)
=> $"{DiscordConfig.CDNUrl}stickers/{stickerId}.{FormatToExtension(format)}";
private static string FormatToExtension(StickerFormatType format)
{
return format switch
{
StickerFormatType.None or StickerFormatType.Png or StickerFormatType.Apng => "png", // In the case of the Sticker endpoint, the sticker will be available as PNG if its format_type is PNG or APNG, and as Lottie if its format_type is LOTTIE.
StickerFormatType.Lottie => "lottie",
_ => throw new ArgumentException(nameof(format)),
};
}
private static string FormatToExtension(ImageFormat format, string imageId)
{
if (format == ImageFormat.Auto)
format = imageId.StartsWith("a_") ? ImageFormat.Gif : ImageFormat.Png;
switch (format)
return format switch
{
case ImageFormat.Gif:
return "gif";
case ImageFormat.Jpeg:
return "jpeg";
case ImageFormat.Png:
return "png";
case ImageFormat.WebP:
return "webp";
default:
throw new ArgumentException(nameof(format));
}
ImageFormat.Gif => "gif",
ImageFormat.Jpeg => "jpeg",
ImageFormat.Png => "png",
ImageFormat.WebP => "webp",
_ => throw new ArgumentException(nameof(format)),
};
}
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" />
<Import Project="../../StyleAnalyzer.targets"/>
<PropertyGroup>
@@ -16,4 +16,4 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>

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

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

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

View File

@@ -33,6 +33,18 @@ namespace Discord
/// <summary>
/// Indicates that a user can play this song.
/// </summary>
Play = 0b100000
Play = 0b100000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel with friends.
/// </summary>
PartyPrivacyFriends = 0b1000000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel.
/// </summary>
PartyPrivacyVoiceChannel = 0b10000000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel.
/// </summary>
Embedded = 0b10000000
}
}

View File

@@ -25,5 +25,9 @@ namespace Discord
/// The user has set a custom status.
/// </summary>
CustomStatus = 4,
/// <summary>
/// The user is competing in a game.
/// </summary>
Competing = 5,
}
}

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

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

View File

@@ -142,5 +142,55 @@ namespace Discord
/// A message was unpinned from this guild.
/// </summary>
MessageUnpinned = 75,
/// <summary>
/// A integration was created
/// </summary>
IntegrationCreated = 80,
/// <summary>
/// A integration was updated
/// </summary>
IntegrationUpdated = 81,
/// <summary>
/// An integration was deleted
/// </summary>
IntegrationDeleted = 82,
/// <summary>
/// A stage instance was created.
/// </summary>
StageInstanceCreated = 83,
/// <summary>
/// A stage instance was updated.
/// </summary>
StageInstanceUpdated = 84,
/// <summary>
/// A stage instance was deleted.
/// </summary>
StageInstanceDeleted = 85,
/// <summary>
/// A sticker was created.
/// </summary>
StickerCreated = 90,
/// <summary>
/// A sticker was updated.
/// </summary>
StickerUpdated = 91,
/// <summary>
/// A sticker was deleted.
/// </summary>
StickerDeleted = 92,
/// <summary>
/// A thread was created.
/// </summary>
ThreadCreate = 110,
/// <summary>
/// A thread was updated.
/// </summary>
ThreadUpdate = 111,
/// <summary>
/// A thread was deleted.
/// </summary>
ThreadDelete = 112
}
}

View File

@@ -14,6 +14,18 @@ namespace Discord
/// <summary> The channel is a category channel. </summary>
Category = 4,
/// <summary> The channel is a news channel. </summary>
News = 5
News = 5,
/// <summary> The channel is a store channel. </summary>
Store = 6,
/// <summary> The channel is a temporary thread channel under a news channel. </summary>
NewsThread = 10,
/// <summary> The channel is a temporary thread channel under a text channel. </summary>
PublicThread = 11,
/// <summary> The channel is a private temporary thread channel under a text channel. </summary>
PrivateThread = 12,
/// <summary> The channel is a stage voice channel. </summary>
Stage = 13,
/// <summary> The channel is a guild directory used in hub servers. (Unreleased)</summary>
GuildDirectory = 14
}
}

View File

@@ -28,11 +28,14 @@ namespace Discord
/// 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 message.</param>
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
Task<IUserMessage> SendMessageAsync(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 file to this message channel with an optional caption.
/// </summary>
@@ -65,11 +68,14 @@ namespace Discord
/// 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(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null);
/// <summary>
/// Sends a file to this message channel with an optional caption.
/// </summary>
@@ -99,11 +105,72 @@ namespace Discord
/// 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(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);
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.

View File

@@ -12,7 +12,7 @@ namespace Discord
/// Gets the parent (category) ID of this channel in the guild's channel list.
/// </summary>
/// <returns>
/// A <see cref="ulong"/> representing the snowflake identifier of the parent of this channel;
/// A <see cref="ulong"/> representing the snowflake identifier of the parent of this channel;
/// <c>null</c> if none is set.
/// </returns>
ulong? CategoryId { get; }
@@ -56,6 +56,50 @@ namespace Discord
/// metadata object containing information for the created invite.
/// </returns>
Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null);
/// <summary>
/// Creates a new invite to this channel.
/// </summary>
/// <example>
/// <para>The following example creates a new invite to this channel; the invite lasts for 12 hours and can only
/// be used 3 times throughout its lifespan.</para>
/// <code language="cs">
/// await guildChannel.CreateInviteAsync(maxAge: 43200, maxUses: 3);
/// </code>
/// </example>
/// <param name="applicationId">The id of the embedded application to open for this invite.</param>
/// <param name="maxAge">The time (in seconds) until the invite expires. Set to <c>null</c> to never expire.</param>
/// <param name="maxUses">The max amount of times this invite may be used. Set to <c>null</c> to have unlimited uses.</param>
/// <param name="isTemporary">If <c>true</c>, the user accepting this invite will be kicked from the guild after closing their client.</param>
/// <param name="isUnique">If <c>true</c>, don't try to reuse a similar invite (useful for creating many unique one time use invites).</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous invite creation operation. The task result contains an invite
/// metadata object containing information for the created invite.
/// </returns>
Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null);
/// <summary>
/// Creates a new invite to this channel.
/// </summary>
/// <example>
/// <para>The following example creates a new invite to this channel; the invite lasts for 12 hours and can only
/// be used 3 times throughout its lifespan.</para>
/// <code language="cs">
/// await guildChannel.CreateInviteAsync(maxAge: 43200, maxUses: 3);
/// </code>
/// </example>
/// <param name="user">The id of the user whose stream to display for this invite.</param>
/// <param name="maxAge">The time (in seconds) until the invite expires. Set to <c>null</c> to never expire.</param>
/// <param name="maxUses">The max amount of times this invite may be used. Set to <c>null</c> to have unlimited uses.</param>
/// <param name="isTemporary">If <c>true</c>, the user accepting this invite will be kicked from the guild after closing their client.</param>
/// <param name="isUnique">If <c>true</c>, don't try to reuse a similar invite (useful for creating many unique one time use invites).</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous invite creation operation. The task result contains an invite
/// metadata object containing information for the created invite.
/// </returns>
Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null);
/// <summary>
/// Gets a collection of all invites to this channel.
/// </summary>B

View File

@@ -0,0 +1,114 @@
using System;
using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// Represents a generic Stage Channel.
/// </summary>
public interface IStageChannel : IVoiceChannel
{
/// <summary>
/// Gets the topic of the Stage instance.
/// </summary>
/// <remarks>
/// If the stage isn't live then this property will be set to <see langword="null"/>.
/// </remarks>
string Topic { get; }
/// <summary>
/// Gets the <see cref="StagePrivacyLevel"/> of the current stage.
/// </summary>
/// <remarks>
/// If the stage isn't live then this property will be set to <see langword="null"/>.
/// </remarks>
StagePrivacyLevel? PrivacyLevel { get; }
/// <summary>
/// Gets whether or not stage discovery is disabled.
/// </summary>
bool? IsDiscoverableDisabled { get; }
/// <summary>
/// Gets whether or not the stage is live.
/// </summary>
bool IsLive { get; }
/// <summary>
/// Starts the stage, creating a stage instance.
/// </summary>
/// <param name="topic">The topic for the stage/</param>
/// <param name="privacyLevel">The privacy level of the stage.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous start operation.
/// </returns>
Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null);
/// <summary>
/// Modifies the current stage instance.
/// </summary>
/// <param name="func">The properties to modify the stage instance with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modify operation.
/// </returns>
Task ModifyInstanceAsync(Action<StageInstanceProperties> func, RequestOptions options = null);
/// <summary>
/// Stops the stage, deleting the stage instance.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous stop operation.
/// </returns>
Task StopStageAsync(RequestOptions options = null);
/// <summary>
/// Indicates that the bot would like to speak within a stage channel.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous request to speak operation.
/// </returns>
Task RequestToSpeakAsync(RequestOptions options = null);
/// <summary>
/// Makes the current user become a speaker within a stage.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous speaker modify operation.
/// </returns>
Task BecomeSpeakerAsync(RequestOptions options = null);
/// <summary>
/// Makes the current user a listener.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous stop operation.
/// </returns>
Task StopSpeakingAsync(RequestOptions options = null);
/// <summary>
/// Makes a user a speaker within a stage.
/// </summary>
/// <param name="user">The user to make the speaker.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous move operation.
/// </returns>
Task MoveToSpeakerAsync(IGuildUser user, RequestOptions options = null);
/// <summary>
/// Removes a user from speaking.
/// </summary>
/// <param name="user">The user to remove from speaking.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous remove operation.
/// </returns>
Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = null);
}
}

View File

@@ -114,5 +114,40 @@ namespace Discord
/// of webhooks that is available in this channel.
/// </returns>
Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null);
/// <summary>
/// Creates a thread within this <see cref="ITextChannel"/>.
/// </summary>
/// <remarks>
/// When <paramref name="message"/> is <see langword="null"/> the thread type will be based off of the
/// channel its created in. When called on a <see cref="ITextChannel"/>, it creates a <see cref="ThreadType.PublicThread"/>.
/// When called on a <see cref="INewsChannel"/>, it creates a <see cref="ThreadType.NewsThread"/>. The id of the created
/// thread will be the same as the id of the message, and as such a message can only have a
/// single thread created from it.
/// </remarks>
/// <param name="name">The name of the thread.</param>
/// <param name="type">
/// The type of the thread.
/// <para>
/// <b>Note: </b>This parameter is not used if the <paramref name="message"/> parameter is not specified.
/// </para>
/// </param>
/// <param name="autoArchiveDuration">
/// The duration on which this thread archives after.
/// <para>
/// <b>Note: </b> Options <see cref="ThreadArchiveDuration.OneWeek"/> and <see cref="ThreadArchiveDuration.ThreeDays"/>
/// are only available for guilds that are boosted. You can check in the <see cref="IGuild.Features"/> to see if the
/// guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>.
/// </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, bool? invitable = null, int? slowmode = null, RequestOptions options = null);
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// Represents a thread channel inside of a guild.
/// </summary>
public interface IThreadChannel : ITextChannel
{
/// <summary>
/// Gets the type of the current thread channel.
/// </summary>
ThreadType Type { get; }
/// <summary>
/// Gets whether or not the current user has joined this thread.
/// </summary>
bool HasJoined { get; }
/// <summary>
/// Gets whether or not the current thread is archived.
/// </summary>
bool IsArchived { get; }
/// <summary>
/// Gets the duration of time before the thread is automatically archived after no activity.
/// </summary>
ThreadArchiveDuration AutoArchiveDuration { get; }
/// <summary>
/// Gets the timestamp when the thread's archive status was last changed, used for calculating recent activity.
/// </summary>
DateTimeOffset ArchiveTimestamp { get; }
/// <summary>
/// Gets whether or not the current thread is locked.
/// </summary>
bool IsLocked { get; }
/// <summary>
/// Gets an approximate count of users in a thread, stops counting after 50.
/// </summary>
int MemberCount { get; }
/// <summary>
/// Gets an approximate count of messages in a thread, stops counting after 50.
/// </summary>
int MessageCount { get; }
/// <summary>
/// Joins the current thread.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous join operation.
/// </returns>
Task JoinAsync(RequestOptions options = null);
/// <summary>
/// Leaves the current thread.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous leave operation.
/// </returns>
Task LeaveAsync(RequestOptions options = null);
/// <summary>
/// Adds a user to this thread.
/// </summary>
/// <param name="user">The <see cref="IGuildUser"/> to add.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous operation of adding a member to a thread.
/// </returns>
Task AddUserAsync(IGuildUser user, RequestOptions options = null);
/// <summary>
/// Removes a user from this thread.
/// </summary>
/// <param name="user">The <see cref="IGuildUser"/> to remove from this thread.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous operation of removing a user from this thread.
/// </returns>
Task RemoveUserAsync(IGuildUser user, RequestOptions options = null);
}
}

View File

@@ -0,0 +1,18 @@
namespace Discord
{
/// <summary>
/// Represents properties to use when modifying a stage instance.
/// </summary>
public class StageInstanceProperties
{
/// <summary>
/// Gets or sets the topic of the stage.
/// </summary>
public Optional<string> Topic { get; set; }
/// <summary>
/// Gets or sets the privacy level of the stage.
/// </summary>
public Optional<StagePrivacyLevel> PrivacyLevel { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
namespace Discord
{
/// <summary>
/// Represents the privacy level of a stage.
/// </summary>
public enum StagePrivacyLevel
{
/// <summary>
/// The Stage instance is visible publicly, such as on Stage Discovery.
/// </summary>
Public = 1,
/// <summary>
/// The Stage instance is visible to only guild members.
/// </summary>
GuildOnly = 2
}
}

View File

@@ -38,5 +38,21 @@ namespace Discord
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception>
public Optional<int> SlowModeInterval { get; set; }
/// <summary>
/// Gets or sets whether or not the thread is archived.
/// </summary>
public Optional<bool> Archived { get; set; }
/// <summary>
/// Gets or sets whether or not the thread is locked.
/// </summary>
public Optional<bool> Locked { get; set; }
/// <summary>
/// Gets or sets the auto archive duration.
/// </summary>
public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
namespace Discord
{
/// <summary>
/// Represents the thread auto archive duration.
/// </summary>
public enum ThreadArchiveDuration
{
/// <summary>
/// One hour (60 minutes).
/// </summary>
OneHour = 60,
/// <summary>
/// One day (1440 minutes).
/// </summary>
OneDay = 1440,
/// <summary>
/// Three days (4320 minutes).
/// <remarks>
/// This option is explicitly available to nitro users.
/// </remarks>
/// </summary>
ThreeDays = 4320,
/// <summary>
/// One week (10080 minutes).
/// <remarks>
/// This option is explicitly available to nitro users.
/// </remarks>
/// </summary>
OneWeek = 10080
}
}

View File

@@ -0,0 +1,23 @@
namespace Discord
{
/// <summary>
/// Represents types of threads.
/// </summary>
public enum ThreadType
{
/// <summary>
/// Represents a temporary sub-channel within a GUILD_NEWS channel.
/// </summary>
NewsThread = 10,
/// <summary>
/// Represents a temporary sub-channel within a GUILD_TEXT channel.
/// </summary>
PublicThread = 11,
/// <summary>
/// Represents a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
/// </summary>
PrivateThread = 12
}
}

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

File diff suppressed because it is too large Load Diff

View File

@@ -102,5 +102,7 @@ namespace Discord
/// A string representing the raw presentation of the emote (e.g. <c>&lt;:thonkang:282745590985523200&gt;</c>).
/// </returns>
public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>";
public static implicit operator Emote(string s) => Parse(s);
}
}

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,
}
}

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

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

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,
}
}

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,
}
}

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,
}
}

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

View File

@@ -2,6 +2,7 @@ using Discord.Audio;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
namespace Discord
@@ -199,12 +200,19 @@ namespace Discord
/// </returns>
IReadOnlyCollection<GuildEmote> Emotes { get; }
/// <summary>
/// Gets a collection of all extra features added to this guild.
/// Gets a collection of all custom stickers for this guild.
/// </summary>
/// <returns>
/// A read-only collection of enabled features in this guild.
/// A read-only collection of all custom stickers for this guild.
/// </returns>
IReadOnlyCollection<string> Features { get; }
IReadOnlyCollection<ICustomSticker> Stickers { get; }
/// <summary>
/// Gets the features for this guild.
/// </summary>
/// <returns>
/// A flags enum containing all the features for the guild.
/// </returns>
GuildFeatures Features { get; }
/// <summary>
/// Gets a collection of all roles in this guild.
/// </summary>
@@ -316,6 +324,14 @@ namespace Discord
/// </returns>
string PreferredLocale { get; }
/// <summary>
/// Gets the NSFW level of this guild.
/// </summary>
/// <returns>
/// The NSFW level of this guild.
/// </returns>
NsfwLevel NsfwLevel { get; }
/// <summary>
/// Gets the preferred culture of this guild.
/// </summary>
@@ -323,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.
@@ -522,6 +545,27 @@ namespace Discord
/// </returns>
Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets a stage channel in this guild.
/// </summary>
/// <param name="id">The snowflake identifier for the stage channel.</param>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the stage channel associated
/// with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns>
Task<IStageChannel> GetStageChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets a collection of all stage channels in this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
/// stage channels found within this guild.
/// </returns>
Task<IReadOnlyCollection<IStageChannel>> GetStageChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets the AFK voice channel in this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
@@ -572,15 +616,35 @@ namespace Discord
/// </returns>
Task<ITextChannel> GetRulesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets the text channel channel where admins and moderators of Community guilds receive notices from Discord.
/// Gets the text channel where admins and moderators of Community guilds receive notices from Discord.
/// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the text channel channel where
/// A task that represents the asynchronous get operation. The task result contains the text channel where
/// admins and moderators of Community guilds receive notices from Discord; <see langword="null" /> if none is set.
/// </returns>
Task<ITextChannel> GetPublicUpdatesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets a thread channel within this guild.
/// </summary>
/// <param name="id">The id of the thread channel.</param>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the thread channel.
/// </returns>
Task<IThreadChannel> GetThreadChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets a collection of all thread channels in this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
/// thread channels found within this guild.
/// </returns>
Task<IReadOnlyCollection<IThreadChannel>> GetThreadChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Creates a new text channel in this guild.
@@ -610,6 +674,17 @@ namespace Discord
/// </returns>
Task<IVoiceChannel> CreateVoiceChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null);
/// <summary>
/// Creates a new stage channel in this guild.
/// </summary>
/// <param name="name">The new name for the stage channel.</param>
/// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the newly created
/// stage channel.
/// </returns>
Task<IStageChannel> CreateStageChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null);
/// <summary>
/// Creates a new channel category in this guild.
/// </summary>
/// <param name="name">The new name for the category.</param>
@@ -703,6 +778,12 @@ namespace Discord
/// <returns>A guild user associated with the specified <paramref name="userId" />; <see langword="null" /> if the user is already in the guild.</returns>
Task<IGuildUser> AddGuildUserAsync(ulong userId, string accessToken, Action<AddGuildUserProperties> func = null, RequestOptions options = null);
/// <summary>
/// Disconnects the user from its current voice channel.
/// </summary>
/// <param name="user">The user to disconnect.</param>
/// <returns>A task that represents the asynchronous operation for disconnecting a user.</returns>
Task DisconnectAsync(IGuildUser user);
/// <summary>
/// Gets a collection of all users in this guild.
/// </summary>
/// <remarks>
@@ -760,7 +841,7 @@ namespace Discord
/// Downloads all users for this guild if the current list is incomplete.
/// </summary>
/// <remarks>
/// This method downloads all users found within this guild throught the Gateway and caches them.
/// This method downloads all users found within this guild through the Gateway and caches them.
/// </remarks>
/// <returns>
/// A task that represents the asynchronous download operation.
@@ -883,6 +964,15 @@ namespace Discord
/// emote.
/// </returns>
Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null);
/// <summary>
/// Moves the user to the voice channel.
/// </summary>
/// <param name="user">The user to move.</param>
/// <param name="targetChannel">the channel where the user gets moved to.</param>
/// <returns>A task that represents the asynchronous operation for moving a user.</returns>
Task MoveAsync(IGuildUser user, IVoiceChannel targetChannel);
/// <summary>
/// Deletes an existing <see cref="GuildEmote"/> from this guild.
/// </summary>
@@ -892,5 +982,174 @@ namespace Discord
/// A task that represents the asynchronous removal operation.
/// </returns>
Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null);
/// <summary>
/// Creates a new sticker in this guild.
/// </summary>
/// <param name="name">The name of the sticker.</param>
/// <param name="description">The description of the sticker.</param>
/// <param name="tags">The tags of the sticker.</param>
/// <param name="image">The image of the new emote.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the created sticker.
/// </returns>
Task<ICustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image, RequestOptions options = null);
/// <summary>
/// Creates a new sticker in this guild.
/// </summary>
/// <param name="name">The name of the sticker.</param>
/// <param name="description">The description of the sticker.</param>
/// <param name="tags">The tags of the sticker.</param>
/// <param name="path">The path of the file to upload.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the created sticker.
/// </returns>
Task<ICustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, string path, RequestOptions options = null);
/// <summary>
/// Creates a new sticker in this guild.
/// </summary>
/// <param name="name">The name of the sticker.</param>
/// <param name="description">The description of the sticker.</param>
/// <param name="tags">The tags of the sticker.</param>
/// <param name="stream">The stream containing the file data.</param>
/// <param name="filename">The name of the file <b>with</b> the extension, ex: image.png.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the created sticker.
/// </returns>
Task<ICustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, Stream stream, string filename, RequestOptions options = null);
/// <summary>
/// Gets a specific sticker within this guild.
/// </summary>
/// <param name="id">The id of the sticker to get.</param>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the sticker found with the
/// specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns>
Task<ICustomSticker> GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets a collection of all stickers within this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of stickers found within the guild.
/// </returns>
Task<IReadOnlyCollection<ICustomSticker>> GetStickersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Deletes a sticker within this guild.
/// </summary>
/// <param name="sticker">The sticker to delete.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous removal operation.
/// </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>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of application commands found within the guild.
/// </returns>
Task<IReadOnlyCollection<IApplicationCommand>> GetApplicationCommandsAsync(RequestOptions options = null);
/// <summary>
/// Gets an application command within this guild with the specified id.
/// </summary>
/// <param name="id">The id of the application command to get.</param>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A ValueTask that represents the asynchronous get operation. The task result contains a <see cref="IApplicationCommand"/>
/// if found, otherwise <see langword="null"/>.
/// </returns>
Task<IApplicationCommand> GetApplicationCommandAsync(ulong id, CacheMode mode = CacheMode.AllowDownload,
RequestOptions options = null);
/// <summary>
/// Creates an application command within this guild.
/// </summary>
/// <param name="properties">The properties to use when creating the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the command that was created.
/// </returns>
Task<IApplicationCommand> CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null);
/// <summary>
/// Overwrites the application commands within this guild.
/// </summary>
/// <param name="properties">A collection of properties to use when creating the commands.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created.
/// </returns>
Task<IReadOnlyCollection<IApplicationCommand>> BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties,
RequestOptions options = null);
}
}

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

View File

@@ -0,0 +1,22 @@
namespace Discord
{
public enum NsfwLevel
{
/// <summary>
/// Default or unset.
/// </summary>
Default = 0,
/// <summary>
/// Guild has extremely suggestive or mature content that would only be suitable for users 18 or over.
/// </summary>
Explicit = 1,
/// <summary>
/// Guild has no content that could be deemed NSFW; in other words, SFW.
/// </summary>
Safe = 2,
/// <summary>
/// Guild has mildly NSFW content that may not be suitable for users under 18.
/// </summary>
AgeRestricted = 3
}
}

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

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>

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Discord
{
/// <summary>
/// Represents a <see cref="IApplicationCommandOption"/> for making slash commands.
/// </summary>
public class ApplicationCommandOptionProperties
{
private string _name;
private string _description;
/// <summary>
/// Gets or sets the name of this option.
/// </summary>
public string Name
{
get => _name;
set
{
if (value == null)
throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null.");
if (value.Length > 32)
throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 32.");
if (!Regex.IsMatch(value, @"^[\w-]{1,32}$"))
throw new FormatException($"{nameof(value)} must match the regex ^[\\w-]{{1,32}}$");
_name = value;
}
}
/// <summary>
/// Gets or sets the description of this option.
/// </summary>
public string Description
{
get => _description;
set => _description = value?.Length switch
{
> 100 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be less than or equal to 100."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be at least 1."),
_ => value
};
}
/// <summary>
/// Gets or sets the type of this option.
/// </summary>
public ApplicationCommandOptionType Type { get; set; }
/// <summary>
/// Gets or sets whether or not this options is the first required option for the user to complete. only one option can be default.
/// </summary>
public bool? IsDefault { get; set; }
/// <summary>
/// Gets or sets if the option is required.
/// </summary>
public bool? IsRequired { get; set; }
/// <summary>
/// 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>
public List<ApplicationCommandOptionChoiceProperties> Choices { get; set; }
/// <summary>
/// Gets or sets if this option is a subcommand or subcommand group type, these nested options will be the parameters.
/// </summary>
public List<ApplicationCommandOptionProperties> Options { get; set; }
/// <summary>
/// Gets or sets the allowed channel types for this option.
/// </summary>
public List<ChannelType> ChannelTypes { get; set; }
}
}

View File

@@ -0,0 +1,44 @@
using System;
namespace Discord
{
/// <summary>
/// Represents a choice for a <see cref="IApplicationCommandInteractionDataOption"/>. This class is used when making new commands.
/// </summary>
public class ApplicationCommandOptionChoiceProperties
{
private string _name;
private object _value;
/// <summary>
/// Gets or sets the name of this choice.
/// </summary>
public string Name
{
get => _name;
set => _name = value?.Length switch
{
> 100 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 100."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must at least 1."),
_ => value
};
}
/// <summary>
/// Gets the value of this choice.
/// <note type="warning">
/// Discord only accepts int, double/floats, and string as the input.
/// </note>
/// </summary>
public object Value
{
get => _value;
set
{
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;
}
}
}
}

View File

@@ -0,0 +1,58 @@
namespace Discord
{
/// <summary>
/// The option type of the Slash command parameter, See <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptiontype">the discord docs</see>.
/// </summary>
public enum ApplicationCommandOptionType : byte
{
/// <summary>
/// A sub command.
/// </summary>
SubCommand = 1,
/// <summary>
/// A group of sub commands.
/// </summary>
SubCommandGroup = 2,
/// <summary>
/// A <see langword="string"/> of text.
/// </summary>
String = 3,
/// <summary>
/// An <see langword="int"/>.
/// </summary>
Integer = 4,
/// <summary>
/// A <see langword="bool"/>.
/// </summary>
Boolean = 5,
/// <summary>
/// A <see cref="IUser"/>.
/// </summary>
User = 6,
/// <summary>
/// A <see cref="IGuildChannel"/>.
/// </summary>
Channel = 7,
/// <summary>
/// A <see cref="IRole"/>.
/// </summary>
Role = 8,
/// <summary>
/// A <see cref="IUser"/> or <see cref="IRole"/>.
/// </summary>
Mentionable = 9,
/// <summary>
/// A <see cref="double"/>.
/// </summary>
Number = 10
}
}

View File

@@ -0,0 +1,22 @@
namespace Discord
{
/// <summary>
/// Represents the base class to create/modify application commands.
/// </summary>
public abstract class ApplicationCommandProperties
{
internal abstract ApplicationCommandType Type { get; }
/// <summary>
/// Gets or sets the name of this command.
/// </summary>
public Optional<string> Name { get; set; }
/// <summary>
/// Gets or sets whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/>
/// </summary>
public Optional<bool> IsDefaultPermission { get; set; }
internal ApplicationCommandProperties() { }
}
}

View File

@@ -0,0 +1,23 @@
namespace Discord
{
/// <summary>
/// Represents the types of application commands.
/// </summary>
public enum ApplicationCommandType : byte
{
/// <summary>
/// A Slash command type
/// </summary>
Slash = 1,
/// <summary>
/// A Context Menu User command type
/// </summary>
User = 2,
/// <summary>
/// A Context Menu Message command type
/// </summary>
Message = 3
}
}

View File

@@ -0,0 +1,36 @@
namespace Discord
{
/// <summary>
/// Represents an autocomplete option.
/// </summary>
public class AutocompleteOption
{
/// <summary>
/// Gets the type of this option.
/// </summary>
public ApplicationCommandOptionType Type { get; }
/// <summary>
/// Gets the name of the option.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the value of the option.
/// </summary>
public object Value { get; }
/// <summary>
/// Gets whether or not this option is focused by the executing user.
/// </summary>
public bool Focused { get; }
internal AutocompleteOption(ApplicationCommandOptionType type, string name, object value, bool focused)
{
Type = type;
Name = name;
Value = value;
Focused = focused;
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
namespace Discord
{
/// <summary>
/// Represents a result to an autocomplete interaction.
/// </summary>
public class AutocompleteResult
{
private object _value;
private string _name;
/// <summary>
/// Gets or sets the name of the result.
/// </summary>
/// <remarks>
/// Name cannot be null and has to be between 1-100 characters in length.
/// </remarks>
/// <exception cref="ArgumentNullException"/>
/// <exception cref="ArgumentException"/>
public string Name
{
get => _name;
set
{
if (value == null)
throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null.");
_name = value.Length switch
{
> 100 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 100."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must be at least 1."),
_ => value
};
}
}
/// <summary>
/// Gets or sets the value of the result.
/// </summary>
/// <remarks>
/// Only <see cref="string"/>, <see cref="int"/>, and <see cref="double"/> are allowed for a value.
/// </remarks>
/// <exception cref="ArgumentNullException"/>
/// <exception cref="ArgumentException"/>
public object Value
{
get => _value;
set
{
if (value is not string && !value.IsNumericType())
throw new ArgumentException($"{nameof(value)} must be a numeric type or a string!");
_value = value;
}
}
/// <summary>
/// Creates a new <see cref="AutocompleteResult"/>.
/// </summary>
public AutocompleteResult() { }
/// <summary>
/// Creates a new <see cref="AutocompleteResult"/> with the passed in <paramref name="name"/> and <paramref name="value"/>.
/// </summary>
/// <exception cref="ArgumentNullException"/>
/// <exception cref="ArgumentException"/>
public AutocompleteResult(string name, object value)
{
Name = name;
Value = value;
}
}
}

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

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

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

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

View File

@@ -0,0 +1,77 @@
namespace Discord
{
/// <summary>
/// A class used to build Message commands.
/// </summary>
public class MessageCommandBuilder
{
/// <summary>
/// Returns the maximum length a commands name allowed by Discord
/// </summary>
public const int MaxNameLength = 32;
/// <summary>
/// Gets or sets the name of this Message command.
/// </summary>
public string Name
{
get => _name;
set
{
Preconditions.NotNullOrEmpty(value, nameof(Name));
Preconditions.AtLeast(value.Length, 1, nameof(Name));
Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name));
_name = value;
}
}
/// <summary>
/// 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;
private string _name;
/// <summary>
/// Build the current builder into a <see cref="MessageCommandProperties"/> class.
/// </summary>
/// <returns>
/// A <see cref="MessageCommandProperties"/> that can be used to create message commands.
/// </returns>
public MessageCommandProperties Build()
{
var props = new MessageCommandProperties
{
Name = Name,
IsDefaultPermission = IsDefaultPermission
};
return props;
}
/// <summary>
/// Sets the field name.
/// </summary>
/// <param name="name">The value to set the field name to.</param>
/// <returns>
/// The current builder.
/// </returns>
public MessageCommandBuilder WithName(string name)
{
Name = name;
return this;
}
/// <summary>
/// Sets the default permission of the current command.
/// </summary>
/// <param name="isDefaultPermission">The default permission value to set.</param>
/// <returns>The current builder.</returns>
public MessageCommandBuilder WithDefaultPermission(bool isDefaultPermission)
{
IsDefaultPermission = isDefaultPermission;
return this;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Discord
{
/// <summary>
/// A class used to create message commands.
/// </summary>
public class MessageCommandProperties : ApplicationCommandProperties
{
internal override ApplicationCommandType Type => ApplicationCommandType.Message;
}
}

View File

@@ -0,0 +1,75 @@
namespace Discord
{
/// <summary>
/// A class used to build user commands.
/// </summary>
public class UserCommandBuilder
{
/// <summary>
/// Returns the maximum length a commands name allowed by Discord.
/// </summary>
public const int MaxNameLength = 32;
/// <summary>
/// Gets or sets the name of this User command.
/// </summary>
public string Name
{
get => _name;
set
{
Preconditions.NotNullOrEmpty(value, nameof(Name));
Preconditions.AtLeast(value.Length, 1, nameof(Name));
Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name));
_name = value;
}
}
/// <summary>
/// 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;
private string _name;
/// <summary>
/// Build the current builder into a <see cref="UserCommandProperties"/> class.
/// </summary>
/// <returns>A <see cref="UserCommandProperties"/> that can be used to create user commands.</returns>
public UserCommandProperties Build()
{
var props = new UserCommandProperties
{
Name = Name,
IsDefaultPermission = IsDefaultPermission
};
return props;
}
/// <summary>
/// Sets the field name.
/// </summary>
/// <param name="name">The value to set the field name to.</param>
/// <returns>
/// The current builder.
/// </returns>
public UserCommandBuilder WithName(string name)
{
Name = name;
return this;
}
/// <summary>
/// Sets the default permission of the current command.
/// </summary>
/// <param name="isDefaultPermission">The default permission value to set.</param>
/// <returns>The current builder.</returns>
public UserCommandBuilder WithDefaultPermission(bool isDefaultPermission)
{
IsDefaultPermission = isDefaultPermission;
return this;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Discord
{
/// <summary>
/// A class used to create User commands.
/// </summary>
public class UserCommandProperties : ApplicationCommandProperties
{
internal override ApplicationCommandType Type => ApplicationCommandType.User;
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// The base command model that belongs to an application.
/// </summary>
public interface IApplicationCommand : ISnowflakeEntity, IDeletable
{
/// <summary>
/// Gets the unique id of the parent application.
/// </summary>
ulong ApplicationId { get; }
/// <summary>
/// Gets the type of the command.
/// </summary>
ApplicationCommandType Type { get; }
/// <summary>
/// Gets the name of the command.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the description of the command.
/// </summary>
string Description { get; }
/// <summary>
/// Gets whether the command is enabled by default when the app is added to a guild.
/// </summary>
bool IsDefaultPermission { get; }
/// <summary>
/// Gets a collection of options for this application command.
/// </summary>
IReadOnlyCollection<IApplicationCommandOption> Options { get; }
/// <summary>
/// Modifies the current application command.
/// </summary>
/// <param name="func">The new properties to use when modifying the command.</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<ApplicationCommandProperties> func, RequestOptions options = null);
/// <summary>
/// Modifies the current application command.
/// </summary>
/// <param name="func">The new properties to use when modifying the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
/// <exception cref="InvalidOperationException">Thrown when you pass in an invalid <see cref="ApplicationCommandProperties"/> type.</exception>
Task ModifyAsync<TArg>(Action<TArg> func, RequestOptions options = null)
where TArg : ApplicationCommandProperties;
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
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 : IDiscordInteractionData
{
/// <summary>
/// Gets the snowflake id of this command.
/// </summary>
ulong Id { get; }
/// <summary>
/// Gets the name of this command.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the options that the user has provided.
/// </summary>
IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; }
}
}

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Represents a option group for a command.
/// </summary>
public interface IApplicationCommandInteractionDataOption
{
/// <summary>
/// Gets the name of the parameter.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the value of the pair.
/// <note>
/// This objects type can be any one of the option types in <see cref="ApplicationCommandOptionType"/>.
/// </note>
/// </summary>
object Value { get; }
/// <summary>
/// Gets the type of this data's option.
/// </summary>
ApplicationCommandOptionType Type { get; }
/// <summary>
/// Gets the nested options of this option.
/// </summary>
IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; }
}
}

View File

@@ -0,0 +1,60 @@
using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Options for the <see cref="IApplicationCommand"/>.
/// </summary>
public interface IApplicationCommandOption
{
/// <summary>
/// Gets the type of this <see cref="IApplicationCommandOption"/>.
/// </summary>
ApplicationCommandOptionType Type { get; }
/// <summary>
/// Gets the name of this command option.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the description of this command option.
/// </summary>
string Description { get; }
/// <summary>
/// Gets whether or not this is the first required option for the user to complete.
/// </summary>
bool? IsDefault { get; }
/// <summary>
/// Gets whether or not the parameter is required or optional.
/// </summary>
bool? IsRequired { get; }
/// <summary>
/// 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 sub-options for this command option.
/// </summary>
IReadOnlyCollection<IApplicationCommandOption> Options { get; }
/// <summary>
/// Gets the allowed channel types for this option.
/// </summary>
IReadOnlyCollection<ChannelType> ChannelTypes { get; }
}
}

View File

@@ -0,0 +1,18 @@
namespace Discord
{
/// <summary>
/// Specifies choices for command group.
/// </summary>
public interface IApplicationCommandOptionChoice
{
/// <summary>
/// Gets the choice name.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the value of the choice.
/// </summary>
object Value { get; }
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// Represents a discord interaction.
/// </summary>
public interface IDiscordInteraction : ISnowflakeEntity
{
/// <summary>
/// Gets the id of the interaction.
/// </summary>
new ulong Id { get; }
/// <summary>
/// Gets the type of this <see cref="IDiscordInteraction"/>.
/// </summary>
InteractionType Type { get; }
/// <summary>
/// Gets the data sent within this interaction.
/// </summary>
IDiscordInteractionData Data { get; }
/// <summary>
/// Gets the continuation token for responding to the interaction.
/// </summary>
string Token { get; }
/// <summary>
/// Gets the version of the interaction, always 1.
/// </summary>
int Version { get; }
/// <summary>
/// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false,
bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null);
/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <returns>
/// The sent message.
/// </returns>
Task<IUserMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null);
/// <summary>
/// Gets the original response for this interaction.
/// </summary>
/// <param name="options">The request options for this <see langword="async"/> request.</param>
/// <returns>A <see cref="IUserMessage"/> that represents the initial response.</returns>
Task<IUserMessage> GetOriginalResponseAsync(RequestOptions options = null);
/// <summary>
/// Edits original response for this interaction.
/// </summary>
/// <param name="func">A delegate containing the properties to modify the message with.</param>
/// <param name="options">The request options for this <see langword="async"/> request.</param>
/// <returns>A <see cref="IUserMessage"/> that represents the initial response.</returns>
Task<IUserMessage> ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options = null);
/// <summary>
/// Acknowledges this interaction.
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
Task DeferAsync(bool ephemeral = false, RequestOptions options = null);
}
}

View File

@@ -0,0 +1,7 @@
namespace Discord
{
/// <summary>
/// Represents an interface used to specify classes that they are a valid data type of a <see cref="IDiscordInteraction"/> class.
/// </summary>
public interface IDiscordInteractionData { }
}

View File

@@ -0,0 +1,46 @@
using System;
namespace Discord
{
/// <summary>
/// The response type for an <see cref="IDiscordInteraction"/>.
/// </summary>
/// <remarks>
/// After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using <see cref="ChannelMessageWithSource"/>
/// or you can choose to send a deferred response with <see cref="DeferredChannelMessageWithSource"/>. If choosing a deferred response, the user will see a loading state for the interaction,
/// and you'll have up to 15 minutes to edit the original deferred response using Edit Original Interaction Response.
/// You can read more about Response types <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-response">Here</see>.
/// </remarks>
public enum InteractionResponseType : byte
{
/// <summary>
/// ACK a Ping.
/// </summary>
Pong = 1,
/// <summary>
/// Respond to an interaction with a message.
/// </summary>
ChannelMessageWithSource = 4,
/// <summary>
/// ACK an interaction and edit a response later, the user sees a loading state.
/// </summary>
DeferredChannelMessageWithSource = 5,
/// <summary>
/// For components: ACK an interaction and edit the original message later; the user does not see a loading state.
/// </summary>
DeferredUpdateMessage = 6,
/// <summary>
/// For components: edit the message the component was attached to.
/// </summary>
UpdateMessage = 7,
/// <summary>
/// Respond with a set of choices to a autocomplete interaction.
/// </summary>
ApplicationCommandAutocompleteResult = 8
}
}

View File

@@ -0,0 +1,28 @@
namespace Discord
{
/// <summary>
/// Represents a type of Interaction from discord.
/// </summary>
public enum InteractionType : byte
{
/// <summary>
/// A ping from discord.
/// </summary>
Ping = 1,
/// <summary>
/// A <see cref="IApplicationCommand"/> sent from discord.
/// </summary>
ApplicationCommand = 2,
/// <summary>
/// A <see cref="IMessageComponent"/> sent from discord.
/// </summary>
MessageComponent = 3,
/// <summary>
/// An autocomplete request sent from discord.
/// </summary>
ApplicationCommandAutocomplete = 4
}
}

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Represents a <see cref="IMessageComponent"/> Row for child components to live in.
/// </summary>
public class ActionRowComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.ActionRow;
/// <summary>
/// Gets the child components in this row.
/// </summary>
public IReadOnlyCollection<IMessageComponent> Components { get; internal set; }
internal ActionRowComponent() { }
internal ActionRowComponent(List<IMessageComponent> components)
{
Components = components;
}
string IMessageComponent.CustomId => null;
}
}

View File

@@ -0,0 +1,61 @@
namespace Discord
{
/// <summary>
/// Represents a <see cref="IMessageComponent"/> Button.
/// </summary>
public class ButtonComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.Button;
/// <summary>
/// Gets the <see cref="ButtonStyle"/> of this button, example buttons with each style can be found <see href="https://discord.com/assets/7bb017ce52cfd6575e21c058feb3883b.png">Here</see>.
/// </summary>
public ButtonStyle Style { get; }
/// <summary>
/// Gets the label of the button, this is the text that is shown.
/// </summary>
public string Label { get; }
/// <summary>
/// Gets the <see cref="IEmote"/> displayed with this button.
/// </summary>
public IEmote Emote { get; }
/// <inheritdoc/>
public string CustomId { get; }
/// <summary>
/// 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>.
/// </remarks>
public string Url { get; }
/// <summary>
/// Gets whether this button is disabled or not.
/// </summary>
public bool IsDisabled { get; }
/// <summary>
/// Turns this button into a button builder.
/// </summary>
/// <returns>
/// A newly created button builder with the same properties as this button.
/// </returns>
public ButtonBuilder ToBuilder()
=> new ButtonBuilder(Label, CustomId, Style, Url, Emote, IsDisabled);
internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool isDisabled)
{
Style = style;
Label = label;
Emote = emote;
CustomId = customId;
Url = url;
IsDisabled = isDisabled;
}
}
}

View File

@@ -0,0 +1,33 @@
namespace Discord
{
/// <summary>
/// Represents different styles to use with buttons. You can see an example of the different styles at <see href="https://discord.com/developers/docs/interactions/message-components#buttons-button-styles"/>
/// </summary>
public enum ButtonStyle
{
/// <summary>
/// A Blurple button
/// </summary>
Primary = 1,
/// <summary>
/// A Grey (or gray) button
/// </summary>
Secondary = 2,
/// <summary>
/// A Green button
/// </summary>
Success = 3,
/// <summary>
/// A Red button
/// </summary>
Danger = 4,
/// <summary>
/// A <see cref="Secondary"/> button with a little popup box indicating that this button is a link.
/// </summary>
Link = 5
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
namespace Discord
{
/// <summary>
/// Represents a type of a component.
/// </summary>
public enum ComponentType
{
/// <summary>
/// A container for other components.
/// </summary>
ActionRow = 1,
/// <summary>
/// A clickable button.
/// </summary>
Button = 2,
/// <summary>
/// A select menu for picking from choices.
/// </summary>
SelectMenu = 3
}
}

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

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

View File

@@ -0,0 +1,18 @@
namespace Discord
{
/// <summary>
/// Represents a message component on a message.
/// </summary>
public interface IMessageComponent
{
/// <summary>
/// Gets the <see cref="ComponentType"/> of this Message Component.
/// </summary>
ComponentType Type { get; }
/// <summary>
/// Gets the custom id of the component if possible; otherwise <see langword="null"/>.
/// </summary>
string CustomId { get; }
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Represents a component object used to send components with messages.
/// </summary>
public class MessageComponent
{
/// <summary>
/// Gets the components to be used in a message.
/// </summary>
public IReadOnlyCollection<ActionRowComponent> Components { get; }
internal MessageComponent(List<ActionRowComponent> components)
{
Components = components;
}
/// <summary>
/// Returns a empty <see cref="MessageComponent"/>.
/// </summary>
internal static MessageComponent Empty
=> new MessageComponent(new List<ActionRowComponent>());
}
}

View File

@@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Linq;
namespace Discord
{
/// <summary>
/// Represents a select menu component defined at <see href="https://discord.com/developers/docs/interactions/message-components#select-menu-object"/>
/// </summary>
public class SelectMenuComponent : IMessageComponent
{
/// <inheritdoc/>
public ComponentType Type => ComponentType.SelectMenu;
/// <inheritdoc/>
public string CustomId { get; }
/// <summary>
/// Gets the menus options to select from.
/// </summary>
public IReadOnlyCollection<SelectMenuOption> Options { get; }
/// <summary>
/// Gets the custom placeholder text if nothing is selected.
/// </summary>
public string Placeholder { get; }
/// <summary>
/// Gets the minimum number of items that must be chosen.
/// </summary>
public int MinValues { get; }
/// <summary>
/// Gets the maximum number of items that can be chosen.
/// </summary>
public int MaxValues { get; }
/// <summary>
/// Gets whether this menu is disabled or not.
/// </summary>
public bool IsDisabled { get; }
/// <summary>
/// Turns this select menu into a builder.
/// </summary>
/// <returns>
/// A newly create builder with the same properties as this select menu.
/// </returns>
public SelectMenuBuilder ToBuilder()
=> new SelectMenuBuilder(
CustomId,
Options.Select(x => new SelectMenuOptionBuilder(x.Label, x.Value, x.Description, x.Emote, x.IsDefault)).ToList(),
Placeholder,
MaxValues,
MinValues,
IsDisabled);
internal SelectMenuComponent(string customId, List<SelectMenuOption> options, string placeholder, int minValues, int maxValues, bool disabled)
{
CustomId = customId;
Options = options;
Placeholder = placeholder;
MinValues = minValues;
MaxValues = maxValues;
IsDisabled = disabled;
}
}
}

View File

@@ -0,0 +1,42 @@
namespace Discord
{
/// <summary>
/// Represents a choice for a <see cref="SelectMenuComponent"/>.
/// </summary>
public class SelectMenuOption
{
/// <summary>
/// Gets the user-facing name of the option.
/// </summary>
public string Label { get; }
/// <summary>
/// Gets the dev-define value of the option.
/// </summary>
public string Value { get; }
/// <summary>
/// Gets a description of the option.
/// </summary>
public string Description { get; }
/// <summary>
/// Gets the <see cref="IEmote"/> displayed with this menu option.
/// </summary>
public IEmote Emote { get; }
/// <summary>
/// Gets whether or not this option will render as selected by default.
/// </summary>
public bool? IsDefault { get; }
internal SelectMenuOption(string label, string value, string description, IEmote emote, bool? defaultValue)
{
Label = label;
Value = value;
Description = description;
Emote = emote;
IsDefault = defaultValue;
}
}
}

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

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

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

View File

@@ -0,0 +1,640 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Discord
{
/// <summary>
/// Represents a class used to build slash commands.
/// </summary>
public class SlashCommandBuilder
{
/// <summary>
/// Returns the maximum length a commands name allowed by Discord
/// </summary>
public const int MaxNameLength = 32;
/// <summary>
/// Returns the maximum length of a commands description allowed by Discord.
/// </summary>
public const int MaxDescriptionLength = 100;
/// <summary>
/// Returns the maximum count of command options allowed by Discord
/// </summary>
public const int MaxOptionsCount = 25;
/// <summary>
/// Gets or sets the name of this slash command.
/// </summary>
public string Name
{
get => _name;
set
{
Preconditions.NotNullOrEmpty(value, nameof(value));
Preconditions.AtLeast(value.Length, 1, nameof(value));
Preconditions.AtMost(value.Length, MaxNameLength, nameof(value));
// Discord updated the docs, this regex prevents special characters like @!$%(... etc,
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
if (!Regex.IsMatch(value, @"^[\w-]{1,32}$"))
throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(value));
_name = value;
}
}
/// <summary>
/// Gets or sets a 1-100 length description of this slash command
/// </summary>
public string Description
{
get => _description;
set
{
Preconditions.NotNullOrEmpty(value, nameof(Description));
Preconditions.AtLeast(value.Length, 1, nameof(Description));
Preconditions.AtMost(value.Length, MaxDescriptionLength, nameof(Description));
_description = value;
}
}
/// <summary>
/// Gets or sets the options for this command.
/// </summary>
public List<SlashCommandOptionBuilder> Options
{
get => _options;
set
{
Preconditions.AtMost(value?.Count ?? 0, MaxOptionsCount, nameof(value));
_options = value;
}
}
/// <summary>
/// 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;
private string _name;
private string _description;
private List<SlashCommandOptionBuilder> _options;
/// <summary>
/// Build the current builder into a <see cref="SlashCommandProperties"/> class.
/// </summary>
/// <returns>A <see cref="SlashCommandProperties"/> that can be used to create slash commands.</returns>
public SlashCommandProperties Build()
{
var props = new SlashCommandProperties
{
Name = Name,
Description = Description,
IsDefaultPermission = IsDefaultPermission,
};
if (Options != null && Options.Any())
{
var options = new List<ApplicationCommandOptionProperties>();
Options.OrderByDescending(x => x.IsRequired ?? false).ToList().ForEach(x => options.Add(x.Build()));
props.Options = options;
}
return props;
}
/// <summary>
/// Sets the field name.
/// </summary>
/// <param name="name">The value to set the field name to.</param>
/// <returns>
/// The current builder.
/// </returns>
public SlashCommandBuilder WithName(string name)
{
Name = name;
return this;
}
/// <summary>
/// Sets the description of the current command.
/// </summary>
/// <param name="description">The description of this command.</param>
/// <returns>The current builder.</returns>
public SlashCommandBuilder WithDescription(string description)
{
Description = description;
return this;
}
/// <summary>
/// Sets the default permission of the current command.
/// </summary>
/// <param name="value">The default permission value to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandBuilder WithDefaultPermission(bool value)
{
IsDefaultPermission = value;
return this;
}
/// <summary>
/// Adds an option to the current slash command.
/// </summary>
/// <param name="name">The name of the option to add.</param>
/// <param name="type">The type of this option.</param>
/// <param name="description">The description of this option.</param>
/// <param name="isRequired">If this option is required for this command.</param>
/// <param name="isDefault">If this option is the default option.</param>
/// <param name="isAutocomplete">If this option is set to autocomplete.</param>
/// <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, 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));
Preconditions.AtLeast(name.Length, 1, nameof(name));
Preconditions.AtMost(name.Length, MaxNameLength, nameof(name));
// Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc,
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name));
// same with description
Preconditions.NotNullOrEmpty(description, nameof(description));
Preconditions.AtLeast(description.Length, 1, nameof(description));
Preconditions.AtMost(description.Length, MaxDescriptionLength, nameof(description));
// make sure theres only one option with default set to true
if (isDefault == true && Options?.Any(x => x.IsDefault == true) == true)
throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault));
var option = new SlashCommandOptionBuilder
{
Name = name,
Description = description,
IsRequired = isRequired,
IsDefault = isDefault,
Options = options,
Type = type,
IsAutocomplete = isAutocomplete,
Choices = (choices ?? Array.Empty<ApplicationCommandOptionChoiceProperties>()).ToList(),
ChannelTypes = channelTypes,
MinValue = minValue,
MaxValue = maxValue,
};
return AddOption(option);
}
/// <summary>
/// Adds an option to this slash command.
/// </summary>
/// <param name="option">The option to add.</param>
/// <returns>The current builder.</returns>
public SlashCommandBuilder AddOption(SlashCommandOptionBuilder option)
{
Options ??= new List<SlashCommandOptionBuilder>();
if (Options.Count >= MaxOptionsCount)
throw new InvalidOperationException($"Cannot have more than {MaxOptionsCount} options!");
Preconditions.NotNull(option, nameof(option));
Options.Add(option);
return this;
}
/// <summary>
/// Adds a collection of options to the current slash command.
/// </summary>
/// <param name="options">The collection of options to add.</param>
/// <returns>The current builder.</returns>
public SlashCommandBuilder AddOptions(params SlashCommandOptionBuilder[] options)
{
if (options == null)
throw new ArgumentNullException(nameof(options), "Options cannot be null!");
if (options.Length == 0)
throw new ArgumentException("Options cannot be empty!", nameof(options));
Options ??= new List<SlashCommandOptionBuilder>();
if (Options.Count + options.Length > MaxOptionsCount)
throw new ArgumentOutOfRangeException(nameof(options), $"Cannot have more than {MaxOptionsCount} options!");
Options.AddRange(options);
return this;
}
}
/// <summary>
/// Represents a class used to build options for the <see cref="SlashCommandBuilder"/>.
/// </summary>
public class SlashCommandOptionBuilder
{
/// <summary>
/// The max length of a choice's name allowed by Discord.
/// </summary>
public const int ChoiceNameMaxLength = 100;
/// <summary>
/// The maximum number of choices allowed by Discord.
/// </summary>
public const int MaxChoiceCount = 25;
private string _name;
private string _description;
/// <summary>
/// Gets or sets the name of this option.
/// </summary>
public string Name
{
get => _name;
set
{
if (value != null)
{
Preconditions.AtLeast(value.Length, 1, nameof(value));
Preconditions.AtMost(value.Length, SlashCommandBuilder.MaxNameLength, nameof(value));
if (!Regex.IsMatch(value, @"^[\w-]{1,32}$"))
throw new ArgumentException("Option name cannot contain any special characters or whitespaces!", nameof(value));
}
_name = value;
}
}
/// <summary>
/// Gets or sets the description of this option.
/// </summary>
public string Description
{
get => _description;
set
{
if (value != null)
{
Preconditions.AtLeast(value.Length, 1, nameof(value));
Preconditions.AtMost(value.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(value));
}
_description = value;
}
}
/// <summary>
/// Gets or sets the type of this option.
/// </summary>
public ApplicationCommandOptionType Type { get; set; }
/// <summary>
/// Gets or sets whether or not this options is the first required option for the user to complete. only one option can be default.
/// </summary>
public bool? IsDefault { get; set; }
/// <summary>
/// Gets or sets if the option is required.
/// </summary>
public bool? IsRequired { get; set; } = null;
/// <summary>
/// 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>
public List<ApplicationCommandOptionChoiceProperties> Choices { get; set; }
/// <summary>
/// Gets or sets if this option is a subcommand or subcommand group type, these nested options will be the parameters.
/// </summary>
public List<SlashCommandOptionBuilder> Options { get; set; }
/// <summary>
/// Gets or sets the allowed channel types for this option.
/// </summary>
public List<ChannelType> ChannelTypes { get; set; }
/// <summary>
/// Builds the current option.
/// </summary>
/// <returns>The built version of this option.</returns>
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");
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,
Description = Description,
IsDefault = IsDefault,
IsRequired = IsRequired,
Type = Type,
Options = Options?.Count > 0
? Options.OrderByDescending(x => x.IsRequired ?? false).Select(x => x.Build()).ToList()
: new List<ApplicationCommandOptionProperties>(),
Choices = Choices,
IsAutocomplete = IsAutocomplete,
ChannelTypes = ChannelTypes,
MinValue = MinValue,
MaxValue = MaxValue
};
}
/// <summary>
/// Adds an option to the current slash command.
/// </summary>
/// <param name="name">The name of the option to add.</param>
/// <param name="type">The type of this option.</param>
/// <param name="description">The description of this option.</param>
/// <param name="isRequired">If this option is required for this command.</param>
/// <param name="isDefault">If this option is the default option.</param>
/// <param name="isAutocomplete">If this option supports autocomplete.</param>
/// <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? 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));
Preconditions.AtLeast(name.Length, 1, nameof(name));
Preconditions.AtMost(name.Length, SlashCommandBuilder.MaxNameLength, nameof(name));
// Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc,
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name));
// same with description
Preconditions.NotNullOrEmpty(description, nameof(description));
Preconditions.AtLeast(description.Length, 1, nameof(description));
Preconditions.AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description));
// make sure theres only one option with default set to true
if (isDefault && Options?.Any(x => x.IsDefault == true) == true)
throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault));
var option = new SlashCommandOptionBuilder
{
Name = name,
Description = description,
IsRequired = required,
IsDefault = isDefault,
IsAutocomplete = isAutocomplete,
MinValue = minValue,
MaxValue = maxValue,
Options = options,
Type = type,
Choices = (choices ?? Array.Empty<ApplicationCommandOptionChoiceProperties>()).ToList(),
ChannelTypes = channelTypes
};
return AddOption(option);
}
/// <summary>
/// Adds a sub option to the current option.
/// </summary>
/// <param name="option">The sub option to add.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder AddOption(SlashCommandOptionBuilder option)
{
Options ??= new List<SlashCommandOptionBuilder>();
if (Options.Count >= SlashCommandBuilder.MaxOptionsCount)
throw new InvalidOperationException($"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!");
Preconditions.NotNull(option, nameof(option));
Options.Add(option);
return this;
}
/// <summary>
/// Adds a choice to the current option.
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The value of the choice.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder AddChoice(string name, int value)
{
return AddChoiceInternal(name, value);
}
/// <summary>
/// Adds a choice to the current option.
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The value of the choice.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder AddChoice(string name, string value)
{
return AddChoiceInternal(name, value);
}
/// <summary>
/// Adds a choice to the current option.
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The value of the choice.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder AddChoice(string name, double value)
{
return AddChoiceInternal(name, value);
}
/// <summary>
/// Adds a choice to the current option.
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The value of the choice.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder AddChoice(string name, float value)
{
return AddChoiceInternal(name, value);
}
/// <summary>
/// Adds a choice to the current option.
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The value of the choice.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder AddChoice(string name, long value)
{
return AddChoiceInternal(name, value);
}
private SlashCommandOptionBuilder AddChoiceInternal(string name, object value)
{
Choices ??= new List<ApplicationCommandOptionChoiceProperties>();
if (Choices.Count >= MaxChoiceCount)
throw new InvalidOperationException($"Cannot add more than {MaxChoiceCount} choices!");
Preconditions.NotNull(name, nameof(name));
Preconditions.NotNull(value, nameof(value));
Preconditions.AtLeast(name.Length, 1, nameof(name));
Preconditions.AtMost(name.Length, 100, nameof(name));
if(value is string str)
{
Preconditions.AtLeast(str.Length, 1, nameof(value));
Preconditions.AtMost(str.Length, 100, nameof(value));
}
Choices.Add(new ApplicationCommandOptionChoiceProperties
{
Name = name,
Value = value
});
return this;
}
/// <summary>
/// Adds a channel type to the current option.
/// </summary>
/// <param name="channelType">The <see cref="ChannelType"/> to add.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder AddChannelType(ChannelType channelType)
{
ChannelTypes ??= new List<ChannelType>();
ChannelTypes.Add(channelType);
return this;
}
/// <summary>
/// Sets the current builders name.
/// </summary>
/// <param name="name">The name to set the current option builder.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithName(string name)
{
Name = name;
return this;
}
/// <summary>
/// Sets the current builders description.
/// </summary>
/// <param name="description">The description to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithDescription(string description)
{
Description = description;
return this;
}
/// <summary>
/// Sets the current builders required field.
/// </summary>
/// <param name="value">The value to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithRequired(bool value)
{
IsRequired = value;
return this;
}
/// <summary>
/// Sets the current builders default field.
/// </summary>
/// <param name="value">The value to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithDefault(bool value)
{
IsDefault = value;
return this;
}
/// <summary>
/// Sets the current builders autocomplete field.
/// </summary>
/// <param name="value">The value to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithAutocomplete(bool value)
{
IsAutocomplete = value;
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>
/// <param name="type">The type to set.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder WithType(ApplicationCommandOptionType type)
{
Type = type;
return this;
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Represents a class used to create slash commands.
/// </summary>
public class SlashCommandProperties : ApplicationCommandProperties
{
internal override ApplicationCommandType Type => ApplicationCommandType.Slash;
/// <summary>
/// Gets or sets the discription of this command.
/// </summary>
public Optional<string> Description { get; set; }
/// <summary>
/// Gets or sets the options for this command.
/// </summary>
public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; }
internal SlashCommandProperties() { }
}
}

View File

@@ -9,6 +9,10 @@ namespace Discord
/// <summary>
/// The invite is for a Go Live stream.
/// </summary>
Stream = 1
Stream = 1,
/// <summary>
/// The invite is for embedded application.
/// </summary>
EmbeddedApplication = 2
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Discord.Utils;
namespace Discord
{
@@ -16,20 +17,20 @@ namespace Discord
private EmbedThumbnail? _thumbnail;
private List<EmbedFieldBuilder> _fields;
/// <summary>
/// Returns the maximum number of fields allowed by Discord.
/// <summary>
/// Returns the maximum number of fields allowed by Discord.
/// </summary>
public const int MaxFieldCount = 25;
/// <summary>
/// Returns the maximum length of title allowed by Discord.
/// <summary>
/// Returns the maximum length of title allowed by Discord.
/// </summary>
public const int MaxTitleLength = 256;
/// <summary>
/// Returns the maximum length of description allowed by Discord.
/// <summary>
/// Returns the maximum length of description allowed by Discord.
/// </summary>
public const int MaxDescriptionLength = 4096;
/// <summary>
/// Returns the maximum length of total characters allowed by Discord.
/// <summary>
/// Returns the maximum length of total characters allowed by Discord.
/// </summary>
public const int MaxEmbedLength = 6000;
@@ -88,9 +89,9 @@ namespace Discord
}
/// <summary> Gets or sets the list of <see cref="EmbedFieldBuilder"/> of an <see cref="Embed"/>. </summary>
/// <exception cref="ArgumentNullException" accessor="set">An embed builder's fields collection is set to
/// <exception cref="ArgumentNullException" accessor="set">An embed builder's fields collection is set to
/// <c>null</c>.</exception>
/// <exception cref="ArgumentException" accessor="set">Description length exceeds <see cref="MaxFieldCount"/>.
/// <exception cref="ArgumentException" accessor="set">Fields count exceeds <see cref="MaxFieldCount"/>.
/// </exception>
/// <returns> The list of existing <see cref="EmbedFieldBuilder"/>.</returns>
public List<EmbedFieldBuilder> Fields
@@ -137,7 +138,7 @@ namespace Discord
/// Gets the total length of all embed properties.
/// </summary>
/// <returns>
/// The combined length of <see cref="Title"/>, <see cref="EmbedAuthor.Name"/>, <see cref="Description"/>,
/// The combined length of <see cref="Title"/>, <see cref="EmbedAuthor.Name"/>, <see cref="Description"/>,
/// <see cref="EmbedFooter.Text"/>, <see cref="EmbedField.Name"/>, and <see cref="EmbedField.Value"/>.
/// </returns>
public int Length
@@ -166,7 +167,7 @@ namespace Discord
Title = title;
return this;
}
/// <summary>
/// <summary>
/// Sets the description of an <see cref="Embed"/>.
/// </summary>
/// <param name="description"> The description to be set. </param>
@@ -178,7 +179,7 @@ namespace Discord
Description = description;
return this;
}
/// <summary>
/// <summary>
/// Sets the URL of an <see cref="Embed"/>.
/// </summary>
/// <param name="url"> The URL to be set. </param>
@@ -190,7 +191,7 @@ namespace Discord
Url = url;
return this;
}
/// <summary>
/// <summary>
/// Sets the thumbnail URL of an <see cref="Embed"/>.
/// </summary>
/// <param name="thumbnailUrl"> The thumbnail URL to be set. </param>
@@ -401,11 +402,29 @@ namespace Discord
/// The built embed object.
/// </returns>
/// <exception cref="InvalidOperationException">Total embed length exceeds <see cref="MaxEmbedLength"/>.</exception>
/// <exception cref="InvalidOperationException">Any Url must include its protocols (i.e http:// or https://).</exception>
public Embed Build()
{
if (Length > MaxEmbedLength)
throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}.");
if (!string.IsNullOrEmpty(Url))
UrlValidation.Validate(Url, true);
if (!string.IsNullOrEmpty(ThumbnailUrl))
UrlValidation.Validate(ThumbnailUrl, true);
if (!string.IsNullOrEmpty(ImageUrl))
UrlValidation.Validate(ImageUrl, true);
if (Author != null)
{
if (!string.IsNullOrEmpty(Author.Url))
UrlValidation.Validate(Author.Url, true);
if (!string.IsNullOrEmpty(Author.IconUrl))
UrlValidation.Validate(Author.IconUrl, true);
}
if(Footer != null)
{
if (!string.IsNullOrEmpty(Footer.IconUrl))
UrlValidation.Validate(Footer.IconUrl, true);
}
var fields = ImmutableArray.CreateBuilder<EmbedField>(Fields.Count);
for (int i = 0; i < Fields.Count; i++)
fields.Add(Fields[i].Build());

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

View File

@@ -55,5 +55,12 @@ namespace Discord
/// The width of this attachment if it is a picture; otherwise <c>null</c>.
/// </returns>
int? Width { get; }
/// <summary>
/// Gets whether or not this attachment is ephemeral.
/// </summary>
/// <returns>
/// <see langword="true"/> if the attachment is ephemeral; otherwise <see langword="false"/>.
/// </returns>
bool Ephemeral { get; }
}
}

View File

@@ -53,6 +53,13 @@ namespace Discord
/// </returns>
string Content { get; }
/// <summary>
/// Gets the clean content for this message.
/// </summary>
/// <returns>
/// A string that contains the body of the message stripped of mentions, markdown, emojis and pings; note that this field may be empty if there is an embed.
/// </returns>
string CleanContent { get; }
/// <summary>
/// Gets the time this message was sent.
/// </summary>
/// <returns>
@@ -165,12 +172,17 @@ namespace Discord
IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions { get; }
/// <summary>
/// Gets all stickers included in this message.
/// The <see cref="IMessageComponent"/>'s attached to this message
/// </summary>
IReadOnlyCollection<IMessageComponent> Components { get; }
/// <summary>
/// Gets all stickers items included in this message.
/// </summary>
/// <returns>
/// A read-only collection of sticker objects.
/// A read-only collection of sticker item objects.
/// </returns>
IReadOnlyCollection<ISticker> Stickers { get; }
IReadOnlyCollection<IStickerItem> Stickers { get; }
/// <summary>
/// Gets the flags related to this message.
@@ -183,6 +195,14 @@ namespace Discord
/// </returns>
MessageFlags? Flags { get; }
/// <summary>
/// Gets the interaction this message is a response to.
/// </summary>
/// <returns>
/// A <see cref="IMessageInteraction"/> if the message is a response to an interaction; otherwise <see langword="null"/>.
/// </returns>
IMessageInteraction Interaction { get; }
/// <summary>
/// Adds a reaction to this message.
/// </summary>

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 a partial <see cref="IDiscordInteraction"/> within a message.
/// </summary>
public interface IMessageInteraction
{
/// <summary>
/// Gets the snowflake id of the interaction.
/// </summary>
ulong Id { get; }
/// <summary>
/// Gets the type of the interaction.
/// </summary>
InteractionType Type { get; }
/// <summary>
/// Gets the name of the application command used.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the <seealso cref="IUser"/> who invoked the interaction.
/// </summary>
IUser User { get; }
}
}

View File

@@ -32,5 +32,17 @@ namespace Discord
/// Flag given to messages that came from the urgent message system.
/// </summary>
Urgent = 1 << 4,
/// <summary>
/// Flag given to messages has an associated thread, with the same id as the message
/// </summary>
HasThread = 1 << 5,
/// <summary>
/// Flag given to messages that is only visible to the user who invoked the Interaction.
/// </summary>
Ephemeral = 1 << 6,
/// <summary>
/// Flag given to messages that is an Interaction Response and the bot is "thinking"
/// </summary>
Loading = 1 << 7
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// Represents a partial <see cref="IDiscordInteraction"/> within a message.
/// </summary>
/// <typeparam name="TUser">The type of the user.</typeparam>
public class MessageInteraction<TUser> : IMessageInteraction where TUser : IUser
{
/// <summary>
/// Gets the snowflake id of the interaction.
/// </summary>
public ulong Id { get; }
/// <summary>
/// Gets the type of the interaction.
/// </summary>
public InteractionType Type { get; }
/// <summary>
/// Gets the name of the application command used.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the <typeparamref name="TUser"/> who invoked the interaction.
/// </summary>
public TUser User { get; }
internal MessageInteraction(ulong id, InteractionType type, string name, TUser user)
{
Id = id;
Type = type;
Name = name;
User = user;
}
IUser IMessageInteraction.User => User;
}
}

View File

@@ -1,10 +1,12 @@
using System.Collections.Generic;
namespace Discord
{
/// <summary>
/// Properties that are used to modify an <see cref="IUserMessage" /> with the specified changes.
/// </summary>
/// <remarks>
/// The content of a message can be cleared with <see cref="System.String.Empty"/> if and only if an
/// The content of a message can be cleared with <see cref="System.String.Empty"/> if and only if an
/// <see cref="Discord.Embed"/> is present.
/// </remarks>
/// <seealso cref="IUserMessage.ModifyAsync"/>
@@ -17,10 +19,25 @@ namespace Discord
/// This must be less than the constant defined by <see cref="DiscordConfig.MaxMessageSize"/>.
/// </remarks>
public Optional<string> Content { get; set; }
/// <summary>
/// Gets or sets the embed the message should display.
/// Gets or sets a single embed for this message.
/// </summary>
/// <remarks>
/// This property will be added to the <see cref="Embeds"/> array, in the future please use the array rather than this property.
/// </remarks>
public Optional<Embed> Embed { get; set; }
/// <summary>
/// Gets or sets the embeds of the message.
/// </summary>
public Optional<Embed[]> Embeds { get; set; }
/// <summary>
/// Gets or sets the components for this message.
/// </summary>
public Optional<MessageComponent> Components { get; set; }
/// <summary>
/// Gets or sets the flags of the message.
/// </summary>
@@ -33,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; }
}
}

View File

@@ -58,8 +58,53 @@ namespace Discord
/// </summary>
ChannelFollowAdd = 12,
/// <summary>
/// The message for when a guild is disqualified from discovery.
/// </summary>
GuildDiscoveryDisqualified = 14,
/// <summary>
/// The message for when a guild is requalified for discovery.
/// </summary>
GuildDiscoveryRequalified = 15,
/// <summary>
/// The message for when the initial warning is sent for the initial grace period discovery.
/// </summary>
GuildDiscoveryGracePeriodInitialWarning = 16,
/// <summary>
/// The message for when the final warning is sent for the initial grace period discovery.
/// </summary>
GuildDiscoveryGracePeriodFinalWarning = 17,
/// <summary>
/// The message for when a thread is created.
/// </summary>
ThreadCreated = 18,
/// <summary>
/// The message is an inline reply.
/// </summary>
/// <remarks>
/// Only available in API v8.
/// </remarks>
Reply = 19,
/// <summary>
/// The message is an Application Command.
/// </summary>
/// <remarks>
/// Only available in API v8.
/// </remarks>
ApplicationCommand = 20,
/// <summary>
/// The message that starts a thread.
/// </summary>
/// <remarks>
/// Only available in API v9.
/// </remarks>
ThreadStarterMessage = 21,
/// <summary>
/// The message for a invite reminder.
/// </summary>
GuildInviteReminder = 22,
/// <summary>
/// The message for a context menu command.
/// </summary>
ContextMenuCommand = 23,
}
}

View File

@@ -0,0 +1,25 @@
namespace Discord
{
/// <summary>
/// Defines the types of formats for stickers.
/// </summary>
public enum StickerFormatType
{
/// <summary>
/// Default value for a sticker format type.
/// </summary>
None = 0,
/// <summary>
/// The sticker format type is png.
/// </summary>
Png = 1,
/// <summary>
/// The sticker format type is apng.
/// </summary>
Apng = 2,
/// <summary>
/// The sticker format type is lottie.
/// </summary>
Lottie = 3
}
}

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