diff --git a/.gitignore b/.gitignore index 45e5e009d..954362408 100644 --- a/.gitignore +++ b/.gitignore @@ -206,3 +206,6 @@ project.lock.json docs/api/\.manifest \.idea/ + +# Codealike UID +codealike.json \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 296b6d1cb..3c4ebac71 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,18 +1,18 @@ # Contributing to Docs -I don't really have any strict conditions for writing documentation, +We don't really have any strict conditions for writing documentation, but just keep these few guidelines in mind: * Keep code samples in the `guides/samples` folder -* When referencing an object in the API, link to it's page in the -API documentation. +* When referencing an object in the API, link to it's page in the + API documentation. * Documentation should be written in clear and proper English* -\* If anyone is interested in translating documentation into other -languages, please open an issue or contact me on +\* If anyone is interested in translating documentation into other +languages, please open an issue or contact me on Discord (`foxbot#0282`). -### Layout +## Layout Documentation should be written in a FAQ/Wiki style format. @@ -33,14 +33,4 @@ Example of long link syntax: Please consult the [API Documentation] for more information. [API Documentation]: xref:System.String -``` - -### Compiling - -Documentation is compiled into a static site using [DocFx]. -We currently use the most recent build off the dev branch. - -After making changes, compile your changes into the static site with -`docfx`. You can also view your changes live with `docfx --serve`. - -[DocFx]: https://dotnet.github.io/docfx/ \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index a672330d4..eaf4725c3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,16 +1,16 @@ # Instructions for Building Documentation -The documentation for the Discord.NET library uses [DocFX][docfx-main]. [Instructions for installing this tool can be found here.][docfx-installing] +The documentation for the Discord.Net library uses [DocFX][docfx-main]. +[Instructions for installing this tool can be found here.][docfx-installing] 1. Navigate to the root of the repository. -2. (Optional) If you intend to target a specific version, ensure that you -have the correct version checked out. -3. Build the library. Run `dotnet build` in the root of this repository. - Ensure that the build passes without errors. -4. Build the docs using `docfx .\docs\docfx.json`. Add the `--serve` parameter -to preview the site locally. Some elements of the page may appear incorrect -when not hosted by a server. - - Remarks: According to the docfx website, this tool does work on Linux under mono. +2. (Optional) If you intend to target a specific version, ensure that + you have the correct version checked out. +3. Build the docs using `docfx docs/docfx.json`. Add the `--serve` + parameter to preview the site locally. Some elements of the page + may appear incorrect when not hosted by a server. + - Remarks: According to the docfx website, this tool does work on + Linux under Mono. [docfx-main]: https://dotnet.github.io/docfx/ [docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html diff --git a/docs/_overwrites/Commands/CommandException.Overwrite.md b/docs/_overwrites/Commands/CommandException.Overwrite.md new file mode 100644 index 000000000..166a011de --- /dev/null +++ b/docs/_overwrites/Commands/CommandException.Overwrite.md @@ -0,0 +1,31 @@ +--- +uid: Discord.Commands.CommandException +remarks: *content +--- + +This @System.Exception class is typically used when diagnosing +an error thrown during the execution of a command. You will find the +thrown exception passed into +[LogMessage.Exception](xref:Discord.LogMessage.Exception), which is +sent to your [CommandService.Log](xref:Discord.Commands.CommandService.Log) +event handler. + +--- +uid: Discord.Commands.CommandException +example: [*content] +--- + +You may use this information to handle runtime exceptions after +execution. Below is an example of how you may use this: + +```cs +public Task LogHandlerAsync(LogMessage logMessage) +{ + // Note that this casting method requires C#7 and up. + if (logMessage?.Exception is CommandException cmdEx) + { + Console.WriteLine($"{cmdEx.GetBaseException().GetType()} was thrown while executing {cmdEx.Command.Aliases.First()} in {cmdEx.Context.Channel} by {cmdEx.Context.User}."); + } + return Task.CompletedTask; +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md b/docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md new file mode 100644 index 000000000..d47980df7 --- /dev/null +++ b/docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md @@ -0,0 +1,22 @@ +--- +uid: Discord.Commands.DontAutoLoadAttribute +remarks: *content +--- + +The attribute can be applied to a public class that inherits +@Discord.Commands.ModuleBase. By applying this attribute, +@Discord.Commands.CommandService.AddModulesAsync* will not discover and +add the marked module to the CommandService. + +--- +uid: Discord.Commands.DontAutoLoadAttribute +example: [*content] +--- + +```cs +[DontAutoLoad] +public class MyModule : ModuleBase +{ + // ... +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md new file mode 100644 index 000000000..950d2990c --- /dev/null +++ b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md @@ -0,0 +1,27 @@ +--- +uid: Discord.Commands.DontInjectAttribute +remarks: *content +--- + +The attribute can be applied to a public settable property inside a +@Discord.Commands.ModuleBase based class. By applying this attribute, +the marked property will not be automatically injected of the +dependency. See @Guides.Commands.DI to learn more. + +--- +uid: Discord.Commands.DontInjectAttribute +example: [*content] +--- + +```cs +public class MyModule : ModuleBase +{ + [DontInject] + public MyService MyService { get; set; } + + public MyModule() + { + MyService = new MyService(); + } +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/ICommandContext.Inclusion.md b/docs/_overwrites/Commands/ICommandContext.Inclusion.md new file mode 100644 index 000000000..c99148db7 --- /dev/null +++ b/docs/_overwrites/Commands/ICommandContext.Inclusion.md @@ -0,0 +1,5 @@ +An example of how this class is used the command system can be seen +below: + +[!code[Sample module](../../guides/commands/samples/empty-module.cs)] +[!code[Command handler](../../guides/commands/samples/command_handler.cs)] \ No newline at end of file diff --git a/docs/_overwrites/Commands/ICommandContext.Overwrite.md b/docs/_overwrites/Commands/ICommandContext.Overwrite.md new file mode 100644 index 000000000..d9e50b46d --- /dev/null +++ b/docs/_overwrites/Commands/ICommandContext.Overwrite.md @@ -0,0 +1,27 @@ +--- +uid: Discord.Commands.ICommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] + +--- +uid: Discord.Commands.CommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] + +--- +uid: Discord.Commands.SocketCommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] + +--- +uid: Discord.Commands.ShardCommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] \ No newline at end of file diff --git a/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md b/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md new file mode 100644 index 000000000..75b9f93a5 --- /dev/null +++ b/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md @@ -0,0 +1,103 @@ +--- +uid: Discord.Commands.PreconditionAttribute +remarks: *content +--- + +This precondition attribute can be applied on module-level or +method-level for a command. + +[!include[Additional Remarks](PreconditionAttribute.Remarks.Inclusion.md)] + +--- +uid: Discord.Commands.ParameterPreconditionAttribute +remarks: *content +--- + +This precondition attribute can be applied on parameter-level for a +command. + +[!include[Additional Remarks](PreconditionAttribute.Remarks.Inclusion.md)] + +--- +uid: Discord.Commands.PreconditionAttribute +example: [*content] +--- + +The following example creates a precondition to see if the user has +sufficient role required to access the command. + +```cs +public class RequireRoleAttribute : PreconditionAttribute +{ + private readonly ulong _roleId; + + public RequireRoleAttribute(ulong roleId) + { + _roleId = roleId; + } + + public override async Task CheckPermissionsAsync(ICommandContext context, + CommandInfo command, IServiceProvider services) + { + var guildUser = context.User as IGuildUser; + if (guildUser == null) + return PreconditionResult.FromError("This command cannot be executed outside of a guild."); + + var guild = guildUser.Guild; + if (guild.Roles.All(r => r.Id != _roleId)) + return PreconditionResult.FromError( + $"The guild does not have the role ({_roleId}) required to access this command."); + + return guildUser.RoleIds.Any(rId => rId == _roleId) + ? PreconditionResult.FromSuccess() + : PreconditionResult.FromError("You do not have the sufficient role required to access this command."); + } +} +``` + +--- +uid: Discord.Commands.ParameterPreconditionAttribute +example: [*content] +--- + +The following example creates a precondition on a parameter-level to +see if the targeted user has a lower hierarchy than the user who +executed the command. + +```cs +public class RequireHierarchyAttribute : ParameterPreconditionAttribute +{ + public override async Task CheckPermissionsAsync(ICommandContext context, + ParameterInfo parameter, object value, IServiceProvider services) + { + // Hierarchy is only available under the socket variant of the user. + if (!(context.User is SocketGuildUser guildUser)) + return PreconditionResult.FromError("This command cannot be used outside of a guild."); + + SocketGuildUser targetUser; + switch (value) + { + case SocketGuildUser targetGuildUser: + targetUser = targetGuildUser; + break; + case ulong userId: + targetUser = await context.Guild.GetUserAsync(userId).ConfigureAwait(false) as SocketGuildUser; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (targetUser == null) + return PreconditionResult.FromError("Target user not found."); + + if (guildUser.Hierarchy < targetUser.Hierarchy) + return PreconditionResult.FromError("You cannot target anyone else whose roles are higher than yours."); + + var currentUser = await context.Guild.GetCurrentUserAsync().ConfigureAwait(false) as SocketGuildUser; + if (currentUser?.Hierarchy < targetUser.Hierarchy) + return PreconditionResult.FromError("The bot's role is lower than the targeted user."); + + return PreconditionResult.FromSuccess(); + } +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md b/docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md new file mode 100644 index 000000000..499cdb0ad --- /dev/null +++ b/docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md @@ -0,0 +1,6 @@ +A "precondidtion" in the command system is used to determine if a +condition is met before entering the command task. Using a +precondidtion may aid in keeping a well-organized command logic. + +The most common use case being whether a user has sufficient +permission to execute the command. \ No newline at end of file diff --git a/docs/_overwrites/Common/EmbedBuilder.Overwrites.md b/docs/_overwrites/Common/EmbedBuilder.Overwrites.md new file mode 100644 index 000000000..2dcb1e004 --- /dev/null +++ b/docs/_overwrites/Common/EmbedBuilder.Overwrites.md @@ -0,0 +1,68 @@ +--- +uid: Discord.EmbedBuilder +seealso: + - linkId: Discord.EmbedFooterBuilder + - linkId: Discord.EmbedAuthorBuilder + - linkId: Discord.EmbedFieldBuilder +remarks: *content +--- + +This builder class is used to build an @Discord.Embed (rich embed) +object that will be ready to be sent via @Discord.IMessageChannel.SendMessageAsync* +after @Discord.EmbedBuilder.Build* is called. + +--- +uid: Discord.EmbedBuilder +example: [*content] +--- + +#### Basic Usage + +The example below builds an embed and sends it to the chat using the +command system. + +```cs +[Command("embed")] +public async Task SendRichEmbedAsync() +{ + var embed = new EmbedBuilder + { + // Embed property can be set within object initializer + Title = "Hello world!" + Description = "I am a description set by initializer." + }; + // Or with methods + embed.AddField("Field title", + "Field value. I also support [hyperlink markdown](https://example.com)!") + .WithAuthor(Context.Client.CurrentUser) + .WithFooter(footer => footer.Text = "I am a footer.") + .WithColor(Color.Blue) + .WithTitle("I overwrote \"Hello world!\"") + .WithDescription("I am a description.") + .WithUrl("https://example.com") + .WithCurrentTimestamp() + .Build(); + await ReplyAsync(embed: embed); +} +``` + +![Embed Example](images/embed-example.png) + +#### Usage with Local Images + +The example below sends an image and has the image embedded in the rich +embed. See @Discord.IMessageChannel.SendFileAsync* for more information +about uploading a file or image. + +```cs +[Command("embedimage")] +public async Task SendEmbedWithImageAsync() +{ + var fileName = "image.png"; + var embed = new EmbedBuilder() + { + ImageUrl = $"attachment://{fileName}" + }.Build(); + await Context.Channel.SendFileAsync(fileName, embed: embed); +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md b/docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md new file mode 100644 index 000000000..eac0d9ca5 --- /dev/null +++ b/docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md @@ -0,0 +1,25 @@ +The example will build a rich embed with an author field, a footer +field, and 2 normal fields using an @Discord.EmbedBuilder: + +```cs +var exampleAuthor = new EmbedAuthorBuilder() + .WithName("I am a bot") + .WithIconUrl("https://discordapp.com/assets/e05ead6e6ebc08df9291738d0aa6986d.png"); +var exampleFooter = new EmbedFooterBuilder() + .WithText("I am a nice footer") + .WithIconUrl("https://discordapp.com/assets/28174a34e77bb5e5310ced9f95cb480b.png"); +var exampleField = new EmbedFieldBuilder() + .WithName("Title of Another Field") + .WithValue("I am an [example](https://example.com).") + .WithInline(true); +var otherField = new EmbedFieldBuilder() + .WithName("Title of a Field") + .WithValue("Notice how I'm inline with that other field next to me.") + .WithInline(true); +var embed = new EmbedBuilder() + .AddField(exampleField) + .AddField(otherField) + .WithAuthor(exampleAuthor) + .WithFooter(exampleFooter) + .Build(); +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md b/docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md new file mode 100644 index 000000000..c633c29b1 --- /dev/null +++ b/docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md @@ -0,0 +1,20 @@ +--- +uid: Discord.EmbedAuthorBuilder +example: [*content] +--- + +[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)] + +--- +uid: Discord.EmbedFooterBuilder +example: [*content] +--- + +[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)] + +--- +uid: Discord.EmbedFieldBuilder +example: [*content] +--- + +[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)] \ No newline at end of file diff --git a/docs/_overwrites/Common/IEmote.Inclusion.md b/docs/_overwrites/Common/IEmote.Inclusion.md new file mode 100644 index 000000000..cf93c7eb5 --- /dev/null +++ b/docs/_overwrites/Common/IEmote.Inclusion.md @@ -0,0 +1,26 @@ +The sample below sends a message and adds an @Discord.Emoji and a custom +@Discord.Emote to the message. + +```cs +public async Task SendAndReactAsync(ISocketMessageChannel channel) +{ + var message = await channel.SendMessageAsync("I am a message."); + + // Creates a Unicode-based emoji based on the Unicode string. + // This is effectively the same as new Emoji("💕"). + var heartEmoji = new Emoji("\U0001f495"); + // Reacts to the message with the Emoji. + await message.AddReactionAsync(heartEmoji); + + // Parses a custom emote based on the provided Discord emote format. + // Please note that this does not guarantee the existence of + // the emote. + var emote = Emote.Parse("<:thonkang:282745590985523200>"); + // Reacts to the message with the Emote. + await message.AddReactionAsync(emote); +} +``` + +#### Result + +![React Example](images/react-example.png) \ No newline at end of file diff --git a/docs/_overwrites/Common/IEmote.Overwrites.md b/docs/_overwrites/Common/IEmote.Overwrites.md new file mode 100644 index 000000000..034533d1d --- /dev/null +++ b/docs/_overwrites/Common/IEmote.Overwrites.md @@ -0,0 +1,81 @@ +--- +uid: Discord.IEmote +seealso: + - linkId: Discord.Emote + - linkId: Discord.Emoji + - linkId: Discord.GuildEmote + - linkId: Discord.IUserMessage +remarks: *content +--- + +This interface is often used with reactions. It can represent an +unicode-based @Discord.Emoji, or a custom @Discord.Emote. + +--- +uid: Discord.Emote +seealso: + - linkId: Discord.IEmote + - linkId: Discord.GuildEmote + - linkId: Discord.Emoji + - linkId: Discord.IUserMessage +remarks: *content +--- + +> [!NOTE] +> A valid @Discord.Emote format is `<:emoteName:emoteId>`. This can be +> obtained by escaping with a `\` in front of the emote using the +> Discord chat client. + +This class represents a custom emoji. This type of emoji can be +created via the @Discord.Emote.Parse* or @Discord.Emote.TryParse* +method. + +--- +uid: Discord.Emoji +seealso: + - linkId: Discord.Emote + - linkId: Discord.GuildEmote + - linkId: Discord.Emoji + - linkId: Discord.IUserMessage +remarks: *content +--- + +> [!NOTE] +> A valid @Discord.Emoji format is Unicode-based. This means only +> something like `🙃` or `\U0001f643` would work, instead of +> `:upside_down:`. +> +> A Unicode-based emoji can be obtained by escaping with a `\` in +> front of the emote using the Discord chat client or by looking up on +> [Emojipedia](https://emojipedia.org). + +This class represents a standard Unicode-based emoji. This type of emoji +can be created by passing the Unicode into the constructor. + +--- +uid: Discord.IEmote +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] + +--- +uid: Discord.Emoji +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] + +--- +uid: Discord.Emote +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] + +--- +uid: Discord.GuildEmote +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] \ No newline at end of file diff --git a/docs/_overwrites/Common/ObjectProperties.Overwrites.md b/docs/_overwrites/Common/ObjectProperties.Overwrites.md new file mode 100644 index 000000000..e9c365d39 --- /dev/null +++ b/docs/_overwrites/Common/ObjectProperties.Overwrites.md @@ -0,0 +1,174 @@ +--- +uid: Discord.GuildChannelProperties +example: [*content] +--- + +The following example uses @Discord.IGuildChannel.ModifyAsync* to +apply changes specified in the properties, + +```cs +var channel = _client.GetChannel(id) as IGuildChannel; +if (channel == null) return; + +await channel.ModifyAsync(x => +{ + x.Name = "new-name"; + x.Position = channel.Position - 1; +}); +``` + +--- +uid: Discord.TextChannelProperties +example: [*content] +--- + +The following example uses @Discord.ITextChannel.ModifyAsync* to +apply changes specified in the properties, + +```cs +var channel = _client.GetChannel(id) as ITextChannel; +if (channel == null) return; + +await channel.ModifyAsync(x => +{ + x.Name = "cool-guys-only"; + x.Topic = "This channel is only for cool guys and adults!!!"; + x.Position = channel.Position - 1; + x.IsNsfw = true; +}); +``` + +--- +uid: Discord.VoiceChannelProperties +example: [*content] +--- + +The following example uses @Discord.IVoiceChannel.ModifyAsync* to +apply changes specified in the properties, + +```cs +var channel = _client.GetChannel(id) as IVoiceChannel; +if (channel == null) return; + +await channel.ModifyAsync(x => +{ + x.UserLimit = 5; +}); +``` + +--- +uid: Discord.EmoteProperties +example: [*content] +--- + +The following example uses @Discord.IGuild.ModifyEmoteAsync* to +apply changes specified in the properties, + +```cs +await guild.ModifyEmoteAsync(x => +{ + x.Name = "blobo"; +}); +``` + +--- +uid: Discord.MessageProperties +example: [*content] +--- + +The following example uses @Discord.IUserMessage.ModifyAsync* to +apply changes specified in the properties, + +```cs +var message = await channel.SendMessageAsync("boo"); +await Task.Delay(TimeSpan.FromSeconds(1)); +await message.ModifyAsync(x => x.Content = "boi"); +``` + +--- +uid: Discord.GuildProperties +example: [*content] +--- + +The following example uses @Discord.IGuild.ModifyAsync* to +apply changes specified in the properties, + +```cs +var guild = _client.GetGuild(id); +if (guild == null) return; + +await guild.ModifyAsync(x => +{ + x.Name = "VERY Fast Discord Running at Incredible Hihg Speed"; +}); +``` + +--- +uid: Discord.RoleProperties +example: [*content] +--- + +The following example uses @Discord.IRole.ModifyAsync* to +apply changes specified in the properties, + +```cs +var role = guild.GetRole(id); +if (role == null) return; + +await role.ModifyAsync(x => +{ + x.Name = "cool boi"; + x.Color = Color.Gold; + x.Hoist = true; + x.Mentionable = true; +}); +``` + +--- +uid: Discord.GuildUserProperties +example: [*content] +--- + +The following example uses @Discord.IGuildUser.ModifyAsync* to +apply changes specified in the properties, + +```cs +var user = guild.GetUser(id); +if (user == null) return; + +await user.ModifyAsync(x => +{ + x.Nickname = "I need healing"; +}); +``` + +--- +uid: Discord.SelfUserProperties +example: [*content] +--- + +The following example uses @Discord.ISelfUser.ModifyAsync* to +apply changes specified in the properties, + +```cs +await selfUser.ModifyAsync(x => +{ + x.Username = "Mercy"; +}); +``` + +--- +uid: Discord.WebhookProperties +example: [*content] +--- + +The following example uses @Discord.IWebhook.ModifyAsync* to +apply changes specified in the properties, + +```cs +await webhook.ModifyAsync(x => +{ + x.Name = "very fast fox"; + x.ChannelId = newChannelId; +}); +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md b/docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md new file mode 100644 index 000000000..29b547e49 --- /dev/null +++ b/docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md @@ -0,0 +1,24 @@ +--- +uid: Discord.Commands.OverrideTypeReaderAttribute +remarks: *content +--- + +This attribute is used to override a command parameter's type reading +behaviour. This may be useful when you have multiple custom +@Discord.Commands.TypeReader and would like to specify one. + +--- +uid: Discord.Commands.OverrideTypeReaderAttribute +examples: [*content] +--- + +The following example will override the @Discord.Commands.TypeReader +of @Discord.IUser to `MyUserTypeReader`. + +```cs +public async Task PrintUserAsync( + [OverrideTypeReader(typeof(MyUserTypeReader))] IUser user) +{ + //... +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/images/embed-example.png b/docs/_overwrites/Common/images/embed-example.png new file mode 100644 index 000000000..f23fb4d70 Binary files /dev/null and b/docs/_overwrites/Common/images/embed-example.png differ diff --git a/docs/_overwrites/Common/images/react-example.png b/docs/_overwrites/Common/images/react-example.png new file mode 100644 index 000000000..822857d3d Binary files /dev/null and b/docs/_overwrites/Common/images/react-example.png differ diff --git a/docs/_template/bootstrap-modal/plugins/DocFx.Plugin.ImageProcessor.dll b/docs/_template/bootstrap-modal/plugins/DocFx.Plugin.ImageProcessor.dll new file mode 100644 index 000000000..c608b941a Binary files /dev/null and b/docs/_template/bootstrap-modal/plugins/DocFx.Plugin.ImageProcessor.dll differ diff --git a/docs/_template/lastmodified/plugins/LastModifiedPostProcessor.dll b/docs/_template/lastmodified/plugins/LastModifiedPostProcessor.dll new file mode 100644 index 000000000..c527ec698 Binary files /dev/null and b/docs/_template/lastmodified/plugins/LastModifiedPostProcessor.dll differ diff --git a/docs/_template/light-dark-theme/partials/affix.tmpl.partial b/docs/_template/light-dark-theme/partials/affix.tmpl.partial new file mode 100644 index 000000000..ffbd27dcb --- /dev/null +++ b/docs/_template/light-dark-theme/partials/affix.tmpl.partial @@ -0,0 +1,32 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + diff --git a/docs/_template/light-dark-theme/partials/head.tmpl.partial b/docs/_template/light-dark-theme/partials/head.tmpl.partial new file mode 100644 index 000000000..3deee4aba --- /dev/null +++ b/docs/_template/light-dark-theme/partials/head.tmpl.partial @@ -0,0 +1,25 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + + {{#_description}}{{/_description}} + + + + + + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_enableNewTab}}{{/_enableNewTab}} + + diff --git a/docs/_template/light-dark-theme/partials/scripts.tmpl.partial b/docs/_template/light-dark-theme/partials/scripts.tmpl.partial new file mode 100644 index 000000000..324d16794 --- /dev/null +++ b/docs/_template/light-dark-theme/partials/scripts.tmpl.partial @@ -0,0 +1,10 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + + + + + \ No newline at end of file diff --git a/docs/_template/light-dark-theme/styles/cornerify.js b/docs/_template/light-dark-theme/styles/cornerify.js new file mode 100644 index 000000000..4430f2d01 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/cornerify.js @@ -0,0 +1,3 @@ +window.onload = function (e) { + $('img').corner(); +} diff --git a/docs/_template/light-dark-theme/styles/dark.css b/docs/_template/light-dark-theme/styles/dark.css new file mode 100644 index 000000000..19899c7e9 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/dark.css @@ -0,0 +1,263 @@ +/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */ + +@import url('vs2015.css'); +html, +body { + background: #212121; + color: #C0C0C0; +} + +button, +a { + color: #64B5F6; +} + +.sidenav{ + background-color: rgb(30, 30, 30); +} + +button:hover, +button:focus, +a:hover, +a:focus, +.btn:focus, +.btn:hover{ + color: #2196F3; +} + +a.disable, +a.disable:hover { + color: #EEEEEE; +} + +.divider { + color: #37474F; +} + +hr { + border-color: #37474F; +} + +.subnav { + background: #383838 +} + +.inheritance h5, +.inheritedMembers h5 { + border-bottom: 1px solid #37474F; +} + +article h4 { + border-bottom: 1px solid #37474F; +} + +.docs-search { + background: #424242; +} + +.search-results-group-heading { + color: #424242; +} + +.search-close { + color: #424242; +} + +.sidetoc { + background-color: #1b1b1b; + border-left: 0px solid #37474F; + border-right: 0px solid #37474F; +} + +.sideaffix { + overflow: visible; +} + +.sideaffix>div.contribution>ul>li>a.contribution-link:hover { + background-color: #333333; +} + +.toc .nav>li>a { + color: rgb(218, 218, 218); +} + +.toc .nav>li>a:hover, +.toc .nav>li>a:focus { + color: #E0E0E0; +} + +.toc .nav>li.active>a { + color: #90CAF9; +} + +.toc .nav>li.active>a:hover, +.toc .nav>li.active>a:focus { + background-color: #37474F; + color: #4FC3F7; +} + +.sidefilter { + background-color: #1b1b1b; + border-left: 0px solid #37474F; + border-right: 0px solid #37474F; +} + +.affix ul>li>a:hover { + background: none; + color: #EEEEEE; +} + +.affix ul>li.active>a, +.affix ul>li.active>a:before { + color: #B3E5FC; +} + +.affix ul>li>a { + color: #EEEEEE; +} + +.affix>ul>li.active>a, +.affix>ul>li.active>a:before { + color: #B3E5FC; +} + +.tryspan { + border-color: #37474F; +} + +.footer { + border-top: 1px solid #5F5F5F; + background: #616161; +} + +.alert-info { + color: #d9edf7; + background: #004458; + border-color: #005873 +} + +.alert-warning { + color: #fffaf2; + background: #80551a; + border-color: #99661f +} + +.alert-danger { + color: #fff2f2; + background: #4d0000; + border-color: #660000 +} + +/* For tabbed content */ + +.tabGroup { + margin-top: 1rem; +} + +.tabGroup ul[role="tablist"] { + margin: 0; + padding: 0; + list-style: none; +} + +.tabGroup ul[role="tablist"]>li { + list-style: none; + display: inline-block; +} + +.tabGroup a[role="tab"] { + color: white; + box-sizing: border-box; + display: inline-block; + padding: 5px 7.5px; + text-decoration: none; + border-bottom: 2px solid #fff; +} + +.tabGroup a[role="tab"]:hover, +.tabGroup a[role="tab"]:focus, +.tabGroup a[role="tab"][aria-selected="true"] { + border-bottom: 2px solid #607D8B; +} + +.tabGroup a[role="tab"][aria-selected="true"] { + color: #81D4FA; +} + +.tabGroup a[role="tab"]:hover, +.tabGroup a[role="tab"]:focus { + color: #29B6F6; +} + +.tabGroup a[role="tab"]:focus { + outline: 1px solid #607D8B; + outline-offset: -1px; +} + +@media (min-width: 768px) { + .tabGroup a[role="tab"] { + padding: 5px 15px; + } +} + +.tabGroup section[role="tabpanel"] { + border: 1px solid #607D8B; + padding: 15px; + margin: 0; + overflow: hidden; +} + +.tabGroup section[role="tabpanel"]>.codeHeader, +.tabGroup section[role="tabpanel"]>pre { + margin-left: -16px; + margin-right: -16px; +} + +.tabGroup section[role="tabpanel"]> :first-child { + margin-top: 0; +} + +.tabGroup section[role="tabpanel"]>pre:last-child { + display: block; + margin-bottom: -16px; +} + +.mainContainer[dir='rtl'] main ul[role="tablist"] { + margin: 0; +} + +/* code */ + +code { + color: white; + background-color: #4a4c52; + border-radius: 4px; + padding: 3px 7px; +} + +pre { + background-color: #282a36; +} + +/* table */ + +.table-striped>tbody>tr:nth-of-type(odd) { + background-color: #333333; + color: #d3d3d3 +} + +tbody>tr { + background-color: #424242; + color: #c0c0c0 +} + +.table>tbody+tbody { + border-top: 2px solid rgb(173, 173, 173) +} + +/* select */ + +select { + background-color: #3b3b3b; + border-color: #2e2e2e; +} diff --git a/docs/_template/light-dark-theme/styles/light.css b/docs/_template/light-dark-theme/styles/light.css new file mode 100644 index 000000000..a602274a2 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/light.css @@ -0,0 +1,56 @@ +/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */ + +html, +body { + background: #fff; + color: #000; +} + +.sideaffix { + overflow: visible; +} + +/* links */ + +a:active, a:hover, a:visited { + color: #0078d7; +} + +a { + color: #0050c5; + cursor: pointer; + text-decoration: none; + word-wrap: break-word; +} + +/* code */ + +code { + color: #3b5269; + background-color: #ececec; + border-radius: 4px; + padding: 3px 7px; +} + +/* table */ + +.table-striped>tbody>tr:nth-of-type(odd) { + color: #333333; + background-color: #d3d3d3 +} + +tbody>tr { + color: #424242; + background-color: #c0c0c0 +} + +.table>tbody+tbody { + border-top: 2px solid rgb(173, 173, 173) +} + +/* select */ + +select { + background-color: #fcfcfc; + border-color: #aeb1b5; +} diff --git a/docs/_template/light-dark-theme/styles/master.css b/docs/_template/light-dark-theme/styles/master.css new file mode 100644 index 000000000..c8dbe8884 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/master.css @@ -0,0 +1,65 @@ +@import url('https://fonts.googleapis.com/css?family=Titillium+Web'); +html, +body { + font-family: 'Titillium Web', 'Segoe UI', Tahoma, Helvetica, sans-serif; + height: 100%; + font-size: 15px; +} + +p, +li, +.toc { + line-height: 160%; +} + +img { + box-shadow: 0px 0px 3px 0px rgb(66, 66, 66); + max-width: 95% !important; + margin-top: 15px; + margin-bottom: 15px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + line-height: 130%; +} + +.sideaffix { + line-height: 140%; +} + +header .navbar { + border-width: 0 0 0px; + border-radius: 0; +} + +body .toc { + background-color: inherit; + overflow: visible; +} + +select { + display: inline-block; + overflow: auto; + -webkit-box-sizing: border-box; + box-sizing: border-box; + margin: 0; + padding: 0 30px 0 6px; + vertical-align: middle; + height: 28px; + border: 1px solid #e3e3e3; + line-height: 16px; + outline: 0; + text-overflow: ellipsis; + -webkit-appearance: none; + -moz-appearance: none; + cursor: pointer; + background-image: linear-gradient(45deg, transparent 50%, #707070 0), linear-gradient(135deg, #707070 50%, transparent 0); + background-position: calc(100% - 13px) 11px, calc(100% - 8px) 11px; + background-size: 5px 5px, 5px 6px; + background-repeat: no-repeat; +} diff --git a/docs/_template/light-dark-theme/styles/plugin-featherlight.js b/docs/_template/light-dark-theme/styles/plugin-featherlight.js new file mode 100644 index 000000000..846494cf3 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/plugin-featherlight.js @@ -0,0 +1,15 @@ +$(document).ready(function() { + //find all images, but not the logo, and add the lightbox + $('img').not('#logo').each(function(){ + var $img = $(this); + var filename = $img.attr('src') + //add cursor + $img.css('cursor','zoom-in'); + $img.css('cursor','-moz-zoom-in'); + $img.css('cursor','-webkit-zoom-in'); + + //add featherlight + $img.attr('alt', filename); + $img.featherlight(filename); + }); +}); \ No newline at end of file diff --git a/docs/_template/light-dark-theme/styles/styleswitcher.js b/docs/_template/light-dark-theme/styles/styleswitcher.js new file mode 100644 index 000000000..a87b89525 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/styleswitcher.js @@ -0,0 +1,26 @@ +const baseUrl = document.getElementById("docfx-style:rel").content; + +function onThemeSelect(event) { + const theme = event.target.value; + window.localStorage.setItem("theme", theme); + window.themeElement.href = getUrl(theme); +} + +function getUrl(slug) { + return baseUrl + "styles/" + slug + ".css"; +} + +const themeElement = document.createElement("link"); +themeElement.rel = "stylesheet"; + +const theme = window.localStorage.getItem("theme") || "light"; +themeElement.href = getUrl(theme); + +document.head.appendChild(themeElement); +window.themeElement = themeElement; + +document.addEventListener("DOMContentLoaded", function() { + const themeSwitcher = document.getElementById("theme-switcher"); + themeSwitcher.onchange = onThemeSelect; + themeSwitcher.value = theme; +}, false); diff --git a/docs/_template/light-dark-theme/styles/theme-switcher.css b/docs/_template/light-dark-theme/styles/theme-switcher.css new file mode 100644 index 000000000..c6e27c93a --- /dev/null +++ b/docs/_template/light-dark-theme/styles/theme-switcher.css @@ -0,0 +1,9 @@ +div.theme-switch-field { + padding-left: 10px; + padding-bottom: 15px +} + +div.theme-switch-field > p{ + font-weight: bold; + font-size: 1.2em; +} \ No newline at end of file diff --git a/docs/_template/light-dark-theme/styles/vs2015.css b/docs/_template/light-dark-theme/styles/vs2015.css new file mode 100644 index 000000000..d8c14a046 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/vs2015.css @@ -0,0 +1,115 @@ +/* + * Visual Studio 2015 dark style + * Author: Nicolas LLOBERA + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #282a36; + color: #DCDCDC; +} + +.hljs-keyword, +.hljs-literal, +.hljs-symbol, +.hljs-name { + color: #569CD6; +} +.hljs-link { + color: #569CD6; + text-decoration: underline; +} + +.hljs-built_in, +.hljs-type { + color: #4EC9B0; +} + +.hljs-number, +.hljs-class { + color: #B8D7A3; +} + +.hljs-string, +.hljs-meta-string { + color: #D69D85; +} + +.hljs-regexp, +.hljs-template-tag { + color: #9A5334; +} + +.hljs-subst, +.hljs-function, +.hljs-title, +.hljs-params, +.hljs-formula { + color: #DCDCDC; +} + +.hljs-comment, +.hljs-quote { + color: #57A64A; + font-style: italic; +} + +.hljs-doctag { + color: #608B4E; +} + +.hljs-meta, +.hljs-meta-keyword, +.hljs-tag { + color: #9B9B9B; +} + +.hljs-variable, +.hljs-template-variable { + color: #BD63C5; +} + +.hljs-attr, +.hljs-attribute, +.hljs-builtin-name { + color: #9CDCFE; +} + +.hljs-section { + color: gold; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +/*.hljs-code { + font-family:'Monospace'; +}*/ + +.hljs-bullet, +.hljs-selector-tag, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #D7BA7D; +} + +.hljs-addition { + background-color: #144212; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #600; + display: inline-block; + width: 100%; +} diff --git a/docs/api/index.md b/docs/api/index.md index d9433363f..c16ca1363 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -1,13 +1,16 @@ +--- +uid: API.Docs +--- # API Documentation -This is where you will find documentation for all members and objects in Discord.Net +This is where you will find documentation for all members and objects in Discord.Net. -__Commonly Used Entities__ +# Commonly Used Entities -* @Discord.WebSocket -* @Discord.WebSocket.DiscordSocketClient -* @Discord.WebSocket.SocketGuildChannel -* @Discord.WebSocket.SocketGuildUser -* @Discord.WebSocket.SocketMessage -* @Discord.WebSocket.SocketRole \ No newline at end of file +* @Discord.WebSocket +* @Discord.WebSocket.DiscordSocketClient +* @Discord.WebSocket.SocketGuildChannel +* @Discord.WebSocket.SocketGuildUser +* @Discord.WebSocket.SocketMessage +* @Discord.WebSocket.SocketRole \ No newline at end of file diff --git a/docs/docfx.json b/docs/docfx.json index 50ae39092..663a49cda 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -1,74 +1,53 @@ { - "metadata": [ - { - "src": [ - { - "src": "..", - "files": [ - "src/**/*.cs" - ], - "exclude": [ - "**/obj/**", - "**/bin/**", - "_site/**" - ] - } - ], - "dest": "api", - "filter": "filterConfig.yml" + "metadata": [{ + "src": [{ + "src": "../src", + "files": [ + "**.csproj" + ] + }], + "dest": "api", + "filter": "filterConfig.yml", + "properties": { + "TargetFramework": "netstandard1.3" } - ], + }], "build": { - "content": [ - { - "files": [ - "api/**.yml", - "api/index.md" - ] + "content": [{ + "files": ["api/**.yml", "api/index.md"] }, { - "files": [ - "guides/**.md", - "guides/**/toc.yml", - "toc.yml", - "*.md" - ], - "exclude": [ - "obj/**", - "_site/**" - ] - } - ], - "resource": [ + "files": ["toc.yml", "index.md"] + }, { - "files": [ - "**/images/**", - "**/samples/**" - ], - "exclude": [ - "obj/**", - "_site/**" - ] - } - ], - "overwrite": [ + "files": ["faq/**.md", "faq/**/toc.yml"] + }, { - "files": [ - "apidoc/**.md" - ], - "exclude": [ - "obj/**", - "_site/**" - ] + "files": ["guides/**.md", "guides/**/toc.yml"] } ], + "resource": [{ + "files": [ + "**/images/**", + "**/samples/**" + ] + }], "dest": "_site", "template": [ - "default" + "default", + "_template/light-dark-theme", + "_template/lastmodified" ], + "postProcessors": [ "ExtractSearchIndex", "LastModifiedPostProcessor" ], + "overwrite": "_overwrites/**/**.md", "globalMetadata": { - "_appFooter": "Discord.Net (c) 2015-2018 2.0.0-beta" + "_appTitle": "Discord.Net Documentation", + "_appFooter": "Discord.Net (c) 2015-2018 2.0.0-beta", + "_enableSearch": true }, - "noLangKeyword": false + "noLangKeyword": false, + "xrefService": [ + "https://xref.docs.microsoft.com/query?uid={uid}" + ] } } diff --git a/docs/faq/basics/basic-operations.md b/docs/faq/basics/basic-operations.md new file mode 100644 index 000000000..aef28a683 --- /dev/null +++ b/docs/faq/basics/basic-operations.md @@ -0,0 +1,111 @@ +--- +uid: FAQ.Basics.BasicOp +title: Questions about Basic Operations +--- + +# Basic Operations Questions + +## How should I safely check a type? + +> [!WARNING] +> Direct casting (e.g., `(Type)type`) is **the least recommended** +> way of casting, as it *can* throw an [InvalidCastException] +> when the object isn't the desired type. +> +> Please refer to [this post] for more details. + +In Discord.Net, the idea of polymorphism is used throughout. You may +need to cast the object as a certain type before you can perform any +action. + +A good and safe casting example: + +[!code-csharp[Casting](samples/cast.cs)] + +[InvalidCastException]: https://docs.microsoft.com/en-us/dotnet/api/system.invalidcastexception +[this post]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/how-to-safely-cast-by-using-as-and-is-operators + +## How do I send a message? + +> [!TIP] +> The [GetChannel] method by default returns an [IChannel], allowing +> channel types such as [IVoiceChannel], [ICategoryChannel] +> to be returned; consequently, you cannot send a message +> to channels like those. + +Any implementation of [IMessageChannel] has a [SendMessageAsync] +method. You can get the channel via [GetChannel] under the client. +Remember, when using Discord.Net, polymorphism is a common recurring +theme. This means an object may take in many shapes or form, which +means casting is your friend. You should attempt to cast the channel +as an [IMessageChannel] or any other entity that implements it to be +able to message. + +[SendMessageAsync]: xref:Discord.IMessageChannel.SendMessageAsync* +[GetChannel]: xref:Discord.WebSocket.DiscordSocketClient.GetChannel* + +## How can I tell if a message is from X, Y, Z channel? + +You may check the message channel type. Visit [Glossary] to see the +various types of channels. + +[Glossary]: xref:FAQ.Misc.Glossary#message-channels + +## How can I get the guild from a message? + +There are 2 ways to do this. You can do either of the following, + +1. Cast the user as an [IGuildUser] and use its [IGuild] property. +2. Cast the channel as an [IGuildChannel] and use its [IGuild] property. + +## How do I add hyperlink text to an embed? + +Embeds can use standard [markdown] in the description field as well +as in field values. With that in mind, links can be added with +`[text](link)`. + +[markdown]: https://support.discordapp.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline- + +## How do I add reactions to a message? + +Any entity that implements [IUserMessage] has an [AddReactionAsync] +method. This method expects an [IEmote] as a parameter. +In Discord.Net, an Emote represents a custom-image emote, while an +Emoji is a Unicode emoji (standard emoji). Both [Emoji] and [Emote] +implement [IEmote] and are valid options. + +[!code-csharp[Emoji](samples/emoji.cs)] + +[AddReactionAsync]: xref:Discord.IUserMessage.AddReactionAsync* + +## What is a "preemptive rate limit?" + +A preemptive rate limit is Discord.Net's way of telling you to slow +down before you get hit by the real rate limit. Hitting a real rate +limit might prevent your entire client from sending any requests for +a period of time. This is calculated based on the HTTP header +returned by a Discord response. + +## Why am I getting so many preemptive rate limits when I try to add more than one reactions? + +This is due to how HTML header works, mistreating +0.25sec/action to 1sec. This causes the lib to throw preemptive rate +limit more frequently than it should for methods such as adding +reactions. + +## Can I opt-out of preemptive rate limits? + +Unfortunately, not at the moment. See [#401](https://github.com/RogueException/Discord.Net/issues/401). + +[IChannel]: xref:Discord.IChannel +[ICategoryChannel]: xref:Discord.ICategoryChannel +[IGuildChannel]: xref:Discord.IGuildChannel +[ITextChannel]: xref:Discord.ITextChannel +[IGuild]: xref:Discord.IGuild +[IVoiceChannel]: xref:Discord.IVoiceChannel +[IGuildUser]: xref:Discord.IGuildUser +[IMessageChannel]: xref:Discord.IMessageChannel +[IUserMessage]: xref:Discord.IUserMessage +[IEmote]: xref:Discord.IEmote +[Emote]: xref:Discord.Emote +[Emoji]: xref:Discord.Emoji \ No newline at end of file diff --git a/docs/faq/basics/client-basics.md b/docs/faq/basics/client-basics.md new file mode 100644 index 000000000..48386206c --- /dev/null +++ b/docs/faq/basics/client-basics.md @@ -0,0 +1,62 @@ +--- +uid: FAQ.Basics.ClientBasics +title: Basic Questions about Client +--- + +# Client Basics Questions + +## My client keeps returning 401 upon logging in! + +> [!WARNING] +> Userbot/selfbot (logging in with a user token) is no +> longer supported with this library starting from 2.0, as +> logging in under a user account may result in account termination. +> +> For more information, see issue [827] & [958], as well as the official +> [Discord API Terms of Service]. + +There are few possible reasons why this may occur. + +1. You are not using the appropriate [TokenType]. If you are using a + bot account created from the Discord Developer portal, you should + be using `TokenType.Bot`. +2. You are not using the correct login credentials. Please keep in + mind that a token is **different** from a *client secret*. + +[TokenType]: xref:Discord.TokenType +[827]: https://github.com/RogueException/Discord.Net/issues/827 +[958]: https://github.com/RogueException/Discord.Net/issues/958 +[Discord API Terms of Service]: https://discordapp.com/developers/docs/legal + +## How do I do X, Y, Z when my bot connects/logs on? Why do I get a `NullReferenceException` upon calling any client methods after connect? + +Your bot should **not** attempt to interact in any way with +guilds/servers until the [Ready] event fires. When the bot +connects, it first has to download guild information from +Discord for you to get access to any server +information; the client is not ready at this point. + +Technically, the [GuildAvailable] event fires once the data for a +particular guild has downloaded; however, it is best to wait for all +guilds to be downloaded. Once all downloads are complete, the [Ready] +event is triggered, then you can proceed to do whatever you like. + +[Ready]: xref:Discord.WebSocket.DiscordSocketClient.Ready +[GuildAvailable]: xref:Discord.WebSocket.BaseSocketClient.GuildAvailable + +## How do I get a message's previous content when that message is edited? + +If you need to do anything with messages (e.g., checking Reactions, +checking the content of edited/deleted messages), you must set the +[MessageCacheSize] in your [DiscordSocketConfig] settings in order to +use the cached message entity. Read more about it [here](xref:Guides.Concepts.Events#cacheable). + +1. Message Cache must be enabled. +2. Hook the MessageUpdated event. This event provides a *before* and + *after* object. +3. Only messages received *after* the bot comes online will be + available in the cache. + +[MessageCacheSize]: xref:Discord.WebSocket.DiscordSocketConfig.MessageCacheSize +[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig +[MessageUpdated]: xref:Discord.WebSocket.BaseSocketClient.MessageUpdated \ No newline at end of file diff --git a/docs/faq/basics/getting-started.md b/docs/faq/basics/getting-started.md new file mode 100644 index 000000000..6e39aeed0 --- /dev/null +++ b/docs/faq/basics/getting-started.md @@ -0,0 +1,75 @@ +--- +uid: FAQ.Basics.GetStarted +title: Beginner Questions / How to Get Started +--- + +# Basic Concepts / Getting Started + +## How do I add my bot to my server/guild? + +You can do so by using the [permission calculator] provided +by [FiniteReality]. +This tool allows you to set permissions that the bot will be assigned +with, and invite the bot into your guild. With this method, bots will +also be assigned a unique role that a regular user cannot use; this +is what we call a `Managed` role. Because you cannot assign this +role to any other users, it is much safer than creating a single +role which, intentionally or not, can be applied to other users +to escalate their privilege. + +[FiniteReality]: https://github.com/FiniteReality/permissions-calculator +[permission calculator]: https://finitereality.github.io/permissions-calculator + +## What is a token? + +A token is a credential used to log into an account. This information +should be kept **private** and for your eyes only. Anyone with your +token can log into your account. This risk applies to both user +and bot accounts. That also means that you should **never** hardcode +your token or add it into source control, as your identity may be +stolen by scrape bots on the internet that scours through +constantly to obtain a token. + +## What is a client/user/object ID? + +Each user and object on Discord has its own snowflake ID generated +based on various conditions. + +![Snowflake Generation](images/snowflake.png) + +The ID can be seen by anyone; it is public. It is merely used to +identify an object in the Discord ecosystem. Many things in the +Discord ecosystem require an ID to retrieve or identify the said +object. + +There are 2 common ways to obtain the said ID. + +### [Discord Developer Mode](#tab/dev-mode) + +By enabling the developer mode you can right click on most objects +to obtain their snowflake IDs (please note that this may not apply to +all objects, such as role IDs, or DM channel IDs). + +![Developer Mode](images/dev-mode.png) + +### [Escape Character](#tab/escape-char) + +You can escape an object by using `\` in front the object in the +Discord client. For example, when you do `\@Example#1234` in chat, +it will return the user ID of the aforementioned user. + +![Escaping mentions](images/mention-escape.png) + +*** + +## How do I get the role ID? + +> [!WARNING] +> Right-clicking on the role and copying the ID will **not** work. +> This will only copy the message ID. + +Several common ways to do this: + +1. Make the role mentionable and mention the role, and escape it + using the `\` character in front. +2. Inspect the roles collection within the guild via your debugger. \ No newline at end of file diff --git a/docs/faq/basics/images/dev-mode.png b/docs/faq/basics/images/dev-mode.png new file mode 100644 index 000000000..fd20b95d1 Binary files /dev/null and b/docs/faq/basics/images/dev-mode.png differ diff --git a/docs/faq/basics/images/mention-escape.png b/docs/faq/basics/images/mention-escape.png new file mode 100644 index 000000000..927978061 Binary files /dev/null and b/docs/faq/basics/images/mention-escape.png differ diff --git a/docs/faq/basics/images/snowflake.png b/docs/faq/basics/images/snowflake.png new file mode 100644 index 000000000..816a10eee Binary files /dev/null and b/docs/faq/basics/images/snowflake.png differ diff --git a/docs/faq/basics/samples/cast.cs b/docs/faq/basics/samples/cast.cs new file mode 100644 index 000000000..73ef5237f --- /dev/null +++ b/docs/faq/basics/samples/cast.cs @@ -0,0 +1,15 @@ +public async Task MessageReceivedHandler(SocketMessage msg) +{ + // Option 1: + // Using the `as` keyword, which will return `null` if the object isn't the desired type. + var usermsg = msg as SocketUserMessage; + // We bail when the message isn't the desired type. + if (msg == null) return; + + // Option 2: + // Using the `is` keyword to cast (C#7 or above only) + if (msg is SocketUserMessage usermsg) + { + // Do things + } +} \ No newline at end of file diff --git a/docs/faq/basics/samples/emoji.cs b/docs/faq/basics/samples/emoji.cs new file mode 100644 index 000000000..dd3e6317f --- /dev/null +++ b/docs/faq/basics/samples/emoji.cs @@ -0,0 +1,18 @@ +// bail if the message is not a user one (system messages cannot have reactions) +var usermsg = msg as IUserMessage; +if (usermsg == null) return; + +// standard Unicode emojis +Emoji emoji = new Emoji("👍"); +// or +// Emoji emoji = new Emoji("\uD83D\uDC4D"); + +// custom guild emotes +Emote emote = Emote.Parse("<:dotnet:232902710280716288>"); +// using Emote.TryParse may be safer in regards to errors being thrown; +// please note that the method does not verify if the emote exists, +// it simply creates the Emote object for you. + +// add the reaction to the message +await usermsg.AddReactionAsync(emoji); +await usermsg.AddReactionAsync(emote); \ No newline at end of file diff --git a/docs/faq/commands/commands.md b/docs/faq/commands/commands.md new file mode 100644 index 000000000..2c905eaad --- /dev/null +++ b/docs/faq/commands/commands.md @@ -0,0 +1,162 @@ +--- +uid: FAQ.Commands +title: Questions about Commands +--- + +# Command-related Questions + +## How can I restrict some of my commands so only specific users can execute them? + +Based on how you want to implement the restrictions, you can use the +built-in [RequireUserPermission] precondition, which allows you to +restrict the command based on the user's current permissions in the +guild or channel (*e.g., `GuildPermission.Administrator`, +`ChannelPermission.ManageMessages`*). + +If, however, you wish to restrict the commands based on the user's +role, you can either create your custom precondition or use +Joe4evr's [Preconditions Addons] that provides a few custom +preconditions that aren't provided in the stock library. +Its source can also be used as an example for creating your +custom preconditions. + +[RequireUserPermission]: xref:Discord.Commands.RequireUserPermissionAttribute +[Preconditions Addons]: https://github.com/Joe4evr/Discord.Addons/tree/master/src/Discord.Addons.Preconditions + +## Why am I getting an error about `Assembly.GetEntryAssembly`? + +You may be confusing @Discord.Commands.CommandService.AddModulesAsync* +with @Discord.Commands.CommandService.AddModuleAsync*. The former +is used to add modules via the assembly, while the latter is used to +add a single module. + +## What does [Remainder] do in the command signature? + +The [RemainderAttribute] leaves the string unparsed, meaning you +do not have to add quotes around the text for the text to be +recognized as a single object. Please note that if your method has +multiple parameters, the remainder attribute can only be applied to +the last parameter. + +[!code-csharp[Remainder](samples/Remainder.cs)] + +[RemainderAttribute]: xref:Discord.Commands.RemainderAttribute + +## What is a service? Why does my module not hold any data after execution? + +In Discord.Net, modules are created similarly to ASP.NET, meaning +that they have a transient nature; modules are spawned whenever a +request is received, and are killed from memory when the execution +finishes. In other words, you cannot store persistent +data inside a module. Consider using a service if you wish to +workaround this. + +Service is often used to hold data externally so that they persist +throughout execution. Think of it like a chest that holds +whatever you throw at it that won't be affected by anything unless +you want it to. Note that you should also learn Microsoft's +implementation of [Dependency Injection] \([video]) before proceeding, +as well as how it works in [Discord.Net](xref:Guides.Commands.DI#usage-in-modules). + +A brief example of service and dependency injection can be seen below. + +[!code-csharp[DI](samples/DI.cs)] + +[Dependency Injection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection +[video]: https://www.youtube.com/watch?v=QtDTfn8YxXg + +## Discord.Net keeps saying that a `MessageReceived` handler is blocking the gateway, what should I do? + +By default, the library warns the user about any long-running event +handler that persists for **more than 3 seconds**. Any event +handlers that are run on the same thread as the gateway task, the task +in charge of keeping the connection alive, may block the processing of +heartbeat, and thus terminating the connection. + +In this case, the library detects that a `MessageReceived` +event handler is blocking the gateway thread. This warning is +typically associated with the command handler as it listens for that +particular event. If the command handler is blocking the thread, then +this **might** mean that you have a long-running command. + +> [!NOTE] +> In rare cases, runtime errors can also cause blockage, usually +> associated with Mono, which is not supported by this library. + +To prevent a long-running command from blocking the gateway +thread, a flag called [RunMode] is explicitly designed to resolve +this issue. + +There are 2 main `RunMode`s. + +1. `RunMode.Sync` +2. `RunMode.Async` + +`Sync` is the default behavior and makes the command to be run on the +same thread as the gateway one. `Async` will spin the task off to a +different thread from the gateway one. + +> [!IMPORTANT] +> While specifying `RunMode.Async` allows the command to be spun off +> to a different thread, keep in mind that by doing so, there will be +> **potentially unwanted consequences**. Before applying this flag, +> please consider whether it is necessary to do so. +> +> Further details regarding `RunMode.Async` can be found below. + +You can set the `RunMode` either by specifying it individually via +the `CommandAttribute` or by setting the global default with +the [DefaultRunMode] flag under `CommandServiceConfig`. + +# [CommandAttribute](#tab/cmdattrib) + +[!code-csharp[Command Attribute](samples/runmode-cmdattrib.cs)] + +# [CommandServiceConfig](#tab/cmdconfig) + +[!code-csharp[Command Service Config](samples/runmode-cmdconfig.cs)] + +*** + +*** + +[RunMode]: xref:Discord.Commands.RunMode +[CommandAttribute]: xref:Discord.Commands.CommandAttribute +[DefaultRunMode]: xref:Discord.Commands.CommandServiceConfig.DefaultRunMode + +## How does `RunMode.Async` work, and why is Discord.Net *not* using it by default? + +`RunMode.Async` works by spawning a new `Task` with an unawaited +[Task.Run], essentially making the task that is used to invoke the +command task to be finished on a different thread. This design means +that [ExecuteAsync] will be forced to return a successful +[ExecuteResult] regardless of the actual execution result. + +The following are the known caveats with `RunMode.Async`, + +1. You can potentially introduce a race condition. +2. Unnecessary overhead caused by the [async state machine]. +3. [ExecuteAsync] will immediately return [ExecuteResult] instead of + other result types (this is particularly important for those who wish + to utilize [RuntimeResult] in 2.0). +4. Exceptions are swallowed. + +However, there are ways to remedy some of these. + +For #3, in Discord.Net 2.0, the library introduces a new event called +[CommandExecuted], which is raised whenever the command is +**successfully executed**. This event will be raised regardless of +the `RunMode` type and will return the appropriate execution result. + +For #4, exceptions are caught in [CommandService.Log] event under +[LogMessage.Exception] as [CommandException]. + +[Task.Run]: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run +[async state machine]: https://www.red-gate.com/simple-talk/dotnet/net-tools/c-async-what-is-it-and-how-does-it-work/ +[ExecuteAsync]: xref:Discord.Commands.CommandService.ExecuteAsync* +[ExecuteResult]: xref:Discord.Commands.ExecuteResult +[RuntimeResult]: xref:Discord.Commands.RuntimeResult +[CommandExecuted]: xref:Discord.Commands.CommandService.CommandExecuted +[CommandService.Log]: xref:Discord.Commands.CommandService.Log +[LogMessage.Exception]: xref:Discord.LogMessage.Exception* +[CommandException]: xref:Discord.Commands.CommandException \ No newline at end of file diff --git a/docs/faq/commands/samples/DI.cs b/docs/faq/commands/samples/DI.cs new file mode 100644 index 000000000..59f098ff9 --- /dev/null +++ b/docs/faq/commands/samples/DI.cs @@ -0,0 +1,27 @@ +public class MyService +{ + public string MyCoolString {get; set;} +} +public class Setup +{ + public IServiceProvider BuildProvider() => + new ServiceCollection() + .AddSingleton() + .BuildServiceProvider(); +} +public class MyModule : ModuleBase +{ + // Inject via public settable prop + public MyService MyService {get; set;} + + // or via ctor + private readonly MyService _myService; + public MyModule (MyService myService) => _myService = myService; + + [Command("string")] + public Task GetOrSetStringAsync(string input) + { + if (_myService.MyCoolString == null) _myService.MyCoolString = input; + return ReplyAsync(_myService.MyCoolString); + } +} \ No newline at end of file diff --git a/docs/faq/commands/samples/Remainder.cs b/docs/faq/commands/samples/Remainder.cs new file mode 100644 index 000000000..337fb6e45 --- /dev/null +++ b/docs/faq/commands/samples/Remainder.cs @@ -0,0 +1,20 @@ +// Input: +// !echo Coffee Cake + +// Output: +// Coffee Cake +[Command("echo")] +public Task EchoRemainderAsync([Remainder]string text) => ReplyAsync(text); + +// Output: +// CommandError.BadArgCount +[Command("echo-hassle")] +public Task EchoAsync(string text) => ReplyAsync(text); + +// The message would be seen as having multiple parameters, +// while the method only accepts one. +// Wrapping the message in quotes solves this. +// This way, the system knows the entire message is to be parsed as a +// single String. +// e.g., +// !echo "Coffee Cake" \ No newline at end of file diff --git a/docs/faq/commands/samples/runmode-cmdattrib.cs b/docs/faq/commands/samples/runmode-cmdattrib.cs new file mode 100644 index 000000000..253acc4a9 --- /dev/null +++ b/docs/faq/commands/samples/runmode-cmdattrib.cs @@ -0,0 +1,7 @@ +[Command("process", RunMode = RunMode.Async)] +public async Task ProcessAsync(string input) +{ + // Does heavy calculation here. + await Task.Delay(TimeSpan.FromMinute(1)); + await ReplyAsync(input); +} \ No newline at end of file diff --git a/docs/faq/commands/samples/runmode-cmdconfig.cs b/docs/faq/commands/samples/runmode-cmdconfig.cs new file mode 100644 index 000000000..22b356aa3 --- /dev/null +++ b/docs/faq/commands/samples/runmode-cmdconfig.cs @@ -0,0 +1,9 @@ +public class Setup +{ + private readonly CommandService _command; + public Setup() + { + var config = new CommandServiceConfig{DefaultRunMode = RunMode.Async}; + _command = new CommandService(config); + } +} \ No newline at end of file diff --git a/docs/faq/misc/glossary.md b/docs/faq/misc/glossary.md new file mode 100644 index 000000000..44dbb9a9e --- /dev/null +++ b/docs/faq/misc/glossary.md @@ -0,0 +1,73 @@ +--- +uid: FAQ.Misc.Glossary +title: Common Terminologies / Glossary +--- + +# Glossary + +## Common Types + +* A **Guild** ([IGuild]) is an isolated collection of users and +channels, and are often referred to as "servers". + - Example: [Discord API](https://discord.gg/jkrBmQR) +* A **Channel** ([IChannel]) represents a generic channel. + - Example: #dotnet_discord-net + - See [Channel Types](#channel-types) + +[IGuild]: xref:Discord.IGuild +[IChannel]: xref:Discord.IChannel + +## Channel Types + +### Message Channels +* A **Text channel** ([ITextChannel]) is a message channel from a +Guild. +* A **DM channel** ([IDMChannel]) is a message channel from a DM. +* A **Group channel** ([IGroupChannel]) is a message channel from a +Group. + - This is rarely used due to the bot's inability to join groups. +* A **Private channel** ([IPrivateChannel]) is a DM or a Group. +* A **Message channel** ([IMessageChannel]) can be any of the above. + +### Misc Channels +* A **Guild channel** ([IGuildChannel]) is a guild channel in a guild. + - This can be any channels that may exist in a guild. +* A **Voice channel** ([IVoiceChannel]) is a voice channel in a guild. +* A **Category channel** ([ICategoryChannel]) (2.0+) is a category that +holds one or more sub-channels. + +[IGuildChannel]: xref:Discord.IGuildChannel +[IMessageChannel]: xref:Discord.IMessageChannel +[ITextChannel]: xref:Discord.ITextChannel +[IGroupChannel]: xref:Discord.IGroupChannel +[IDMChannel]: xref:Discord.IDMChannel +[IPrivateChannel]: xref:Discord.IPrivateChannel +[IVoiceChannel]: xref:Discord.IVoiceChannel +[ICategoryChannel]: xref:Discord.ICategoryChannel + +## Emoji Types + +* An **Emote** ([Emote]) is a custom emote from a guild. + - Example: `<:dotnet:232902710280716288>` +* An **Emoji** ([Emoji]) is a Unicode emoji. + - Example: `👍` + +[Emote]: xref:Discord.Emote +[Emoji]: xref:Discord.Emoji + +## Activity Types + +* A **Game** ([Game]) refers to a user's game activity. +* A **Rich Presence** ([RichGame]) refers to a user's detailed +gameplay status. + - Visit [Rich Presence Intro] on Discord docs for more info. +* A **Streaming Status** ([StreamingGame]) refers to user's activity +for streaming on services such as Twitch. +* A **Spotify Status** ([SpotifyGame]) (2.0+) refers to a user's +activity for listening to a song on Spotify. + +[Game]: xref:Discord.Game +[RichGame]: xref:Discord.RichGame +[StreamingGame]: xref:Discord.StreamingGame +[SpotifyGame]: xref:Discord.SpotifyGame +[Rich Presence Intro]: https://discordapp.com/developers/docs/rich-presence/best-practices \ No newline at end of file diff --git a/docs/faq/misc/legacy.md b/docs/faq/misc/legacy.md new file mode 100644 index 000000000..fec6ba24d --- /dev/null +++ b/docs/faq/misc/legacy.md @@ -0,0 +1,26 @@ +--- +uid: FAQ.Misc.Legacy +title: Questions about Legacy Versions +--- + +# Legacy Questions + +## X, Y, Z does not work! It doesn't return a valid value anymore + +If you are currently using an older version of the stable branch, +please upgrade to the latest pre-release version to ensure maximum +compatibility. Several features may be broken in older +versions and will likely not be fixed in the version branch due to +their breaking nature. + +Visit the repo's [release tag] to see the latest public pre-release. + +[release tag]: https://github.com/RogueException/Discord.Net/releases + +## I came from an earlier version of Discord.Net 1.0, and DependencyMap doesn't seem to exist anymore in the later revision? What happened to it? + +The `DependencyMap` has been replaced with Microsoft's +[DependencyInjection] Abstractions. An example usage can be seen +[here](https://github.com/foxbot/DiscordBotBase/blob/csharp/src/DiscordBot/Program.cs#L36). + +[DependencyInjection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection \ No newline at end of file diff --git a/docs/faq/toc.yml b/docs/faq/toc.yml new file mode 100644 index 000000000..1a6cd21bb --- /dev/null +++ b/docs/faq/toc.yml @@ -0,0 +1,18 @@ +- name: Basic Concepts + items: + - name: Getting Started + topicUid: FAQ.Basics.GetStarted + - name: Basic Operations + topicUid: FAQ.Basics.BasicOp + - name: Client Basics + topicUid: FAQ.Basics.ClientBasics +- name: Command + items: + - name: Commands + topicUid: FAQ.Commands +- name: Misc + items: + - name: Glossary + topicUid: FAQ.Misc.Glossary + - name: Legacy or Upgrade + topicUid: FAQ.Misc.Legacy diff --git a/docs/filterConfig.yml b/docs/filterConfig.yml index 715b39606..410a80d25 100644 --- a/docs/filterConfig.yml +++ b/docs/filterConfig.yml @@ -1,11 +1,7 @@ apiRules: - exclude: - uidRegex: ^Discord\.API$ + uidRegex: ^Discord\.Net\..*$ + type: Namespace - exclude: - uidRegex: ^Discord\.API.*$ -- exclude: - uidRegex: ^Discord\.Net\.Converters$ -- exclude: - uidRegex: ^Discord\.Net.*$ -- exclude: - uidRegex: ^RegexAnalyzer$ \ No newline at end of file + uidRegex: ^Discord\.Analyzers$ + type: Namespace \ No newline at end of file diff --git a/docs/guides/commands/commands.md b/docs/guides/commands/commands.md deleted file mode 100644 index 2b012af0e..000000000 --- a/docs/guides/commands/commands.md +++ /dev/null @@ -1,343 +0,0 @@ -# The Command Service - -[Discord.Commands](xref:Discord.Commands) provides an Attribute-based -command parser. - -## Setup - -To use Commands, you must create a [Command Service] and a Command -Handler. - -Included below is a very barebone Command Handler. You can extend your -Command Handler as much as you like; however, the below is the bare -minimum. - -The `CommandService` will optionally accept a [CommandServiceConfig], -which _does_ set a few default values for you. It is recommended to -look over the properties in [CommandServiceConfig] and their default -values. - -[!code-csharp[Command Handler](samples/command_handler.cs)] - -[Command Service]: xref:Discord.Commands.CommandService -[CommandServiceConfig]: xref:Discord.Commands.CommandServiceConfig - -## With Attributes - -In 1.0, Commands can be defined ahead of time with attributes, or at -runtime with builders. - -For most bots, ahead-of-time Commands should be all you need, and this -is the recommended method of defining Commands. - -### Modules - -The first step to creating Commands is to create a _module_. - -A Module is an organizational pattern that allows you to write your -Commands in different classes and have them automatically loaded. - -Discord.Net's implementation of Modules is influenced heavily from -ASP.NET Core's Controller pattern. This means that the lifetime of a -module instance is only as long as the Command is being invoked. - -**Avoid using long-running code** in your modules wherever possible. -You should **not** be implementing very much logic into your modules, -instead, outsource to a service for that. - -If you are unfamiliar with Inversion of Control, it is recommended to -read the MSDN article on [IoC] and [Dependency Injection]. - -To begin, create a new class somewhere in your project and inherit the -class from [ModuleBase]. This class **must** be `public`. - ->[!NOTE] ->[ModuleBase] is an _abstract_ class, meaning that you may extend it ->or override it as you see fit. Your module may inherit from any ->extension of ModuleBase. - -By now, your module should look like this: - -[!code-csharp[Empty Module](samples/empty-module.cs)] - -[IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx -[Dependency Injection]: https://msdn.microsoft.com/en-us/library/ff921152.aspx -[ModuleBase]: xref:Discord.Commands.ModuleBase`1 - -### Adding Commands - -The next step to creating Commands is actually creating the Commands. - -To create a Command, add a method to your module of type `Task`. -Typically, you will want to mark this method as `async`, although it -is not required. - -Adding parameters to a Command is done by adding parameters to the -parent Task. - -For example, to take an integer as an argument from the user, add `int -arg`; to take a user as an argument from the user, add `IUser user`. -In 1.0, a Command can accept nearly any type of argument; a full list -of types that are parsed by default can be found in the below section -on _Type Readers_. - -Parameters, by default, are always required. To make a parameter -optional, give it a default value. To accept a comma-separated list, -set the parameter to `params Type[]`. - -Should a parameter include spaces, it **must** be wrapped in quotes. -For example, for a Command with a parameter `string food`, you would -execute it with `!favoritefood "Key Lime Pie"`. - -If you would like a parameter to parse until the end of a Command, -flag the parameter with the [RemainderAttribute]. This will allow a -user to invoke a Command without wrapping a parameter in quotes. - -Finally, flag your Command with the [CommandAttribute]. (you must -specify a name for this Command, except for when it is part of a -Module Group - see below) - -[RemainderAttribute]: xref:Discord.Commands.RemainderAttribute -[CommandAttribute]: xref:Discord.Commands.CommandAttribute - -### Command Overloads - -You may add overloads to your Commands, and the Command parser will -automatically pick up on it. - -If for whatever reason, you have two Commands which are ambiguous to -each other, you may use the @Discord.Commands.PriorityAttribute to -specify which should be tested before the other. - -The `Priority` attributes are sorted in ascending order; the higher -priority will be called first. - -### Command Context - -Every Command can access the execution context through the [Context] -property on [ModuleBase]. `ICommandContext` allows you to access the -message, channel, guild, and user that the Command was invoked from, -as well as the underlying Discord client that the Command was invoked -from. - -Different types of Contexts may be specified using the generic variant -of [ModuleBase]. When using a [SocketCommandContext], for example, the -properties on this context will already be Socket entities, so you -will not need to cast them. - -To reply to messages, you may also invoke [ReplyAsync], instead of -accessing the channel through the [Context] and sending a message. - -> [!WARNING] ->Contexts should **NOT** be mixed! You cannot have one module that ->uses `CommandContext` and another that uses `SocketCommandContext`. - -[Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context -[SocketCommandContext]: xref:Discord.Commands.SocketCommandContext -[ReplyAsync]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_ReplyAsync_System_String_System_Boolean_Discord_Embed_Discord_RequestOptions_ - -### Example Module - -At this point, your module should look comparable to this example: -[!code-csharp[Example Module](samples/module.cs)] - -#### Loading Modules Automatically - -The Command Service can automatically discover all classes in an -Assembly that inherit [ModuleBase] and load them. - -To opt a module out of auto-loading, flag it with -[DontAutoLoadAttribute]. - -Invoke [CommandService.AddModulesAsync] to discover modules and -install them. - -[DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute -[CommandService.AddModulesAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModulesAsync_Assembly_ - -#### Loading Modules Manually - -To manually load a module, invoke [CommandService.AddModuleAsync] by -passing in the generic type of your module and optionally, a -dependency map. - -[CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1 - -### Module Constructors - -Modules are constructed using Dependency Injection. Any parameters -that are placed in the Module's constructor must be injected into an -@System.IServiceProvider first. Alternatively, you may accept an -`IServiceProvider` as an argument and extract services yourself. - -### Module Properties - -Modules with `public` settable properties will have the dependencies -injected after the construction of the Module. - -### Module Groups - -Module Groups allow you to create a module where Commands are -prefixed. To create a group, flag a module with the -@Discord.Commands.GroupAttribute. - -Module groups also allow you to create **nameless Commands**, where -the [CommandAttribute] is configured with no name. In this case, the -Command will inherit the name of the group it belongs to. - -### Submodules - -Submodules are Modules that reside within another one. Typically, -submodules are used to create nested groups (although not required to -create nested groups). - -[!code-csharp[Groups and Submodules](samples/groups.cs)] - -## With Builders - -**TODO** - -## Dependency Injection - -The Command Service is bundled with a very barebone Dependency -Injection service for your convenience. It is recommended that you use -DI when writing your modules. - -### Setup - -First, you need to create an @System.IServiceProvider; you may create -your own one if you wish. - -Next, add the dependencies that your modules will use to the map. - -Finally, pass the map into the `LoadAssembly` method. Your modules -will be automatically loaded with this dependency map. - -[!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] - -### Usage in Modules - -In the constructor of your Module, any parameters will be filled in by -the @System.IServiceProvider that you've passed into `LoadAssembly`. - -Any publicly settable properties will also be filled in the same -manner. - ->[!NOTE] -> Annotating a property with a [DontInjectAttribute] attribute will prevent the -property from being injected. - ->[!NOTE] ->If you accept `CommandService` or `IServiceProvider` as a parameter -in your constructor or as an injectable property, these entries will -be filled by the `CommandService` that the Module is loaded from and -the `ServiceProvider` that is passed into it respectively. - -[!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] - -[DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute - -# Preconditions - -Precondition serve as a permissions system for your Commands. Keep in -mind, however, that they are not limited to _just_ permissions and can -be as complex as you want them to be. - ->[!NOTE] ->There are two types of Preconditions. -[PreconditionAttribute] can be applied to Modules, Groups, or Commands; -[ParameterPreconditionAttribute] can be applied to Parameters. - -[PreconditionAttribute]: xref:Discord.Commands.PreconditionAttribute -[ParameterPreconditionAttribute]: xref:Discord.Commands.ParameterPreconditionAttribute - -## Bundled Preconditions - -Commands ship with four bundled Preconditions; you may view their -usages on their respective API pages. - -- @Discord.Commands.RequireContextAttribute -- @Discord.Commands.RequireOwnerAttribute -- @Discord.Commands.RequireBotPermissionAttribute -- @Discord.Commands.RequireUserPermissionAttribute - -## Custom Preconditions - -To write your own Precondition, create a new class that inherits from -either [PreconditionAttribute] or [ParameterPreconditionAttribute] -depending on your use. - -In order for your Precondition to function, you will need to override -the [CheckPermissions] method. - -Your IDE should provide an option to fill this in for you. - -If the context meets the required parameters, return -[PreconditionResult.FromSuccess], otherwise return -[PreconditionResult.FromError] and include an error message if -necessary. - -[!code-csharp[Custom Precondition](samples/require_owner.cs)] - -[CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_ICommandContext_Discord_Commands_CommandInfo_IServiceProvider_ -[PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromSuccess -[PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromError_System_String_ - -# Type Readers - -Type Readers allow you to parse different types of arguments in -your commands. - -By default, the following Types are supported arguments: - -- bool -- char -- sbyte/byte -- ushort/short -- uint/int -- ulong/long -- float, double, decimal -- string -- DateTime/DateTimeOffset/TimeSpan -- IMessage/IUserMessage -- IChannel/IGuildChannel/ITextChannel/IVoiceChannel/IGroupChannel -- IUser/IGuildUser/IGroupUser -- IRole - -### Creating a Type Readers - -To create a `TypeReader`, create a new class that imports @Discord and -@Discord.Commands and ensure the class inherits from -@Discord.Commands.TypeReader. - -Next, satisfy the `TypeReader` class by overriding the [Read] method. - ->[!NOTE] ->In many cases, Visual Studio can fill this in for you, using the ->"Implement Abstract Class" IntelliSense hint. - -Inside this task, add whatever logic you need to parse the input -string. - -If you are able to successfully parse the input, return -[TypeReaderResult.FromSuccess] with the parsed input, otherwise return -[TypeReaderResult.FromError] and include an error message if -necessary. - -[TypeReaderResult]: xref:Discord.Commands.TypeReaderResult -[TypeReaderResult.FromSuccess]: xref:Discord.Commands.TypeReaderResult#Discord_Commands_TypeReaderResult_FromSuccess_Discord_Commands_TypeReaderValue_ -[TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult#Discord_Commands_TypeReaderResult_FromError_Discord_Commands_CommandError_System_String_ -[Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_ICommandContext_System_String_IServiceProvider_ - -#### Sample - -[!code-csharp[TypeReaders](samples/typereader.cs)] - -### Installing TypeReaders - -TypeReaders are not automatically discovered by the Command Service -and must be explicitly added. - -To install a TypeReader, invoke [CommandService.AddTypeReader]. - -[CommandService.AddTypeReader]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_ diff --git a/docs/guides/commands/dependency-injection.md b/docs/guides/commands/dependency-injection.md new file mode 100644 index 000000000..db63e00c4 --- /dev/null +++ b/docs/guides/commands/dependency-injection.md @@ -0,0 +1,45 @@ +--- +uid: Guides.Commands.DI +title: Dependency Injection +--- + +# Dependency Injection + +The Command Service is bundled with a very barebone Dependency +Injection service for your convenience. It is recommended that you use +DI when writing your modules. + +## Setup + +1. Create an @System.IServiceProvider. +2. Add the dependencies to the service collection that you wish + to use in the modules. +3. Pass the service collection into `AddModulesAsync`. + +### Example - Setting up Injection + +[!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] + +## Usage in Modules + +In the constructor of your module, any parameters will be filled in by +the @System.IServiceProvider that you've passed. + +Any publicly settable properties will also be filled in the same +manner. + +> [!NOTE] +> Annotating a property with a [DontInjectAttribute] attribute will +> prevent the property from being injected. + +> [!NOTE] +> If you accept `CommandService` or `IServiceProvider` as a parameter +> in your constructor or as an injectable property, these entries will +> be filled by the `CommandService` that the module is loaded from and +> the `IServiceProvider` that is passed into it respectively. + +### Example - Injection in Modules + +[!code-csharp[IServiceProvider in Modules](samples/dependency_module.cs)] + +[DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute \ No newline at end of file diff --git a/docs/guides/commands/intro.md b/docs/guides/commands/intro.md new file mode 100644 index 000000000..e2fad73e8 --- /dev/null +++ b/docs/guides/commands/intro.md @@ -0,0 +1,221 @@ +--- +uid: Guides.Commands.Intro +title: Introduction to Command Service +--- + +# The Command Service + +[Discord.Commands](xref:Discord.Commands) provides an attribute-based +command parser. + +## Get Started + +To use commands, you must create a [Command Service] and a command +handler. + +Included below is a barebone command handler. You can extend your +command handler as much as you like; however, the below is the bare +minimum. + +> [!NOTE] +> The `CommandService` will optionally accept a [CommandServiceConfig], +> which *does* set a few default values for you. It is recommended to +> look over the properties in [CommandServiceConfig] and their default +> values. + +[!code-csharp[Command Handler](samples/command_handler.cs)] + +[Command Service]: xref:Discord.Commands.CommandService +[CommandServiceConfig]: xref:Discord.Commands.CommandServiceConfig + +## With Attributes + +Starting from 1.0, commands can be defined ahead of time with +attributes, or at runtime with builders. + +For most bots, ahead-of-time commands should be all you need, and this +is the recommended method of defining commands. + +### Modules + +The first step to creating commands is to create a _module_. + +A module is an organizational pattern that allows you to write your +commands in different classes and have them automatically loaded. + +Discord.Net's implementation of "modules" is influenced heavily from +ASP.NET Core's Controller pattern. This means that the lifetime of a +module instance is only as long as the command is being invoked. + +Before we create a module, it is **crucial** for you to remember that +in order to create a module and have it automatically discovered, +your module must: + +* Be public +* Inherit [ModuleBase] + +By now, your module should look like this: + +[!code-csharp[Empty Module](samples/empty-module.cs)] + +> [!NOTE] +> [ModuleBase] is an `abstract` class, meaning that you may extend it +> or override it as you see fit. Your module may inherit from any +> extension of ModuleBase. + +[IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx +[Dependency Injection]: https://msdn.microsoft.com/en-us/library/ff921152.aspx +[ModuleBase]: xref:Discord.Commands.ModuleBase`1 + +### Adding/Creating Commands + +> [!WARNING] +> **Avoid using long-running code** in your modules wherever possible. +> You should **not** be implementing very much logic into your +> modules, instead, outsource to a service for that. +> +> If you are unfamiliar with Inversion of Control, it is recommended +> to read the MSDN article on [IoC] and [Dependency Injection]. + +The next step to creating commands is actually creating the commands. + +For a command to be valid, it **must** have a return type of `Task` +or `Task`. Typically, you might want to mark this +method as `async`, although it is not required. + +Then, flag your command with the [CommandAttribute]. Note that you must +specify a name for this command, except for when it is part of a +[Module Group](#module-groups). + +### Command Parameters + +Adding parameters to a command is done by adding parameters to the +parent `Task`. + +For example: + +* To take an integer as an argument from the user, add `int num`. +* To take a user as an argument from the user, add `IUser user`. +* ...etc. + +Starting from 1.0, a command can accept nearly any type of argument; +a full list of types that are parsed by default can +be found in @Guides.Commands.TypeReaders. + +[CommandAttribute]: xref:Discord.Commands.CommandAttribute + +#### Optional Parameters + +Parameters, by default, are always required. To make a parameter +optional, give it a default value (i.e., `int num = 0`). + +#### Parameters with Spaces + +To accept a comma-separated list, set the parameter to `params Type[]`. + +Should a parameter include spaces, the parameter **must** be +wrapped in quotes. For example, for a command with a parameter +`string food`, you would execute it with +`!favoritefood "Key Lime Pie"`. + +If you would like a parameter to parse until the end of a command, +flag the parameter with the [RemainderAttribute]. This will +allow a user to invoke a command without wrapping a +parameter in quotes. + +[RemainderAttribute]: xref:Discord.Commands.RemainderAttribute + +### Command Overloads + +You may add overloads to your commands, and the command parser will +automatically pick up on it. + +If, for whatever reason, you have two commands which are ambiguous to +each other, you may use the @Discord.Commands.PriorityAttribute to +specify which should be tested before the other. + +The `Priority` attributes are sorted in ascending order; the higher +priority will be called first. + +### Command Context + +Every command can access the execution context through the [Context] +property on [ModuleBase]. `ICommandContext` allows you to access the +message, channel, guild, user, and the underlying Discord client +that the command was invoked from. + +Different types of `Context` may be specified using the generic variant +of [ModuleBase]. When using a [SocketCommandContext], for example, the +properties on this context will already be Socket entities, so you +will not need to cast them. + +To reply to messages, you may also invoke [ReplyAsync], instead of +accessing the channel through the [Context] and sending a message. + +> [!WARNING] +> Contexts should **NOT** be mixed! You cannot have one module that +> uses `CommandContext` and another that uses `SocketCommandContext`. + +[Context]: xref:Discord.Commands.ModuleBase`1.Context +[SocketCommandContext]: xref:Discord.Commands.SocketCommandContext +[ReplyAsync]: xref:Discord.Commands.ModuleBase`1.ReplyAsync* + +> [!TIP] +> At this point, your module should look comparable to this example: +> [!code-csharp[Example Module](samples/module.cs)] + +#### Loading Modules Automatically + +The Command Service can automatically discover all classes in an +`Assembly` that inherit [ModuleBase] and load them. Invoke +[CommandService.AddModulesAsync] to discover modules and +install them. + +To opt a module out of auto-loading, flag it with +[DontAutoLoadAttribute]. + +[DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute +[CommandService.AddModulesAsync]: xref:Discord.Commands.CommandService.AddModulesAsync* + +#### Loading Modules Manually + +To manually load a module, invoke [CommandService.AddModuleAsync] by +passing in the generic type of your module and optionally, a +service provider. + +[CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService.AddModuleAsync* + +### Module Constructors + +Modules are constructed using @Guides.Commands.DI. Any parameters +that are placed in the Module's constructor must be injected into an +@System.IServiceProvider first. + +> [!TIP] +> Alternatively, you may accept an +> `IServiceProvider` as an argument and extract services yourself, +> although this is discouraged. + +### Module Properties + +Modules with `public` settable properties will have the dependencies +injected after the construction of the module. See @Guides.Commands.DI +to learn more. + +### Module Groups + +Module Groups allow you to create a module where commands are +prefixed. To create a group, flag a module with the +@Discord.Commands.GroupAttribute. + +Module Groups also allow you to create **nameless Commands**, where +the [CommandAttribute] is configured with no name. In this case, the +command will inherit the name of the group it belongs to. + +### Submodules + +Submodules are "modules" that reside within another one. Typically, +submodules are used to create nested groups (although not required to +create nested groups). + +[!code-csharp[Groups and Submodules](samples/groups.cs)] \ No newline at end of file diff --git a/docs/guides/commands/post-execution.md b/docs/guides/commands/post-execution.md new file mode 100644 index 000000000..267f84b8f --- /dev/null +++ b/docs/guides/commands/post-execution.md @@ -0,0 +1,122 @@ +--- +uid: Guides.Commands.PostExecution +title: Post-command Execution Handling +--- + +# Preface + +When developing commands, you may want to consider building a +post-execution handling system so you can have finer control +over commands. Discord.Net offers several post-execution workflows +for you to work with. + +If you recall, in the [Command Guide], we have shown the following +example for executing and handling commands, + +[!code[Command Handler](samples/command_handler.cs)] + +You may notice that after we perform [ExecuteAsync], we store the +result and print it to the chat, essentially creating the most +fundamental form of a post-execution handler. + +With this in mind, we could start doing things like the following, + +[!code[Basic Command Handler](samples/post-execution_basic.cs)] + +However, this may not always be preferred, because you are +creating your post-execution logic *with* the essential command +handler. This design could lead to messy code and could potentially +be a violation of the SRP (Single Responsibility Principle). + +Another major issue is if your command is marked with +`RunMode.Async`, [ExecuteAsync] will **always** return a successful +[ExecuteResult] instead of the actual result. You can learn more +about the impact in the [FAQ](xref:FAQ.Commands). + +## CommandExecuted Event + +Enter [CommandExecuted], an event that was introduced in +Discord.Net 2.0. This event is raised whenever a command is +successfully executed **without any run-time exceptions** or **any +parsing or precondition failure**. This means this event can be +used to streamline your post-execution design, and the best thing +about this event is that it is not prone to `RunMode.Async`'s +[ExecuteAsync] drawbacks. + +Thus, we can begin working on code such as: + +[!code[CommandExecuted demo](samples/command_executed_demo.cs)] + +So now we have a streamlined post-execution pipeline, great! What's +next? We can take this further by using [RuntimeResult]. + +### RuntimeResult + +`RuntimeResult` was initially introduced in 1.0 to allow +developers to centralize their command result logic. +In other words, it is a result type that is designed to be +returned when the command has finished its execution. + +However, it wasn't widely adopted due to the aforementioned +[ExecuteAsync] drawback. Since we now have access to a proper +result-handler via the [CommandExecuted] event, we can start +making use of this class. + +The best way to make use of it is to create your version of +`RuntimeResult`. You can achieve this by inheriting the `RuntimeResult` +class. + +The following creates a bare-minimum required for a sub-class +of `RuntimeResult`, + +[!code[Base Use](samples/customresult_base.cs)] + +The sky is the limit from here. You can add any additional information +you would like regarding the execution result. + +For example, you may want to add your result type or other +helpful information regarding the execution, or something +simple like static methods to help you create return types easily. + +[!code[Extended Use](samples/customresult_extended.cs)] + +After you're done creating your [RuntimeResult], you can +implement it in your command by marking the command return type to +`Task`. + +> [!NOTE] +> You must mark the return type as `Task` instead of +> `Task`. Only the former will be picked up when +> building the module. + +Here's an example of a command that utilizes such logic: + +[!code[Usage](samples/customresult_usage.cs)] + +And now we can check for it in our [CommandExecuted] handler: + +[!code[Usage](samples/command_executed_adv_demo.cs)] + +## CommandService.Log Event + +We have so far covered the handling of various result types, but we +have not talked about what to do if the command enters a catastrophic +failure (i.e., exceptions). To resolve this, we can make use of the +[CommandService.Log] event. + +All exceptions thrown during a command execution are caught and sent +to the Log event under the [LogMessage.Exception] property +as a [CommandException] type. The [CommandException] class allows +us to access the exception thrown, as well as the context +of the command. + +[!code[Logger Sample](samples/command_exception_log.cs)] + +[CommandException]: xref:Discord.Commands.CommandException +[LogMessage.Exception]: xref:Discord.LogMessage.Exception +[CommandService.Log]: xref:Discord.Commands.CommandService.Log +[RuntimeResult]: xref:Discord.Commands.RuntimeResult +[CommandExecuted]: xref:Discord.Commands.CommandService.CommandExecuted +[ExecuteAsync]: xref:Discord.Commands.CommandService.ExecuteAsync* +[ExecuteResult]: xref:Discord.Commands.ExecuteResult +[Command Guide]: xref:Guides.Commands.Intro \ No newline at end of file diff --git a/docs/guides/commands/preconditions.md b/docs/guides/commands/preconditions.md new file mode 100644 index 000000000..7fe6dd3a8 --- /dev/null +++ b/docs/guides/commands/preconditions.md @@ -0,0 +1,58 @@ +--- +uid: Guides.Commands.Preconditions +title: Preconditions +--- + +# Preconditions + +Preconditions serve as a permissions system for your Commands. Keep in +mind, however, that they are not limited to _just_ permissions and can +be as complex as you want them to be. + +There are two types of Preconditions you can use: + +* [PreconditionAttribute] can be applied to Modules, Groups, or Commands. +* [ParameterPreconditionAttribute] can be applied to Parameters. + +You may visit their respective API documentation to find out more. + +[PreconditionAttribute]: xref:Discord.Commands.PreconditionAttribute +[ParameterPreconditionAttribute]: xref:Discord.Commands.ParameterPreconditionAttribute + +## Bundled Preconditions + +@Discord.Commands ships with several bundled Preconditions for you +to use. + +* @Discord.Commands.RequireContextAttribute +* @Discord.Commands.RequireOwnerAttribute +* @Discord.Commands.RequireBotPermissionAttribute +* @Discord.Commands.RequireUserPermissionAttribute +* @Discord.Commands.RequireNsfwAttribute + +## Custom Preconditions + +To write your own Precondition, create a new class that inherits from +either [PreconditionAttribute] or [ParameterPreconditionAttribute] +depending on your use. + +In order for your Precondition to function, you will need to override +the [CheckPermissionsAsync] method. + +If the context meets the required parameters, return +[PreconditionResult.FromSuccess], otherwise return +[PreconditionResult.FromError] and include an error message if +necessary. + +> [!NOTE] +> Visual Studio can help you implement missing members +> from the abstract class by using the "Implement Abstract Class" +> IntelliSense hint. + +### Example - Creating a Custom Precondition + +[!code-csharp[Custom Precondition](samples/require_owner.cs)] + +[CheckPermissionsAsync]: xref:Discord.Commands.PreconditionAttribute.CheckPermissionsAsync* +[PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult.FromSuccess* +[PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult.FromError* diff --git a/docs/guides/commands/samples/command_exception_log.cs b/docs/guides/commands/samples/command_exception_log.cs new file mode 100644 index 000000000..2956a0203 --- /dev/null +++ b/docs/guides/commands/samples/command_exception_log.cs @@ -0,0 +1,13 @@ +public async Task LogAsync(LogMessage logMessage) +{ + // This casting type requries C#7 + if (logMessage.Exception is CommandException cmdException) + { + // We can tell the user that something unexpected has happened + await cmdException.Context.Channel.SendMessageAsync("Something went catastrophically wrong!"); + + // We can also log this incident + Console.WriteLine($"{cmdException.Context.User} failed to execute '{cmdException.Command.Name}' in {cmdException.Context.Channel}."); + Console.WriteLine(cmdException.ToString()); + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/command_executed_adv_demo.cs b/docs/guides/commands/samples/command_executed_adv_demo.cs new file mode 100644 index 000000000..1ce40c51d --- /dev/null +++ b/docs/guides/commands/samples/command_executed_adv_demo.cs @@ -0,0 +1,13 @@ +public async Task OnCommandExecutedAsync(CommandInfo command, ICommandContext context, IResult result) +{ + switch(result) + { + case MyCustomResult customResult: + // do something extra with it + break; + default: + if (!string.IsNullOrEmpty(result.ErrorReason)) + await context.Channel.SendMessageAsync(result.ErrorReason); + break; + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/command_executed_demo.cs b/docs/guides/commands/samples/command_executed_demo.cs new file mode 100644 index 000000000..8d8fb911b --- /dev/null +++ b/docs/guides/commands/samples/command_executed_demo.cs @@ -0,0 +1,38 @@ +public async Task SetupAsync() +{ + await _command.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + // Hook the execution event + _command.CommandExecuted += OnCommandExecutedAsync; + // Hook the command handler + _client.MessageReceived += HandleCommandAsync; +} +public async Task OnCommandExecutedAsync(CommandInfo command, ICommandContext context, IResult result) +{ + // We have access to the information of the command executed, + // the context of the command, and the result returned from the + // execution in this event. + + // We can tell the user what went wrong + if (!string.IsNullOrEmpty(result?.ErrorReason)) + { + await context.Channel.SendMessageAsync(result.ErrorReason); + } + + // ...or even log the result (the method used should fit into + // your existing log handler) + await _log.LogAsync(new LogMessage(LogSeverity.Info, "CommandExecution", $"{command?.Name} was executed at {DateTime.UtcNow}.")); +} +public async Task HandleCommandAsync(SocketMessage msg) +{ + var message = messageParam as SocketUserMessage; + if (message == null) return; + int argPos = 0; + if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return; + var context = new SocketCommandContext(_client, message); + var result = await _commands.ExecuteAsync(context, argPos, _services); + // Optionally, you may pass the result manually into your + // CommandExecuted event handler if you wish to handle parsing or + // precondition failures in the same method. + + // await OnCommandExecutedAsync(null, context, result); +} \ No newline at end of file diff --git a/docs/guides/commands/samples/command_handler.cs b/docs/guides/commands/samples/command_handler.cs index da2453aa8..470be2707 100644 --- a/docs/guides/commands/samples/command_handler.cs +++ b/docs/guides/commands/samples/command_handler.cs @@ -1,46 +1,20 @@ -using System; -using System.Threading.Tasks; -using System.Reflection; -using Discord; -using Discord.WebSocket; -using Discord.Commands; -using Microsoft.Extensions.DependencyInjection; - -public class Program +public class CommandHandle { - private CommandService _commands; - private DiscordSocketClient _client; - private IServiceProvider _services; - - private static void Main(string[] args) => new Program().StartAsync().GetAwaiter().GetResult(); + private readonly DiscordSocketClient _client; + private readonly CommandService _commands; - public async Task StartAsync() + public CommandHandle(DiscordSocketClient client) { - _client = new DiscordSocketClient(); + _client = client; _commands = new CommandService(); - - // Avoid hard coding your token. Use an external source instead in your code. - string token = "bot token here"; - - _services = new ServiceCollection() - .AddSingleton(_client) - .AddSingleton(_commands) - .BuildServiceProvider(); - - await InstallCommandsAsync(); - - await _client.LoginAsync(TokenType.Bot, token); - await _client.StartAsync(); - - await Task.Delay(-1); } - + public async Task InstallCommandsAsync() { // Hook the MessageReceived Event into our Command Handler _client.MessageReceived += HandleCommandAsync; // Discover all of the commands in this assembly and load them. - await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); } private async Task HandleCommandAsync(SocketMessage messageParam) diff --git a/docs/guides/commands/samples/customresult_base.cs b/docs/guides/commands/samples/customresult_base.cs new file mode 100644 index 000000000..895a370c7 --- /dev/null +++ b/docs/guides/commands/samples/customresult_base.cs @@ -0,0 +1,6 @@ +public class MyCustomResult : RuntimeResult +{ + public MyCustomResult(CommandError? error, string reason) : base(error, reason) + { + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/customresult_extended.cs b/docs/guides/commands/samples/customresult_extended.cs new file mode 100644 index 000000000..6754c96aa --- /dev/null +++ b/docs/guides/commands/samples/customresult_extended.cs @@ -0,0 +1,10 @@ +public class MyCustomResult : RuntimeResult +{ + public MyCustomResult(CommandError? error, string reason) : base(error, reason) + { + } + public static MyCustomResult FromError(string reason) => + new MyCustomResult(CommandError.Unsuccessful, reason); + public static MyCustomResult FromSuccess(string reason = null) => + new MyCustomResult(null, reason); +} \ No newline at end of file diff --git a/docs/guides/commands/samples/customresult_usage.cs b/docs/guides/commands/samples/customresult_usage.cs new file mode 100644 index 000000000..f7ec303a2 --- /dev/null +++ b/docs/guides/commands/samples/customresult_usage.cs @@ -0,0 +1,10 @@ +public class MyModule : ModuleBase +{ + [Command("eat")] + public async Task ChooseAsync(string food) + { + if (food == "salad") + return MyCustomResult.FromError("No salad allowed!"); + return MyCustomResult.FromSuccess($"I'll take a {food}!"). + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/dependency_map_setup.cs b/docs/guides/commands/samples/dependency_map_setup.cs index a36925904..34e4c994e 100644 --- a/docs/guides/commands/samples/dependency_map_setup.cs +++ b/docs/guides/commands/samples/dependency_map_setup.cs @@ -14,5 +14,5 @@ public async Task InstallAsync(DiscordSocketClient client) .AddSingleton() .BuildServiceProvider(); // ... - await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); } \ No newline at end of file diff --git a/docs/guides/commands/samples/dependency_module.cs b/docs/guides/commands/samples/dependency_module.cs index 561b0f6ac..3c9b51d7d 100644 --- a/docs/guides/commands/samples/dependency_module.cs +++ b/docs/guides/commands/samples/dependency_module.cs @@ -1,40 +1,30 @@ -using Discord; -using Discord.Commands; -using Discord.WebSocket; - -public class ModuleA : ModuleBase +public class DatabaseModule : ModuleBase { private readonly DatabaseService _database; // Dependencies can be injected via the constructor - public ModuleA(DatabaseService database) + public DatabaseModule(DatabaseService database) { _database = database; } - public async Task ReadFromDb() + [Command("read")] + public async Task ReadFromDbAsync() { - var x = _database.getX(); - await ReplyAsync(x); + await ReplyAsync(_database.GetData()); } } -public class ModuleB +public class MixModule : ModuleBase { - // Public settable properties will be injected - public AnnounceService { get; set; } + public AnnounceService AnnounceService { get; set; } - // Public properties without setters will not - public CommandService Commands { get; } + // Public properties without setters will not be injected + public ImageService ImageService { get; } // Public properties annotated with [DontInject] will not + // be injected [DontInject] - public NotificationService { get; set; } - - public ModuleB(CommandService commands) - { - Commands = commands; - } - -} + public NotificationService NotificationService { get; set; } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/empty-module.cs b/docs/guides/commands/samples/empty-module.cs index 6483c7cd2..db62032c2 100644 --- a/docs/guides/commands/samples/empty-module.cs +++ b/docs/guides/commands/samples/empty-module.cs @@ -1,5 +1,7 @@ using Discord.Commands; +// Keep in mind your module **must** be public and inherit ModuleBase. +// If it isn't, it will not be discovered by AddModulesAsync! public class InfoModule : ModuleBase { diff --git a/docs/guides/commands/samples/module.cs b/docs/guides/commands/samples/module.cs index 1e3555501..e5fe70534 100644 --- a/docs/guides/commands/samples/module.cs +++ b/docs/guides/commands/samples/module.cs @@ -1,24 +1,25 @@ // Create a module with no prefix -public class Info : ModuleBase +public class InfoModule : ModuleBase { - // ~say hello -> hello + // ~say hello world -> hello world [Command("say")] [Summary("Echoes a message.")] - public async Task SayAsync([Remainder] [Summary("The text to echo")] string echo) - { - // ReplyAsync is a method on ModuleBase - await ReplyAsync(echo); - } + public Task SayAsync([Remainder] [Summary("The text to echo")] string echo) + => ReplyAsync(echo); + + // ReplyAsync is a method on ModuleBase } // Create a module with the 'sample' prefix [Group("sample")] -public class Sample : ModuleBase +public class SampleModule : ModuleBase { // ~sample square 20 -> 400 [Command("square")] [Summary("Squares a number.")] - public async Task SquareAsync([Summary("The number to square.")] int num) + public async Task SquareAsync( + [Summary("The number to square.")] + int num) { // We can also access the channel from the Command Context. await Context.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}"); @@ -31,9 +32,12 @@ public class Sample : ModuleBase // ~sample userinfo 96642168176807936 --> Khionu#8708 // ~sample whois 96642168176807936 --> Khionu#8708 [Command("userinfo")] - [Summary("Returns info about the current user, or the user parameter, if one passed.")] + [Summary + ("Returns info about the current user, or the user parameter, if one passed.")] [Alias("user", "whois")] - public async Task UserInfoAsync([Summary("The (optional) user to get info for")] SocketUser user = null) + public async Task UserInfoAsync( + [Summary("The (optional) user to get info from")] + SocketUser user = null) { var userInfo = user ?? Context.Client.CurrentUser; await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}"); diff --git a/docs/guides/commands/samples/post-execution_basic.cs b/docs/guides/commands/samples/post-execution_basic.cs new file mode 100644 index 000000000..19c7bed59 --- /dev/null +++ b/docs/guides/commands/samples/post-execution_basic.cs @@ -0,0 +1,11 @@ +var result = await _commands.ExecuteAsync(context, argPos, _services); +if (result.CommandError != null) + switch(result.CommandError) + { + case CommandError.BadArgCount: + await context.Channel.SendMessageAsync("Parameter count does not match any command's."); + break; + default: + await context.Channel.SendMessageAsync($"An error has occurred {result.ErrorReason}"); + break; + } \ No newline at end of file diff --git a/docs/guides/commands/samples/require_owner.cs b/docs/guides/commands/samples/require_owner.cs index 3611afab8..a4e95cfcb 100644 --- a/docs/guides/commands/samples/require_owner.cs +++ b/docs/guides/commands/samples/require_owner.cs @@ -1,4 +1,5 @@ -// (Note: This precondition is obsolete, it is recommended to use the RequireOwnerAttribute that is bundled with Discord.Commands) +// (Note: This precondition is obsolete, it is recommended to use the +// RequireOwnerAttribute that is bundled with Discord.Commands) using Discord.Commands; using Discord.WebSocket; @@ -10,7 +11,7 @@ using System.Threading.Tasks; public class RequireOwnerAttribute : PreconditionAttribute { // Override the CheckPermissions method - public async override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public async override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { // Get the ID of the bot's owner var ownerId = (await services.GetService().GetApplicationInfoAsync()).Owner.Id; diff --git a/docs/guides/commands/samples/typereader-register.cs b/docs/guides/commands/samples/typereader-register.cs new file mode 100644 index 000000000..292caea6f --- /dev/null +++ b/docs/guides/commands/samples/typereader-register.cs @@ -0,0 +1,29 @@ +public class CommandHandler +{ + private readonly CommandService _commands; + private readonly DiscordSocketClient _client; + private readonly IServiceProvider _services; + + public CommandHandler(CommandService commands, DiscordSocketClient client, IServiceProvider services) + { + _commands = commands; + _client = client; + _services = services; + } + + public async Task SetupAsync() + { + _client.MessageReceived += CommandHandleAsync; + + // Add BooleanTypeReader to type read for the type "bool" + _commands.AddTypeReader(typeof(bool), new BooleanTypeReader()); + + // Then register the modules + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + } + + public async Task CommandHandleAsync(SocketMessage msg) + { + // ... + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/typereader.cs b/docs/guides/commands/samples/typereader.cs index d2864a4c7..a2a4627d2 100644 --- a/docs/guides/commands/samples/typereader.cs +++ b/docs/guides/commands/samples/typereader.cs @@ -1,10 +1,11 @@ -// Note: This example is obsolete, a boolean type reader is bundled with Discord.Commands +// Note: This example is obsolete, a boolean type reader is bundled +// with Discord.Commands using Discord; using Discord.Commands; public class BooleanTypeReader : TypeReader { - public override Task Read(ICommandContext context, string input, IServiceProvider services) + public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { bool result; if (bool.TryParse(input, out result)) diff --git a/docs/guides/commands/typereaders.md b/docs/guides/commands/typereaders.md new file mode 100644 index 000000000..a502fb3c8 --- /dev/null +++ b/docs/guides/commands/typereaders.md @@ -0,0 +1,69 @@ +--- +uid: Guides.Commands.TypeReaders +title: Type Readers +--- + +# Type Readers + +Type Readers allow you to parse different types of arguments in +your commands. + +By default, the following Types are supported arguments: + +* `bool` +* `char` +* `sbyte`/`byte` +* `ushort`/`short` +* `uint`/`int` +* `ulong`/`long` +* `float`, `double`, `decimal` +* `string` +* `DateTime`/`DateTimeOffset`/`TimeSpan` +* `Nullable` where applicible +* Any implementation of `IChannel`/`IMessage`/`IUser`/`IRole` + +## Creating a Type Reader + +To create a `TypeReader`, create a new class that imports @Discord and +@Discord.Commands and ensure the class inherits from +@Discord.Commands.TypeReader. Next, satisfy the `TypeReader` class by +overriding the [ReadAsync] method. + +Inside this Task, add whatever logic you need to parse the input +string. + +If you are able to successfully parse the input, return +[TypeReaderResult.FromSuccess] with the parsed input, otherwise return +[TypeReaderResult.FromError] and include an error message if +necessary. + +> [!NOTE] +> Visual Studio can help you implement missing members +> from the abstract class by using the "Implement Abstract Class" +> IntelliSense hint. + +[TypeReaderResult]: xref:Discord.Commands.TypeReaderResult +[TypeReaderResult.FromSuccess]: xref:Discord.Commands.TypeReaderResult.FromSuccess* +[TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult.FromError* +[ReadAsync]: xref:Discord.Commands.TypeReader.ReadAsync* + +### Example - Creating a Type Reader + +[!code-csharp[TypeReaders](samples/typereader.cs)] + +## Registering a Type Reader + +TypeReaders are not automatically discovered by the Command Service +and must be explicitly added. + +To register a TypeReader, invoke [CommandService.AddTypeReader]. + +> [!IMPORTANT] +> TypeReaders must be added prior to module discovery, otherwise your +> TypeReaders may not work! + +[CommandService.AddTypeReader]: xref:Discord.Commands.CommandService.AddTypeReader* + +### Example - Adding a Type Reader + +[!code-csharp[Adding TypeReaders](samples/typereader-register.cs)] \ No newline at end of file diff --git a/docs/guides/concepts/connections.md b/docs/guides/concepts/connections.md index 30e5e55cd..99ea45756 100644 --- a/docs/guides/concepts/connections.md +++ b/docs/guides/concepts/connections.md @@ -1,51 +1,48 @@ --- +uid: Guides.Concepts.ManageConnections title: Managing Connections --- In Discord.Net, once a client has been started, it will automatically -maintain a connection to Discord's gateway, until it is manually +maintain a connection to Discord's gateway until it is manually stopped. ### Usage To start a connection, invoke the `StartAsync` method on a client that -supports a WebSocket connection. - -These clients include the [DiscordSocketClient] and -[DiscordRpcClient], as well as Audio clients. - -To end a connection, invoke the `StopAsync` method. This will -gracefully close any open WebSocket or UdpSocket connections. +supports a WebSocket connection; to end a connection, invoke the +`StopAsync` method, which gracefully closes any open WebSocket or +UdpSocket connections. Since the Start/Stop methods only signal to an underlying connection -manager that a connection needs to be started, **they return before a -connection is actually made.** +manager that a connection needs to be started, **they return before a +connection is made.** -As a result, you will need to hook into one of the connection-state +As a result, you need to hook into one of the connection-state based events to have an accurate representation of when a client is ready for use. All clients provide a `Connected` and `Disconnected` event, which is raised respectively when a connection opens or closes. In the case of -the DiscordSocketClient, this does **not** mean that the client is +the [DiscordSocketClient], this does **not** mean that the client is ready to be used. -A separate event, `Ready`, is provided on DiscordSocketClient, which +A separate event, `Ready`, is provided on [DiscordSocketClient], which is raised only when the client has finished guild stream or guild -sync, and has a complete guild cache. +sync and has a completed guild cache. [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient -[DiscordRpcClient]: xref:Discord.Rpc.DiscordRpcClient ### Samples [!code-csharp[Connection Sample](samples/events.cs)] -### Tips +### Reconnection -Avoid running long-running code on the gateway! If you deadlock the -gateway (as explained in [events]), the connection manager will be -unable to recover and reconnect. +> [!TIP] +> Avoid running long-running code on the gateway! If you deadlock the +> gateway (as explained in [events]), the connection manager will +> **NOT** be able to recover and reconnect. Assuming the client disconnected because of a fault on Discord's end, and not a deadlock on your end, we will always attempt to reconnect @@ -53,6 +50,6 @@ and resume a connection. Don't worry about trying to maintain your own connections, the connection manager is designed to be bulletproof and never fail - if -your client doesn't manage to reconnect, you've found a bug! +your client does not manage to reconnect, you have found a bug! -[events]: events.md \ No newline at end of file +[events]: xref:Guides.Concepts.Events diff --git a/docs/guides/concepts/deployment.md b/docs/guides/concepts/deployment.md new file mode 100644 index 000000000..eea747817 --- /dev/null +++ b/docs/guides/concepts/deployment.md @@ -0,0 +1,86 @@ +--- +uid: Guides.Concepts.Deployment +title: Deploying the Bot +--- + +# Deploying the Bot + +After finishing your application, you may want to deploy your bot to a +remote location such as a Virtual Private Server (VPS) or another +computer so you can keep the bot up and running 24/7. + +## Recommended VPS + +For small-medium scaled bots, a cheap VPS (~$5) might be sufficient +enough. Here is a list of recommended VPS provider. + +* [DigitalOcean](https://www.digitalocean.com/) + * Description: American cloud infrastructure provider headquartered + in New York City with data centers worldwide. + * Location(s): + * Asia: Singapore, India + * America: Canada, United States + * Europe: Netherlands, Germany, United Kingdom + * Based in: United States +* [Vultr](https://www.vultr.com/) + * Description: DigitalOcean-like + * Location(s): + * Asia: Japan, Australia, Singapore + * America: United States + * Europe: United Kingdom, France, Netherlands, Germany + * Based in: United States +* [OVH](https://www.ovh.com/) + * Description: French cloud computing company that offers VPS, + dedicated servers and other web services. + * Location(s): + * Asia: Australia, Singapore + * America: United States, Canada + * Europe: United Kingdom, Poland, Germany + * Based in: Europe +* [Scaleway](https://www.scaleway.com/) + * Description: Cheap but powerful VPS owned by [Online.net](https://online.net/). + * Location(s): + * Europe: France, Netherlands + * Based in: Europe +* [Time4VPS](https://www.time4vps.eu/) + * Description: Affordable and powerful VPS Hosting in Europe. + * Location(s): + * Europe: Lithuania + * Based in: Europe + +## .NET Core Deployment + +> [!NOTE] +> This section only covers the very basics of .NET Core deployment. +> To learn more about deployment, visit [.NET Core application deployment] +> by Microsoft. + +By default, .NET Core compiles all projects as a DLL file, so that any +.NET Core runtime can execute the application. + +You may execute the application via `dotnet myprogram.dll` assuming you +have the dotnet CLI installed. + +When redistributing the application, you may want to publish the +application, or in other words, create a self-contained package +for use on another machine without installing the dependencies first. + +This can be achieved by using the dotnet CLI too on the development +machine: + +* `dotnet publish -c Release` + +Additionally, you may want to target a specific platform when +publishing the application so you may use the application without +having to install the Core runtime on the target machine. To do this, +you may specify an [Runtime ID] upon build/publish with the `-r` +option. + +For example, when targeting a Windows 10 machine, you may want to use +the following to create the application in Windows executable +format (.exe): + +* `dotnet publish -c Release -r win10-x64` + +[.NET Core application deployment]: https://docs.microsoft.com/en-us/dotnet/core/deploying/ +[Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog \ No newline at end of file diff --git a/docs/guides/concepts/entities.md b/docs/guides/concepts/entities.md index 3a5d5496b..7415f043a 100644 --- a/docs/guides/concepts/entities.md +++ b/docs/guides/concepts/entities.md @@ -1,23 +1,26 @@ --- +uid: Guides.Concepts.Entities title: Entities --- ->[!NOTE] -This article is written with the Socket variants of entities in mind, -not the general interfaces or Rest/Rpc entities. +# Entities in Discord.Net + +> [!NOTE] +> This article is written with the Socket variants of entities in mind, +> not the general interfaces or Rest/Rpc entities. Discord.Net provides a versatile entity system for navigating the Discord API. -### Inheritance +## Inheritance Due to the nature of the Discord API, some entities are designed with multiple variants; for example, `SocketUser` and `SocketGuildUser`. All models will contain the most detailed version of an entity -possible, even if the type is less detailed. +possible, even if the type is less detailed. -For example, in the case of the `MessageReceived` event, a +For example, in the case of the `MessageReceived` event, a `SocketMessage` is passed in with a channel property of type `SocketMessageChannel`. All messages come from channels capable of messaging, so this is the only variant of a channel that can cover @@ -28,44 +31,36 @@ But that doesn't mean a message _can't_ come from a retrieve information about a guild from a message entity, you will need to cast its channel object to a `SocketTextChannel`. -### Navigation +You can find out various types of entities in the @FAQ.Misc.Glossary +page. + +## Navigation All socket entities have navigation properties on them, which allow you to easily navigate to an entity's parent or children. As explained above, you will sometimes need to cast to a more detailed version of an entity to navigate to its parent. -### Accessing Entities +## Accessing Entities The most basic forms of entities, `SocketGuild`, `SocketUser`, and `SocketChannel` can be pulled from the DiscordSocketClient's global cache, and can be retrieved using the respective `GetXXX` method on DiscordSocketClient. ->[!TIP] -It is **vital** that you use the proper IDs for an entity when using -a GetXXX method. It is recommended that you enable Discord's -_developer mode_ to allow easy access to entity IDs, found in -Settings > Appearance > Advanced +> [!TIP] +> It is **vital** that you use the proper IDs for an entity when using +> a `GetXXX` method. It is recommended that you enable Discord's +> _developer mode_ to allow easy access to entity IDs, found in +> Settings > Appearance > Advanced. Read more about it in the +> [FAQ](xref:FAQ.Basics.GetStarted) page. More detailed versions of entities can be pulled from the basic -entities, e.g. `SocketGuild.GetUser`, which returns a -`SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a +entities, e.g., `SocketGuild.GetUser`, which returns a +`SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a `SocketGuildChannel`. Again, you may need to cast these objects to get a variant of the type that you need. -### Samples - -[!code-csharp[Entity Sample](samples/entities.cs)] - -### Tips - -Avoid using boxing-casts to coerce entities into a variant, use the -[`as`] keyword, and a null-conditional operator instead. - -This allows you to write safer code and avoid [InvalidCastExceptions]. - -For example, `(message.Author as SocketGuildUser)?.Nickname`. +## Sample -[`as`]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as -[InvalidCastExceptions]: https://msdn.microsoft.com/en-us/library/system.invalidcastexception(v=vs.110).aspx \ No newline at end of file +[!code-csharp[Entity Sample](samples/entities.cs)] \ No newline at end of file diff --git a/docs/guides/concepts/events.md b/docs/guides/concepts/events.md index 47db49aa8..293b5dc72 100644 --- a/docs/guides/concepts/events.md +++ b/docs/guides/concepts/events.md @@ -1,16 +1,19 @@ --- +uid: Guides.Concepts.Events title: Working with Events --- +# Events in Discord.Net + Events in Discord.Net are consumed in a similar manner to the standard convention, with the exception that every event must be of the type -`System.Threading.Tasks.Task` and instead of using `EventArgs`, the -event's parameters are passed directly into the handler. +@System.Threading.Tasks.Task and instead of using @System.EventArgs, +the event's parameters are passed directly into the handler. This allows for events to be handled in an async context directly instead of relying on `async void`. -### Usage +## Usage To receive data from an event, hook into it using C#'s delegate event pattern. @@ -18,7 +21,7 @@ event pattern. You may either opt to hook an event to an anonymous function (lambda) or a named function. -### Safety +## Safety All events are designed to be thread-safe; events are executed synchronously off the gateway task in the same context as the gateway @@ -39,7 +42,7 @@ a deadlock that will be impossible to recover from. Exceptions in commands will be swallowed by the gateway and logged out through the client's log method. -### Common Patterns +## Common Patterns As you may know, events in Discord.Net are only given a signature of `Func`. There is no room for predefined argument names, @@ -49,7 +52,7 @@ directly. That being said, there are a variety of common patterns that allow you to infer what the parameters in an event mean. -#### Entity, Entity +### Entity, Entity An event handler with a signature of `Func` typically means that the first object will be a clone of the entity @@ -58,10 +61,10 @@ model of the entity _after_ the change was made. This pattern is typically only found on `EntityUpdated` events. -#### Cacheable +### Cacheable An event handler with a signature of `Func` -means that the `before` state of the entity was not provided by the +means that the `before` state of the entity was not provided by the API, so it can either be pulled from the client's cache or downloaded from the API. @@ -70,15 +73,12 @@ object. [Cacheable]: xref:Discord.Cacheable`2 -### Samples - -[!code-csharp[Event Sample](samples/events.cs)] +> [!NOTE] +> Many events relating to a Message entity (i.e., `MessageUpdated` and +> `ReactionAdded`) rely on the client's message cache, which is +> **not** enabled by default. Set the `MessageCacheSize` flag in +> @Discord.WebSocket.DiscordSocketConfig to enable it. -### Tips +## Sample -Many events relating to a Message entity (i.e. `MessageUpdated` and -`ReactionAdded`) rely on the client's message cache, which is -**not** enabled by default. Set the `MessageCacheSize` flag in -[DiscordSocketConfig] to enable it. - -[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig \ No newline at end of file +[!code-csharp[Event Sample](samples/events.cs)] diff --git a/docs/guides/concepts/logging.md b/docs/guides/concepts/logging.md index 50d2e9546..b92d2bd53 100644 --- a/docs/guides/concepts/logging.md +++ b/docs/guides/concepts/logging.md @@ -1,19 +1,28 @@ --- -title: Logging +uid: Guides.Concepts.Logging +title: Logging Events/Data --- -Discord.Net's clients provide a [Log] event that all messages will be -disbatched over. +# Logging in Discord.Net + +Discord.Net's clients provide a log event that all messages will be +dispatched over. For more information about events in Discord.Net, see the [Events] section. -[Log]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log -[Events]: events.md +[Events]: xref:Guides.Concepts.Events + +> [!WARNING] +> Due to the nature of Discord.Net's event system, all log event +> handlers will be executed synchronously on the gateway thread. If your +> log output will be dumped to a Web API (e.g., Sentry), you are advised +> to wrap your output in a `Task.Run` so the gateway thread does not +> become blocked while waiting for logging data to be written. -### Usage +## Usage in Client(s) -To receive log events, simply hook the discord client's log method +To receive log events, simply hook the Discord client's @Discord.Rest.BaseDiscordClient.Log to a `Task` with a single parameter of type [LogMessage]. It is recommended that you use an established function instead of a @@ -22,10 +31,10 @@ to a logging function to write their own messages. [LogMessage]: xref:Discord.LogMessage -### Usage in Commands +## Usage in Commands -Discord.Net's [CommandService] also provides a log event, identical -in signature to other log events. +Discord.Net's [CommandService] also provides a @Discord.Commands.CommandService.Log +event, identical in signature to other log events. Data logged through this event is typically coupled with a [CommandException], where information about the command's context @@ -34,14 +43,6 @@ and error can be found and handled. [CommandService]: xref:Discord.Commands.CommandService [CommandException]: xref:Discord.Commands.CommandException -#### Samples +## Sample [!code-csharp[Logging Sample](samples/logging.cs)] - -#### Tips - -Due to the nature of Discord.Net's event system, all log event -handlers will be executed synchronously on the gateway thread. If your -log output will be dumped to a Web API (e.g. Sentry), you are advised -to wrap your output in a `Task.Run` so the gateway thread does not -become blocked while waiting for logging data to be written. \ No newline at end of file diff --git a/docs/guides/concepts/samples/connections.cs b/docs/guides/concepts/samples/connections.cs index f96251a39..b610da524 100644 --- a/docs/guides/concepts/samples/connections.cs +++ b/docs/guides/concepts/samples/connections.cs @@ -10,7 +10,7 @@ public class Program { _client = new DiscordSocketClient(); - await _client.LoginAsync(TokenType.Bot, "bot token"); + await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken")); await _client.StartAsync(); Console.WriteLine("Press any key to exit..."); diff --git a/docs/guides/concepts/samples/entities.cs b/docs/guides/concepts/samples/entities.cs index 7655c44e9..64383858d 100644 --- a/docs/guides/concepts/samples/entities.cs +++ b/docs/guides/concepts/samples/entities.cs @@ -1,13 +1,11 @@ public string GetChannelTopic(ulong id) { var channel = client.GetChannel(81384956881809408) as SocketTextChannel; - if (channel == null) return ""; - return channel.Topic; + return channel?.Topic; } -public string GuildOwner(SocketChannel channel) +public SocketGuildUser GetGuildOwner(SocketChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; - if (guild == null) return ""; - return Context.Guild.Owner.Username; + return guild?.Owner; } \ No newline at end of file diff --git a/docs/guides/concepts/samples/events.cs b/docs/guides/concepts/samples/events.cs index cf0492cb5..dce625b33 100644 --- a/docs/guides/concepts/samples/events.cs +++ b/docs/guides/concepts/samples/events.cs @@ -14,7 +14,7 @@ public class Program var _config = new DiscordSocketConfig { MessageCacheSize = 100 }; _client = new DiscordSocketClient(_config); - await _client.LoginAsync(TokenType.Bot, "bot token"); + await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken")); await _client.StartAsync(); _client.MessageUpdated += MessageUpdated; diff --git a/docs/guides/concepts/samples/logging.cs b/docs/guides/concepts/samples/logging.cs index a2ddf7b90..2419452f0 100644 --- a/docs/guides/concepts/samples/logging.cs +++ b/docs/guides/concepts/samples/logging.cs @@ -15,7 +15,7 @@ public class Program _client.Log += Log; - await _client.LoginAsync(TokenType.Bot, "bot token"); + await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken")); await _client.StartAsync(); await Task.Delay(-1); diff --git a/docs/guides/getting_started/first-bot.md b/docs/guides/getting_started/first-bot.md new file mode 100644 index 000000000..a5a83aa16 --- /dev/null +++ b/docs/guides/getting_started/first-bot.md @@ -0,0 +1,251 @@ +--- +uid: Guides.GettingStarted.FirstBot +title: Start making a bot +--- + +# Making a Ping-Pong bot + +One of ways to get started with the Discord API is to write a basic +ping-pong bot. This bot will respond to a simple command "ping." +We will expand on this to create more diverse commands later, but for +now, it is a good starting point. + +## Creating a Discord Bot + +Before writing your bot, it is necessary to create a bot account via the +Discord Applications Portal first. + +1. Visit the [Discord Applications Portal]. +2. Create a New Application. +3. Give the application a name (this will be the bot's initial username). +4. Create the Application. + + ![Step 4](images/intro-create-app.png) + +5. In the application review page, click **Create a Bot User**. + + ![Step 5](images/intro-create-bot.png) + +6. Confirm the popup. +7. (Optional) If this bot will be public, check "Public Bot." + * **Do not tick any other options!** + +[Discord Applications Portal]: https://discordapp.com/developers/applications/me + +## Adding your bot to a server + +Bots **cannot** use invite links; they must be explicitly invited +through the OAuth2 flow. + +1. Open your bot's application on the [Discord Applications Portal]. +2. Navigate to `OAuth2 URL Generator` and click on `Generate OAuth2 URL`. + + ![Step 2](images/intro-generate-oauth.png) + +3. Select the permissions that you wish to assign your bot with. + + > [!NOTE] + > This will assign the bot with a special "managed" role that no + > one else can use. The permissions can be changed later in the + > roles settings if you ever change your mind! + +4. Open the generated authorization URL in your browser. +5. Select a server. +6. Click on authorize. + + > [!NOTE] + > Only servers where you have the `MANAGE_SERVER` permission will be + > present in this list. + + ![Step 6](images/intro-add-bot.png) + +## Connecting to Discord + +If you have not already created a project and installed Discord.Net, +do that now. + +For more information, see @Guides.GettingStarted.Installation. + +### Async + +Discord.Net uses .NET's [Task-based Asynchronous Pattern (TAP)] +extensively - nearly every operation is asynchronous. It is highly +recommended that these operations are awaited in a +properly established async context whenever possible. + +To establish an async context, we will be creating an async main method +in your console application, and rewriting the static main method to +invoke the new async main. + +[!code-csharp[Async Context](samples/first-bot/async-context.cs)] + +As a result of this, your program will now start and immediately +jump into an async context. This will allow us to create a connection +to Discord later on without having to worry about setting up the +correct async implementation. + +> [!WARNING] +> If your application throws any exceptions within an async context, +> they will be thrown all the way back up to the first non-async method; +> since our first non-async method is the program's `Main` method, this +> means that **all** unhandled exceptions will be thrown up there, which +> will crash your application. +> +> Discord.Net will prevent exceptions in event handlers from crashing +> your program, but any exceptions in your async main **will** cause +> the application to crash. + +[Task-based Asynchronous Pattern (TAP)]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async + +### Creating a logging method + +Before we create and configure a Discord client, we will add a method +to handle Discord.Net's log events. + +To allow agnostic support of as many log providers as possible, we +log information through a `Log` event with a proprietary `LogMessage` +parameter. See the [API Documentation] for this event. + +If you are using your own logging framework, this is where you would +invoke it. For the sake of simplicity, we will only be logging to +the console. + +You may learn more about this concept in @Guides.Concepts.Logging. + +[!code-csharp[Async Context](samples/first-bot/logging.cs)] + +[API Documentation]: xref:Discord.Rest.BaseDiscordClient.Log + +### Creating a Discord Client + +Finally, we can create a new connection to Discord. + +Since we are writing a bot, we will be using a [DiscordSocketClient] +along with socket entities. See @Guides.GettingStarted.Terminology +if you are unsure of the differences. + +To establish a new connection, we will create an instance of +[DiscordSocketClient] in the new async main. You may pass in an +optional @Discord.WebSocket.DiscordSocketConfig if necessary. For most +users, the default will work fine. + +Before connecting, we should hook the client's `Log` event to the +log handler that we had just created. Events in Discord.Net work +similarly to any other events in C#. + +Next, you will need to "login to Discord" with the [LoginAsync] +method with the application's "token." + +> [!NOTE] +> Pay attention to what you are copying from the developer portal! +> A token is not the same as the application's "client secret." + +![Token](images/intro-token.png) + +> [!IMPORTANT] +> Your bot's token can be used to gain total access to your bot, so +> **do __NOT__ share this token with anyone else!** It may behoove you +> to store this token in an external source if you plan on distributing +> the source code for your bot. + +We may now invoke the client's [StartAsync] method, which will +start connection/reconnection logic. It is important to note that +**this method will return as soon as connection logic has been started!** + +Any methods that rely on the client's state should go in an event +handler. This means that you should **not** directly be interacting with +the client before it is fully ready. + +Finally, we will want to block the async main method from returning +when running the application. To do this, we can await an infinite delay +or any other blocking method, such as reading from the console. + +The following lines can now be added: + +[!code-csharp[Create client](samples/first-bot/client.cs)] + +At this point, feel free to start your program and see your bot come +online in Discord. + +> [!TIP] +> Encountering a `PlatformNotSupportedException` when starting your bot? +> This means that you are targeting a platform where .NET's default +> WebSocket client is not supported. Refer to the [installation guide] +> for how to fix this. + +[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient +[LoginAsync]: xref:Discord.Rest.BaseDiscordClient.LoginAsync* +[StartAsync]: xref:Discord.WebSocket.DiscordSocketClient.StartAsync* +[installation guide]: xref:Guides.GettingStarted.Installation#installing-on-net-standard-11 + +### Handling a 'ping' + +> [!WARNING] +> Please note that this is *not* a proper way to create a command. +> Use the `CommandService` provided by the library instead, as explained +> in the [Command Guide](xref:Guides.Commands.Intro) section. + +Now that we have learned to open a connection to Discord, we can +begin handling messages that the users are sending. To start out, our +bot will listen for any message whose content is equal to `!ping` and +will respond back with "Pong!". + +Since we want to listen for new messages, the event to hook into +is [MessageReceived]. + +In your program, add a method that matches the signature of the +`MessageReceived` event - it must be a method (`Func`) that returns +the type `Task` and takes a single parameter, a [SocketMessage]. Also, +since we will be sending data to Discord in this method, we will flag +it as `async`. + +In this method, we will add an `if` block to determine if the message +content fits the rules of our scenario - recall that it must be equal +to `!ping`. + +Inside the branch of this condition, we will want to send a message, +`Pong!`, back to the channel from which the message comes from. To +find the channel, look for the `Channel` property on the message +parameter. + +Next, we will want to send a message to this channel. Since the +channel object is of type [ISocketMessageChannel], we can invoke the +[SendMessageAsync] instance method. For the message content, send back +a string, "Pong!". + +You should have now added the following lines, + +[!code-csharp[Message](samples/first-bot/message.cs)] + +Now that your first bot is complete. You may continue to add on to this +if you desire, but for any bots that will be carrying out multiple +commands, it is strongly recommended to use the command framework as +shown below. + +> [!NOTE] +> For your reference, you may view the [completed program]. + +[MessageReceived]: xref:Discord.WebSocket.BaseSocketClient.MessageReceived +[SocketMessage]: xref:Discord.WebSocket.SocketMessage +[ISocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel +[SendMessageAsync]: xref:Discord.WebSocket.ISocketMessageChannel.SendMessageAsync* +[completed program]: samples/first-bot/complete.cs + +# Building a bot with commands + +@Guides.Commands.Intro will guide you through how to setup a program +that is ready for [CommandService], a service that is ready for +advanced command usage. + +For reference, view an [annotated example] of this structure. + +[annotated example]: samples/first-bot/structure.cs + +It is important to know that the recommended design pattern of bots +should be to separate... + +1. the program (initialization and command handler) +2. the modules (handle commands) +3. the services (persistent storage, pure functions, data manipulation) + +[CommandService]: xref:Discord.Commands.CommandService \ No newline at end of file diff --git a/docs/guides/getting_started/images/install-vs-nuget.png b/docs/guides/getting_started/images/install-vs-nuget.png index 64da79a9f..ecf627d11 100644 Binary files a/docs/guides/getting_started/images/install-vs-nuget.png and b/docs/guides/getting_started/images/install-vs-nuget.png differ diff --git a/docs/guides/getting_started/images/intro-client-id.png b/docs/guides/getting_started/images/intro-client-id.png deleted file mode 100644 index e370aa2ec..000000000 Binary files a/docs/guides/getting_started/images/intro-client-id.png and /dev/null differ diff --git a/docs/guides/getting_started/images/intro-copy-oauth.png b/docs/guides/getting_started/images/intro-copy-oauth.png new file mode 100644 index 000000000..31dc6f743 Binary files /dev/null and b/docs/guides/getting_started/images/intro-copy-oauth.png differ diff --git a/docs/guides/getting_started/images/intro-create-app.png b/docs/guides/getting_started/images/intro-create-app.png index 7aceb84b4..94f229a73 100644 Binary files a/docs/guides/getting_started/images/intro-create-app.png and b/docs/guides/getting_started/images/intro-create-app.png differ diff --git a/docs/guides/getting_started/images/intro-create-bot.png b/docs/guides/getting_started/images/intro-create-bot.png index 0522358cf..cc4e55ec3 100644 Binary files a/docs/guides/getting_started/images/intro-create-bot.png and b/docs/guides/getting_started/images/intro-create-bot.png differ diff --git a/docs/guides/getting_started/images/intro-generate-oauth.png b/docs/guides/getting_started/images/intro-generate-oauth.png new file mode 100644 index 000000000..5d7100ad2 Binary files /dev/null and b/docs/guides/getting_started/images/intro-generate-oauth.png differ diff --git a/docs/guides/getting_started/installing.md b/docs/guides/getting_started/installing.md index 5d4c85d81..b174a3829 100644 --- a/docs/guides/getting_started/installing.md +++ b/docs/guides/getting_started/installing.md @@ -1,38 +1,48 @@ --- +uid: Guides.GettingStarted.Installation title: Installing Discord.Net --- -Discord.Net is distributed through the NuGet package manager, and it -is recommended to use NuGet to get started. +# Discord.Net Installation -Optionally, you may compile from source and install yourself. +Discord.Net is distributed through the NuGet package manager, so it is +recommended for you to install the library that way. -# Supported Platforms +Alternatively, you may compile from the source and install the library +yourself. + +## Supported Platforms Currently, Discord.Net targets [.NET Standard] 1.3 and offers support for .NET Standard 1.1. If your application will be targeting .NET Standard 1.1, please see the [additional steps]. -Since Discord.Net is built on the .NET Standard, it is also -recommended to create applications using [.NET Core], though not +Since Discord.Net is built on top of .NET Standard, it is also +recommended to create applications using [.NET Core], although it is not required. When using .NET Framework, it is suggested to target `.NET Framework 4.6.1` or higher. +> [!WARNING] +> Using this library with [Mono] is not recommended until further +> notice. It is known to have issues with the library's WebSockets +> implementation and may crash the application upon startup. + +[Mono]: https://www.mono-project.com/ [.NET Standard]: https://docs.microsoft.com/en-us/dotnet/articles/standard/library [.NET Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/ [additional steps]: #installing-on-net-standard-11 -# Installing with NuGet +## Installing with NuGet -Release builds of Discord.Net 1.0 will be published to the +Release builds of Discord.Net will be published to the [official NuGet feed]. -Development builds of Discord.Net 1.0, as well as addons *(TODO)* are -published to our development [MyGet feed]. +Development builds of Discord.Net, as well as add-ons, will be +published to our [MyGet feed]. -Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` +* Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` -Not sure how to add a direct feed? See how [with Visual Studio] or +Not sure how to add a direct feed? See how [with Visual Studio] or [without Visual Studio]. [official NuGet feed]: https://nuget.org @@ -40,85 +50,101 @@ Not sure how to add a direct feed? See how [with Visual Studio] or [with Visual Studio]: https://docs.microsoft.com/en-us/nuget/tools/package-manager-ui#package-sources [without Visual Studio]: #configuring-nuget-without-visual-studio -## Using Visual Studio +### [Using Visual Studio](#tab/vs-install) > [!TIP] ->Don't forget to change your package source if you're installing from -the developer feed. ->Also make sure to check "Enable Prereleases" if installing a dev -build! - -1. Create a solution for your bot. -2. In Solution Explorer, find the "Dependencies" element under your -bot's project. +> Don't forget to change your package source if you're installing from +> the developer feed. +> Also make sure to check "Enable Prereleases" if installing a dev +> build! + +1. Create a new solution for your bot. +2. In the Solution Explorer, find the "Dependencies" element under your + bot's project. 3. Right click on "Dependencies", and select "Manage NuGet packages." -![Step 3](images/install-vs-deps.png) + ![Step 3](images/install-vs-deps.png) 4. In the "Browse" tab, search for `Discord.Net`. 5. Install the `Discord.Net` package. -![Step 5](images/install-vs-nuget.png) + ![Step 5](images/install-vs-nuget.png) -## Using JetBrains Rider +### [Using JetBrains Rider](#tab/rider-install) > [!TIP] -Make sure to check the "Prerelease" box if installing a dev build! +> Make sure to check the "Prerelease" box if installing a dev build! 1. Create a new solution for your bot. 2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for -Solution). + Solution). ![Step 2](images/install-rider-nuget-manager.png) 3. In the "Packages" tab, search for `Discord.Net`. ![Step 3](images/install-rider-search.png) 4. Install by adding the package to your project. ![Step 4](images/install-rider-add.png) -## Using Visual Studio Code +### [Using Visual Studio Code](#tab/vs-code) > [!TIP] -Don't forget to add the package source to a [NuGet.Config file] if -you're installing from the developer feed. +> Don't forget to add the package source to a [NuGet.Config file] if +> you're installing from the developer feed. 1. Create a new project for your bot. 2. Add `Discord.Net` to your .csproj. -[!code-xml[Sample .csproj](samples/project.csproj)] +[!code[Sample .csproj](samples/project.xml)] + +[NuGet.Config file]: #configuring-nuget-without-visual-studio + +### [Using dotnet CLI](#tab/dotnet-cli) + +> [!TIP] +> Don't forget to add the package source to a [NuGet.Config file] if +> you're installing from the developer feed. + +1. Open command-line and navigate to where your .csproj is located. +2. Enter `dotnet add package Discord.Net`. [NuGet.Config file]: #configuring-nuget-without-visual-studio -# Compiling from Source +*** + +## Compiling from Source -In order to compile Discord.Net, you require the following: +In order to compile Discord.Net, you will need the following: ### Using Visual Studio - [Visual Studio 2017](https://www.visualstudio.com/) -- [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk) +- [.NET Core SDK] The .NET Core and Docker (Preview) workload is required during Visual Studio installation. ### Using Command Line -- [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk) +- [.NET Core SDK] -# Additional Information +[.NET Core SDK]: https://www.microsoft.com/net/download/ -## Installing on .NET Standard 1.1 +## Additional Information + +### Installing on .NET Standard 1.1 For applications targeting a runtime corresponding with .NET Standard -1.1 or 1.2, the builtin WebSocket and UDP provider will not work. For -applications which utilize a WebSocket connection to Discord -(WebSocket or RPC), third-party provider packages will need to be +1.1 or 1.2, the built-in WebSocket and UDP provider will not work. For +applications which utilize a WebSocket connection to Discord, such as +WebSocket or RPC, third-party provider packages will need to be installed and configured. -First, install the following packages through NuGet, or compile +> [!NOTE] +> `Discord.Net.Providers.UDPClient` is _only_ required if your +> bot will be utilizing voice chat. + +First, install the following packages through NuGet, or compile them yourself, if you prefer: - Discord.Net.Providers.WS4Net - Discord.Net.Providers.UDPClient -Note that `Discord.Net.Providers.UDPClient` is _only_ required if your -bot will be utilizing voice chat. - Next, you will need to configure your [DiscordSocketClient] to use these custom providers over the default ones. @@ -131,16 +157,16 @@ are passing into your client. [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient [DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig -## Configuring NuGet without Visual Studio +### Configuring NuGet without Visual Studio If you plan on deploying your bot or developing outside of Visual Studio, you will need to create a local NuGet configuration file for your project. -To do this, create a file named `nuget.config` alongside the root of -your application, where the project solution is located. +To do this, create a file named `NuGet.Config` alongside the root of +your application, where the project is located. Paste the following snippets into this configuration file, adding any -additional feeds as necessary. +additional feeds if necessary. -[!code-xml[NuGet Configuration](samples/nuget.config)] +[!code[NuGet Configuration](samples/nuget.config)] diff --git a/docs/guides/getting_started/intro.md b/docs/guides/getting_started/intro.md deleted file mode 100644 index db086df21..000000000 --- a/docs/guides/getting_started/intro.md +++ /dev/null @@ -1,237 +0,0 @@ ---- -title: Getting Started ---- - -# Making a Ping-Pong bot - -One of the first steps to getting started with the Discord API is to -write a basic ping-pong bot. We will expand on this to create more -diverse commands later, but for now, it is a good starting point. - -## Creating a Discord Bot - -Before you can begin writing your bot, it is necessary to create a bot -account on Discord. - -1. Visit the [Discord Applications Portal]. -2. Create a New Application. -3. Give the application a name (this will be the bot's initial -username). -4. Create the Application. - - ![Step 4](images/intro-create-app.png) - -5. In the application review page, click **Create a Bot User**. - - ![Step 5](images/intro-create-bot.png) - -6. Confirm the popup. -7. If this bot will be public, check "Public Bot." **Do not tick any -other options!** - -[Discord Applications Portal]: https://discordapp.com/developers/applications/me - -## Adding your bot to a server - -Bots **cannot** use invite links, they must be explicitly invited -through the OAuth2 flow. - -1. Open your bot's application on the [Discord Applications Portal]. -2. Retrieve the app's **Client ID**. - - ![Step 2](images/intro-client-id.png) - -3. Create an OAuth2 authorization URL -`https://discordapp.com/oauth2/authorize?client_id=&scope=bot` -4. Open the authorization URL in your browser. -5. Select a server. -6. Click on authorize. - - >[!NOTE] - Only servers where you have the `MANAGE_SERVER` permission will be - present in this list. - - ![Step 6](images/intro-add-bot.png) - - -## Connecting to Discord - -If you have not already created a project and installed Discord.Net, -do that now. (see the [Installing](installing.md) section) - -### Async - -Discord.Net uses .NET's [Task-based Asynchronous Pattern (TAP)] -extensively - nearly every operation is asynchronous. - -It is highly recommended that these operations are awaited in a -properly established async context whenever possible. Establishing an -async context can be problematic, but not hard. - -To do so, we will be creating an async main in your console -application, and rewriting the static main method to invoke the new -async main. - -[!code-csharp[Async Context](samples/intro/async-context.cs)] - -As a result of this, your program will now start and immediately -jump into an async context. This will allow us to create a connection -to Discord later on without needing to worry about setting up the -correct async implementation. - ->[!TIP] -If your application throws any exceptions within an async context, -they will be thrown all the way back up to the first non-async method; -since our first non-async method is the program's `Main` method, this -means that **all** unhandled exceptions will be thrown up there, which -will crash your application. Discord.Net will prevent exceptions in -event handlers from crashing your program, but any exceptions in your -async main **will** cause the application to crash. - -[Task-based Asynchronous Pattern (TAP)]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async - -### Creating a logging method - -Before we create and configure a Discord client, we will add a method -to handle Discord.Net's log events. - -To allow agnostic support of as many log providers as possible, we -log information through a `Log` event with a proprietary `LogMessage` -parameter. See the [API Documentation] for this event. - -If you are using your own logging framework, this is where you would -invoke it. For the sake of simplicity, we will only be logging to -the Console. - -[!code-csharp[Async Context](samples/intro/logging.cs)] - -[API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log - -### Creating a Discord Client - -Finally, we can create a connection to Discord. Since we are writing -a bot, we will be using a [DiscordSocketClient] along with socket -entities. See the [terminology](terminology.md) if you're unsure of -the differences. - -To do so, create an instance of [DiscordSocketClient] in your async -main, passing in a configuration object only if necessary. For most -users, the default will work fine. - -Before connecting, we should hook the client's `Log` event to the -log handler that was just created. Events in Discord.Net work -similarly to other events in C#, so hook this event the way that -you typically would. - -Next, you will need to "login to Discord" with the `LoginAsync` -method. - -You may create a variable to hold your bot's token (this can be found -on your bot's application page on the [Discord Applications Portal]). - -![Token](images/intro-token.png) - ->[!IMPORTANT] -Your bot's token can be used to gain total access to your bot, so -**do __NOT__ share this token with anyone else!** It may behoove you -to store this token in an external file if you plan on distributing -the source code for your bot. - -We may now invoke the client's `StartAsync` method, which will -start connection/reconnection logic. It is important to note that -**this method returns as soon as connection logic has been started!** - -Any methods that rely on the client's state should go in an event -handler. - -Finally, we will want to block the async main method from returning -until after the application is exited. To do this, we can await an -infinite delay or any other blocking method, such as reading from -the console. - -The following lines can now be added: - -[!code-csharp[Create client](samples/intro/client.cs)] - -At this point, feel free to start your program and see your bot come -online in Discord. - ->[!TIP] -Encountering a `PlatformNotSupportedException` when starting your bot? -This means that you are targeting a platform where .NET's default -WebSocket client is not supported. Refer to the [installation guide] -for how to fix this. - -[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient -[installation guide]: installing.md#installing-on-net-standard-11 - -### Handling a 'ping' - ->[!WARNING] -Please note that this is *not* a proper way to create a command. -Use the `CommandService` provided by the library instead, as explained -in the [Command Guide] section. - -Now that we have learned how to open a connection to Discord, we can -begin handling messages that users are sending. - -To start out, our bot will listen for any message where the content -is equal to `!ping` and respond back with "Pong!". - -Since we want to listen for new messages, the event to hook into -is [MessageReceived]. - -In your program, add a method that matches the signature of the -`MessageReceived` event - it must be a method (`Func`) that returns -the type `Task` and takes a single parameter, a [SocketMessage]. Also, -since we will be sending data to Discord in this method, we will flag -it as `async`. - -In this method, we will add an `if` block to determine if the message -content fits the rules of our scenario - recall that it must be equal -to `!ping`. - -Inside the branch of this condition, we will want to send a message -back to the channel from which the message comes from - "Pong!". To -find the channel, look for the `Channel` property on the message -parameter. - -Next, we will want to send a message to this channel. Since the -channel object is of type [SocketMessageChannel], we can invoke the -`SendMessageAsync` instance method. For the message content, send back -a string containing "Pong!". - -You should have now added the following lines: - -[!code-csharp[Message](samples/intro/message.cs)] - -Now your first bot is complete. You may continue to add on to this -if you desire, but for any bots that will be carrying out multiple -commands, it is strongly recommended to use the command framework as -shown below. - -For your reference, you may view the [completed program]. - -[MessageReceived]: xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_MessageReceived -[SocketMessage]: xref:Discord.WebSocket.SocketMessage -[SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel -[completed program]: samples/intro/complete.cs -[Command Guide]: ../commands/commands.md - -# Building a bot with commands - -This section will show you how to write a program that is ready for -[Commands](../commands/commands.md). Note that we will not be -explaining _how_ to write Commands or Services, it will only be -covering the general structure. - -For reference, view an [annotated example] of this structure. - -[annotated example]: samples/intro/structure.cs - -It is important to know that the recommended design pattern of bots -should be to separate the program (initialization and command handler), -the modules (handle commands), and the services (persistent storage, -pure functions, data manipulation). - -**todo:** diagram of bot structure \ No newline at end of file diff --git a/docs/guides/getting_started/samples/first-bot/async-context.cs b/docs/guides/getting_started/samples/first-bot/async-context.cs new file mode 100644 index 000000000..3c98c9e46 --- /dev/null +++ b/docs/guides/getting_started/samples/first-bot/async-context.cs @@ -0,0 +1,9 @@ +public class Program +{ + public static void Main(string[] args) + => new Program().MainAsync().GetAwaiter().GetResult(); + + public async Task MainAsync() + { + } +} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/intro/client.cs b/docs/guides/getting_started/samples/first-bot/client.cs similarity index 57% rename from docs/guides/getting_started/samples/intro/client.cs rename to docs/guides/getting_started/samples/first-bot/client.cs index a73082052..6ff75b8a3 100644 --- a/docs/guides/getting_started/samples/intro/client.cs +++ b/docs/guides/getting_started/samples/first-bot/client.cs @@ -1,17 +1,16 @@ -// Program.cs -using Discord.WebSocket; -// ... private DiscordSocketClient _client; + public async Task MainAsync() { _client = new DiscordSocketClient(); _client.Log += Log; - string token = "abcdefg..."; // Remember to keep this private! - await _client.LoginAsync(TokenType.Bot, token); + // Remember to keep token private or to read it + // from an external source! + await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken")); await _client.StartAsync(); // Block this task until the program is closed. await Task.Delay(-1); -} +} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/first-bot/complete.cs b/docs/guides/getting_started/samples/first-bot/complete.cs new file mode 100644 index 000000000..8c3903d41 --- /dev/null +++ b/docs/guides/getting_started/samples/first-bot/complete.cs @@ -0,0 +1,37 @@ +public class Program +{ + private DiscordSocketClient _client; + + public static void Main(string[] args) + => new Program().MainAsync().GetAwaiter().GetResult(); + + public async Task MainAsync() + { + _client = new DiscordSocketClient(); + + _client.Log += Log; + _client.MessageReceived += MessageReceivedAsync; + + // Remember to keep this private or to read this + // from an external source! + await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken")); + await _client.StartAsync(); + + // Block this task until the program is closed. + await Task.Delay(-1); + } + + private async Task MessageReceivedAsync(SocketMessage message) + { + if (message.Content == "!ping") + { + await message.Channel.SendMessageAsync("Pong!"); + } + } + + private Task Log(LogMessage msg) + { + Console.WriteLine(msg.ToString()); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/first-bot/logging.cs b/docs/guides/getting_started/samples/first-bot/logging.cs new file mode 100644 index 000000000..c6ffc406e --- /dev/null +++ b/docs/guides/getting_started/samples/first-bot/logging.cs @@ -0,0 +1,5 @@ +private Task Log(LogMessage msg) +{ + Console.WriteLine(msg.ToString()); + return Task.CompletedTask; +} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/intro/message.cs b/docs/guides/getting_started/samples/first-bot/message.cs similarity index 92% rename from docs/guides/getting_started/samples/intro/message.cs rename to docs/guides/getting_started/samples/first-bot/message.cs index d6fd90778..f636d6f35 100644 --- a/docs/guides/getting_started/samples/intro/message.cs +++ b/docs/guides/getting_started/samples/first-bot/message.cs @@ -1,6 +1,6 @@ public async Task MainAsync() { - // client.Log ... + // ... _client.MessageReceived += MessageReceived; // ... } diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/first-bot/structure.cs similarity index 99% rename from docs/guides/getting_started/samples/intro/structure.cs rename to docs/guides/getting_started/samples/first-bot/structure.cs index a9a018c3a..9ec1043c9 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/first-bot/structure.cs @@ -36,7 +36,7 @@ class Program // you must set the MessageCacheSize. You may adjust the number as needed. //MessageCacheSize = 50, - // If your platform doesn't have native websockets, + // If your platform doesn't have native WebSockets, // add Discord.Net.Providers.WS4Net from NuGet, // add the `using` at the top, and uncomment this line: //WebSocketProvider = WS4NetProvider.Instance diff --git a/docs/guides/getting_started/samples/intro/async-context.cs b/docs/guides/getting_started/samples/intro/async-context.cs deleted file mode 100644 index c01ddec55..000000000 --- a/docs/guides/getting_started/samples/intro/async-context.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace MyBot -{ - public class Program - { - public static void Main(string[] args) - => new Program().MainAsync().GetAwaiter().GetResult(); - - public async Task MainAsync() - { - } - } -} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/intro/complete.cs b/docs/guides/getting_started/samples/intro/complete.cs deleted file mode 100644 index 23b59ce6f..000000000 --- a/docs/guides/getting_started/samples/intro/complete.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Discord; -using Discord.WebSocket; -using System; -using System.Threading.Tasks; - -namespace MyBot -{ - public class Program - { - private DiscordSocketClient _client; - - public static void Main(string[] args) - => new Program().MainAsync().GetAwaiter().GetResult(); - - public async Task MainAsync() - { - _client = new DiscordSocketClient(); - - _client.Log += Log; - _client.MessageReceived += MessageReceived; - - string token = "abcdefg..."; // Remember to keep this private! - await _client.LoginAsync(TokenType.Bot, token); - await _client.StartAsync(); - - // Block this task until the program is closed. - await Task.Delay(-1); - } - - private async Task MessageReceived(SocketMessage message) - { - if (message.Content == "!ping") - { - await message.Channel.SendMessageAsync("Pong!"); - } - } - - private Task Log(LogMessage msg) - { - Console.WriteLine(msg.ToString()); - return Task.CompletedTask; - } - } -} diff --git a/docs/guides/getting_started/samples/intro/logging.cs b/docs/guides/getting_started/samples/intro/logging.cs deleted file mode 100644 index 4fb85a063..000000000 --- a/docs/guides/getting_started/samples/intro/logging.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Discord; -using System; -using System.Threading.Tasks; - -namespace MyBot -{ - public class Program - { - public static void Main(string[] args) - => new Program().MainAsync().GetAwaiter().GetResult(); - - public async Task MainAsync() - { - } - - private Task Log(LogMessage msg) - { - Console.WriteLine(msg.ToString()); - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/project.csproj b/docs/guides/getting_started/samples/project.csproj deleted file mode 100644 index feb0b0c40..000000000 --- a/docs/guides/getting_started/samples/project.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - Exe - netcoreapp1.1 - true - - - - - - - diff --git a/docs/guides/getting_started/samples/project.xml b/docs/guides/getting_started/samples/project.xml new file mode 100644 index 000000000..983ac228b --- /dev/null +++ b/docs/guides/getting_started/samples/project.xml @@ -0,0 +1,16 @@ + + + + + Exe + netcoreapp2.0 + + + + + + + diff --git a/docs/guides/getting_started/terminology.md b/docs/guides/getting_started/terminology.md index 74f7a6259..61a226dcf 100644 --- a/docs/guides/getting_started/terminology.md +++ b/docs/guides/getting_started/terminology.md @@ -1,5 +1,5 @@ --- -uid: Terminology +uid: Guides.GettingStarted.Terminology title: Terminology --- @@ -7,34 +7,34 @@ title: Terminology ## Preface -Most terms for objects remain the same between 0.9 and 1.0. The major -difference is that the ``Server`` is now called ``Guild`` to stay in -line with Discord internally. +Most terms for objects remain the same between 0.9 and 1.0 and above. +The major difference is that the ``Server`` is now called ``Guild`` +to stay in line with Discord internally. ## Implementation Specific Entities -Discord.Net 1.0 is split into a core library and three different -implementations - `Discord.Net.Core`, `Discord.Net.Rest`, +Discord.Net is split into a core library and three different +implementations - `Discord.Net.Core`, `Discord.Net.Rest`, `Discord.Net.Rpc`, and `Discord.Net.WebSockets`. -As a bot developer, you will only need to use `Discord.Net.WebSockets`, +As a bot developer, you will only need to use `Discord.Net.WebSockets`, but you should be aware of the differences between them. -`Discord.Net.Core` provides a set of interfaces that models Discord's -API. These interfaces are consistent throughout all implementations of -Discord.Net, and if you are writing an implementation-agnostic library -or addon, you can rely on the core interfaces to ensure that your +`Discord.Net.Core` provides a set of interfaces that models Discord's +API. These interfaces are consistent throughout all implementations of +Discord.Net, and if you are writing an implementation-agnostic library +or addon, you can rely on the core interfaces to ensure that your addon will run on all platforms. -`Discord.Net.Rest` provides a set of concrete classes to be used -**strictly** with the REST portion of Discord's API. Entities in this -implementation are prefixed with `Rest` (e.g. `RestChannel`). +`Discord.Net.Rest` provides a set of concrete classes to be used +**strictly** with the REST portion of Discord's API. Entities in this +implementation are prefixed with `Rest` (e.g., `RestChannel`). -`Discord.Net.Rpc` provides a set of concrete classes that are used -with Discord's RPC API. Entities in this implementation are prefixed -with `Rpc` (e.g. `RpcChannel`). +`Discord.Net.Rpc` provides a set of concrete classes that are used +with Discord's RPC API. Entities in this implementation are prefixed +with `Rpc` (e.g., `RpcChannel`). -`Discord.Net.WebSocket` provides a set of concrete classes that are +`Discord.Net.WebSocket` provides a set of concrete classes that are used primarily with Discord's WebSocket API or entities that are kept -in cache. When developing bots, you will be using this implementation. -All entities are prefixed with `Socket` (e.g. `SocketChannel`). \ No newline at end of file +in cache. When developing bots, you will be using this implementation. +All entities are prefixed with `Socket` (e.g., `SocketChannel`). \ No newline at end of file diff --git a/docs/guides/introduction/intro.md b/docs/guides/introduction/intro.md new file mode 100644 index 000000000..c22edd1f7 --- /dev/null +++ b/docs/guides/introduction/intro.md @@ -0,0 +1,51 @@ +--- +uid: Guides.Introduction +title: Introduction to Discord.Net +--- + +# Introduction + +## Looking to get started? + +First of all, welcome! You may visit us on our Discord should you +have any questions. Before you delve into using the library, +however, you should have some decent understanding of the language +you are about to use. This library touches on +[Task-based Asynchronous Pattern] \(TAP), [polymorphism], [interface] +and many more advanced topics extensively. Please make sure that you +understand these topics to some extent before proceeding. + +Here are some examples: + +1. [Official samples] +2. [Official template] + +> [!NOTE] +> Please note that you should *not* try to blindly copy paste +> the code. The examples are meant to be a template or a guide. +> It is not meant to be something that will work out of the box. + +[Official template]: https://github.com/foxbot/DiscordBotBase/tree/csharp/src/DiscordBot +[Official samples]: https://github.com/RogueException/Discord.Net/tree/dev/samples +[Task-based Asynchronous Pattern]: https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap +[polymorphism]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/polymorphism +[interface]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/ + +## New to .NET/C#? + +If you are new to the language, using this lib may prove to be +difficult, but don't worry! There are many resources online that can +help you get started in the wonderful world of .NET. Here are some +resources to get you started. + +- [C# Programming Guide (MSDN/Microsoft, Free)](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/) +- [C# Fundamentals For Absolute Beginners (Channel9/Microsoft, Free)](https://channel9.msdn.com/Series/C-Fundamentals-for-Absolute-Beginners) +- [C# Path (Pluralsight, Paid)](https://www.pluralsight.com/paths/csharp) + +## Still have questions? + +Please visit us at `#dotnet_discord-net` on the [Discord API] server. +Describe the problem in details to us, what you've done, and, +if any, the problematic code uploaded onto [Hastebin](https://hastebin.com). + +[Discord API]: https://discord.gg/jkrBmQR \ No newline at end of file diff --git a/docs/guides/migrating/migrating.md b/docs/guides/migrating/migrating.md deleted file mode 100644 index bc628a5f8..000000000 --- a/docs/guides/migrating/migrating.md +++ /dev/null @@ -1,61 +0,0 @@ -# Migrating from 0.9 - -**1.0.0 is the biggest breaking change the library has gone through, due to massive -changes in the design of the library.** - ->A medium to advanced understanding is recommended when working with this library. - -It is recommended to familiarize yourself with the entities in 1.0 before continuing. -Feel free to look through the library's source directly, look through IntelliSense, or -look through our hosted [API Documentation](xref:Discord). - -## Entities - -Most API models function _similarly_ to 0.9, however their names have been changed. -You should also keep in mind that we now separate different types of Channels and Users. - -Before proceeding, please read over @Terminology to understand the naming behind some objects. - -Below is a table that compares most common 0.9 entities to their 1.0 counterparts. - ->This should be used mostly for migration purposes. Please take some time to consider whether ->or not you are using the right "tool for the job" when working with 1.0 - -| 0.9 | 1.0 | Notice | -| --- | --- | ------ | -| Server | @Discord.WebSocket.SocketGuild | -| Channel | @Discord.WebSocket.SocketGuildChannel | Applies only to channels that are members of a Guild | -| Channel.IsPrivate | @Discord.WebSocket.SocketDMChannel -| ChannelType.Text | @Discord.WebSocket.SocketTextChannel | This applies only to Text Channels in Guilds -| ChannelType.Voice | @Discord.WebSocket.SocketVoiceChannel | This applies only to Voice Channels in Guilds -| User | @Discord.WebSocket.SocketGuildUser | This applies only to users belonging to a Guild* -| Profile | @Discord.WebSocket.SocketGuildUser -| Message | @Discord.WebSocket.SocketUserMessage - -\* To retrieve an @Discord.WebSocket.SocketGuildUser, you must retrieve the user from an @Discord.WebSocket.SocketGuild. - -## Event Registration - -Prior to 1.0, events were registered using the standard c# `Handler(EventArgs)` pattern. In 1.0, -events are delegates, but are still registered the same. - -For example, let's look at [DiscordSocketClient.MessageReceived](xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_MessageReceived) - -To hook an event into MessageReceived, we now use the following code: -[!code-csharp[Event Registration](samples/event.cs)] - -> **All Event Handlers in 1.0 MUST return Task!** - -If your event handler is marked as `async`, it will automatically return `Task`. However, -if you do not need to execute asynchronus code, do _not_ mark your handler as `async`, and instead, -stick a `return Task.CompletedTask` at the bottom. - -[!code-csharp[Sync Event Registration](samples/sync_event.cs)] - -**Event handlers no longer require a sender.** The only arguments your event handler needs to accept -are the parameters used by the event. It is recommended to look at the event in IntelliSense or on the -API docs before implementing it. - -## Async - -Nearly everything in 1.0 is an async Task. You should always await any tasks you invoke. diff --git a/docs/guides/migrating/samples/event.cs b/docs/guides/migrating/samples/event.cs deleted file mode 100644 index 8719942f2..000000000 --- a/docs/guides/migrating/samples/event.cs +++ /dev/null @@ -1,4 +0,0 @@ -_client.MessageReceived += async (msg) => -{ - await msg.Channel.SendMessageAsync(msg.Content); -} \ No newline at end of file diff --git a/docs/guides/migrating/samples/sync_event.cs b/docs/guides/migrating/samples/sync_event.cs deleted file mode 100644 index f4a55cdd3..000000000 --- a/docs/guides/migrating/samples/sync_event.cs +++ /dev/null @@ -1,5 +0,0 @@ -_client.Log += (msg) => -{ - Console.WriteLine(msg.ToString()); - return Task.CompletedTask; -} \ No newline at end of file diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml index 2e3a61e19..639fe9d88 100644 --- a/docs/guides/toc.yml +++ b/docs/guides/toc.yml @@ -1,27 +1,36 @@ +- name: Introduction + topicUid: Guides.Introduction - name: Getting Started items: - name: Installation - href: getting_started/installing.md + topicUid: Guides.GettingStarted.Installation - name: Your First Bot - href: getting_started/intro.md + topicUid: Guides.GettingStarted.FirstBot - name: Terminology - href: getting_started/terminology.md + topicUid: Guides.GettingStarted.Terminology - name: Basic Concepts items: - name: Logging Data - href: concepts/logging.md + topicUid: Guides.Concepts.Logging - name: Working with Events - href: concepts/events.md + topicUid: Guides.Concepts.Events - name: Managing Connections - href: concepts/connections.md + topicUid: Guides.Concepts.ManageConnections - name: Entities - href: concepts/entities.md + topicUid: Guides.Concepts.Entities + - name: Deployment + topicUid: Guides.Concepts.Deployment - name: The Command Service items: - - name: Command Guide - href: commands/commands.md + - name: Introduction + topicUid: Guides.Commands.Intro + - name: TypeReaders + topicUid: Guides.Commands.TypeReaders + - name: Preconditions + topicUid: Guides.Commands.Preconditions + - name: Dependency Injection + topicUid: Guides.Commands.DI + - name: Post-execution Handling + topicUid: Guides.Commands.PostExecution - name: Voice - items: - - name: Voice Guide - href: voice/sending-voice.md -- name: Migrating from 0.9 \ No newline at end of file + topicUid: Guides.Voice.SendingVoice diff --git a/docs/guides/voice/sending-voice.md b/docs/guides/voice/sending-voice.md index 024a98b95..c30805836 100644 --- a/docs/guides/voice/sending-voice.md +++ b/docs/guides/voice/sending-voice.md @@ -1,4 +1,5 @@ --- +uid: Guides.Voice.SendingVoice title: Sending Voice --- @@ -44,7 +45,7 @@ guild. To switch channels within a guild, invoke [ConnectAsync] on another voice channel in the guild. [IAudioClient]: xref:Discord.Audio.IAudioClient -[ConnectAsync]: xref:Discord.IAudioChannel#Discord_IAudioChannel_ConnectAsync_Action_IAudioClient__ +[ConnectAsync]: xref:Discord.IAudioChannel.ConnectAsync* ## Transmitting Audio @@ -98,4 +99,4 @@ you will want to wait for audio to stop playing before continuing on to the next song. You can await `AudioOutStream.FlushAsync` to wait for the audio client's internal buffer to clear out. -[!code-csharp[Sending Audio](samples/audio_ffmpeg.cs)] \ No newline at end of file +[!code-csharp[Sending Audio](samples/audio_ffmpeg.cs)] diff --git a/docs/index.md b/docs/index.md index ef9ecdfdd..8622bc002 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,13 +1,28 @@ +--- +uid: Root.Landing +title: Home +--- # Discord.Net Documentation -Discord.Net is an asynchronous, multiplatform .NET Library used to interface with the [Discord API](https://discordapp.com/). +## What is Discord.Net? -If this is your first time using Discord.Net, you should refer to the [Intro](guides/getting_started/intro.md) for tutorials. -More experienced users might refer to the [API Documentation](api/index.md) for a breakdown of the individuals objects in the library. +Discord.Net is an asynchronous, multi-platform .NET Library used to +interface with the [Discord API](https://discordapp.com/). -For additional resources: - - [Discord API Guild](https://discord.gg/discord-api) - Look for `#dotnet_discord-net` - - [GitHub](https://github.com/RogueException/Discord.Net/tree/dev) - - [NuGet](https://www.nuget.org/packages/Discord.Net/) - - [MyGet Feed](https://www.myget.org/feed/Packages/discord-net) - Addons and nightly builds \ No newline at end of file +## Where to begin? + +If this is your first time using Discord.Net, you should refer to the +[Intro](xref:Guides.Introduction) for tutorials. + +More experienced users might want to refer to the +[API Documentation](xref:API.Docs) for a breakdown of the individual +objects in the library. + +## Additional Resources + +- [Discord API Guild](https://discord.gg/discord-api) - Look for `#dotnet_discord-net` +- [GitHub](https://github.com/RogueException/Discord.Net/) +- [NuGet](https://www.nuget.org/packages/Discord.Net/) +- [MyGet Feed](https://www.myget.org/feed/Packages/discord-net) - Add-ons and nightly builds +- [AppVeyor CI](https://ci.appveyor.com/project/RogueException/discord-net) - Nightly builds via Continuous Integration \ No newline at end of file diff --git a/docs/toc.yml b/docs/toc.yml index c08e708bf..337294b1a 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -1,6 +1,9 @@ - - name: Guides href: guides/ + topicUid: Guides.Introduction +- name: FAQ + href: faq/ + topicUid: FAQ.Basics.GetStarted - name: API Documentation href: api/ - homepage: api/index.md + topicUid: API.Docs \ No newline at end of file diff --git a/experiment/Discord.Net.Rpc/DiscordRpcApiClient.cs b/experiment/Discord.Net.Rpc/DiscordRpcApiClient.cs index 50d467054..300784f4e 100644 --- a/experiment/Discord.Net.Rpc/DiscordRpcApiClient.cs +++ b/experiment/Discord.Net.Rpc/DiscordRpcApiClient.cs @@ -192,7 +192,7 @@ namespace Discord.API internal override async Task DisconnectInternalAsync() { if (_webSocketClient == null) - throw new NotSupportedException("This client is not configured with websocket support."); + throw new NotSupportedException("This client is not configured with WebSocket support."); if (ConnectionState == ConnectionState.Disconnected) return; ConnectionState = ConnectionState.Disconnecting; diff --git a/experiment/Discord.Net.Rpc/DiscordRpcConfig.cs b/experiment/Discord.Net.Rpc/DiscordRpcConfig.cs index fd6b74ff1..90df8d1a7 100644 --- a/experiment/Discord.Net.Rpc/DiscordRpcConfig.cs +++ b/experiment/Discord.Net.Rpc/DiscordRpcConfig.cs @@ -14,7 +14,7 @@ namespace Discord.Rpc /// Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. public int ConnectionTimeout { get; set; } = 30000; - /// Gets or sets the provider used to generate new websocket connections. + /// Gets or sets the provider used to generate new WebSocket connections. public WebSocketProvider WebSocketProvider { get; set; } public DiscordRpcConfig() @@ -24,7 +24,7 @@ namespace Discord.Rpc #else WebSocketProvider = () => { - throw new InvalidOperationException("The default websocket provider is not supported on this platform.\n" + + throw new InvalidOperationException("The default WebSocket provider is not supported on this platform.\n" + "You must specify a WebSocketProvider or target a runtime supporting .NET Standard 1.3, such as .NET Framework 4.6+."); }; #endif diff --git a/experiment/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/experiment/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs index 42e590aca..195411678 100644 --- a/experiment/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs +++ b/experiment/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -9,6 +9,7 @@ using Model = Discord.API.Rpc.Channel; namespace Discord.Rpc { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RpcDMChannel : RpcChannel, IRpcMessageChannel, IRpcPrivateChannel, IDMChannel { public IReadOnlyCollection CachedMessages { get; private set; } diff --git a/experiment/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/experiment/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs index 1c9355047..9d484c25d 100644 --- a/experiment/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs +++ b/experiment/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -10,6 +10,7 @@ using Model = Discord.API.Rpc.Channel; namespace Discord.Rpc { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RpcGroupChannel : RpcChannel, IRpcMessageChannel, IRpcAudioChannel, IRpcPrivateChannel, IGroupChannel { public IReadOnlyCollection CachedMessages { get; private set; } diff --git a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs index 6cd0abbb7..acdb600ce 100644 --- a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs @@ -2,11 +2,11 @@ using System; namespace Discord.Commands { - /// Provides aliases for a command. + /// Marks the aliases for a command. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class AliasAttribute : Attribute { - /// The aliases which have been defined for the command. + /// Gets the aliases which have been defined for the command. public string[] Aliases { get; } /// Creates a new with the given aliases. diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index a0fcf3e4a..6cc46a792 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -2,17 +2,30 @@ using System; namespace Discord.Commands { + /// Marks the execution information for a command. [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class CommandAttribute : Attribute { + /// + /// Gets the text that has been set to be recognized as a command. + /// public string Text { get; } + /// + /// Specifies the of the command. This affects how the command is executed. + /// public RunMode RunMode { get; set; } = RunMode.Default; public bool? IgnoreExtraArgs { get; set; } + /// public CommandAttribute() { Text = null; } + + /// + /// Initializes a new attribute with the specified name. + /// + /// The name of the command. public CommandAttribute(string text) { Text = text; diff --git a/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs b/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs index cc23a6d15..451b87f5e 100644 --- a/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs @@ -2,6 +2,7 @@ using System; namespace Discord.Commands { + /// Prevents the marked module from being loaded automatically. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class DontAutoLoadAttribute : Attribute { diff --git a/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs b/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs index c982d93a1..19d946afd 100644 --- a/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs @@ -1,9 +1,12 @@ using System; -namespace Discord.Commands { - - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public class DontInjectAttribute : Attribute { - } - +namespace Discord.Commands +{ + /// + /// Prevents the marked property from being injected into a module. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class DontInjectAttribute : Attribute + { + } } diff --git a/src/Discord.Net.Commands/Attributes/GroupAttribute.cs b/src/Discord.Net.Commands/Attributes/GroupAttribute.cs index b1760d149..e1e38cff7 100644 --- a/src/Discord.Net.Commands/Attributes/GroupAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/GroupAttribute.cs @@ -2,15 +2,26 @@ using System; namespace Discord.Commands { + /// + /// Marks the module as a command group. + /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class GroupAttribute : Attribute { + /// + /// Gets the prefix set for the module. + /// public string Prefix { get; } + /// public GroupAttribute() { Prefix = null; } + /// + /// Initializes a new with the provided prefix. + /// + /// The prefix of the module group. public GroupAttribute(string prefix) { Prefix = prefix; diff --git a/src/Discord.Net.Commands/Attributes/NameAttribute.cs b/src/Discord.Net.Commands/Attributes/NameAttribute.cs index 4a4b2bfed..a6e1f2e5a 100644 --- a/src/Discord.Net.Commands/Attributes/NameAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/NameAttribute.cs @@ -3,11 +3,21 @@ using System; namespace Discord.Commands { // Override public name of command/module + /// + /// Marks the public name of a command, module, or parameter. + /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class NameAttribute : Attribute { + /// + /// Gets the name of the command. + /// public string Text { get; } + /// + /// Marks the public name of a command, module, or parameter with the provided name. + /// + /// The public name of the object. public NameAttribute(string text) { Text = text; diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs index 44ab6d214..1ea2d2574 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -4,17 +4,26 @@ using System.Reflection; namespace Discord.Commands { + /// + /// Marks the to be read by the specified . + /// [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class OverrideTypeReaderAttribute : Attribute { - private static readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); + private static readonly TypeInfo TypeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); + /// + /// Gets the specified of the parameter. + /// public Type TypeReader { get; } + /// + /// The to be used with the parameter. + /// The given does not inherit from . public OverrideTypeReaderAttribute(Type overridenTypeReader) { - if (!_typeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) - throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}"); + if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) + throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}."); TypeReader = overridenTypeReader; } diff --git a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs index 3c5e8cf92..9b750809b 100644 --- a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs @@ -3,9 +3,20 @@ using System.Threading.Tasks; namespace Discord.Commands { + /// + /// Requires the parameter to pass the specified precondition before execution can begin. + /// + /// [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] public abstract class ParameterPreconditionAttribute : Attribute { + /// + /// Checks whether the condition is met before execution of the command. + /// + /// The context of the command. + /// The parameter of the command being checked against. + /// The raw value of the type. + /// The service collection used for dependency injection. public abstract Task CheckPermissionsAsync(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index 367adebf0..316b2729e 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -1,18 +1,31 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.Commands { + /// + /// Requires the module or class to pass the specified precondition before execution can begin. + /// + /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public abstract class PreconditionAttribute : Attribute { /// - /// Specify a group that this precondition belongs to. Preconditions of the same group require only one - /// of the preconditions to pass in order to be successful (A || B). Specifying = - /// or not at all will require *all* preconditions to pass, just like normal (A && B). + /// Specifies a group that this precondition belongs to. /// + /// + /// of the same group require only one of the preconditions to pass in order to + /// be successful (A || B). Specifying = null or not at all will + /// require *all* preconditions to pass, just like normal (A && B). + /// public string Group { get; set; } = null; + /// + /// Checks if the has the sufficient permission to be executed. + /// + /// The context of the command. + /// The command being executed. + /// The service collection used for dependency injection. public abstract Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs index 104252799..adea63edf 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -1,46 +1,52 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.Commands { /// - /// This attribute requires that the bot has a specified permission in the channel a command is invoked in. + /// Requires the bot to have a specific permission in the channel a command is invoked in. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RequireBotPermissionAttribute : PreconditionAttribute { + /// + /// Gets the specified of the precondition. + /// public GuildPermission? GuildPermission { get; } + /// + /// Gets the specified of the precondition. + /// public ChannelPermission? ChannelPermission { get; } /// - /// Require that the bot account has a specified GuildPermission + /// Requires the bot account to have a specific . /// - /// This precondition will always fail if the command is being invoked in a private channel. - /// The GuildPermission that the bot must have. Multiple permissions can be specified by ORing the permissions together. + /// + /// This precondition will always fail if the command is being invoked in a . + /// + /// + /// The that the bot must have. Multiple permissions can be specified + /// by ORing the permissions together. + /// public RequireBotPermissionAttribute(GuildPermission permission) { GuildPermission = permission; ChannelPermission = null; } /// - /// Require that the bot account has a specified ChannelPermission. + /// Requires that the bot account to have a specific . /// - /// The ChannelPermission that the bot must have. Multiple permissions can be specified by ORing the permissions together. - /// - /// - /// [Command("permission")] - /// [RequireBotPermission(ChannelPermission.ManageMessages)] - /// public async Task Purge() - /// { - /// } - /// - /// + /// + /// The that the bot must have. Multiple permissions can be + /// specified by ORing the permissions together. + /// public RequireBotPermissionAttribute(ChannelPermission permission) { ChannelPermission = permission; GuildPermission = null; } + /// public override async Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { IGuildUser guildUser = null; @@ -50,9 +56,9 @@ namespace Discord.Commands if (GuildPermission.HasValue) { if (guildUser == null) - return PreconditionResult.FromError("Command must be used in a guild channel"); + return PreconditionResult.FromError("Command must be used in a guild channel."); if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) - return PreconditionResult.FromError($"Bot requires guild permission {GuildPermission.Value}"); + return PreconditionResult.FromError($"Bot requires guild permission {GuildPermission.Value}."); } if (ChannelPermission.HasValue) @@ -64,7 +70,7 @@ namespace Discord.Commands perms = ChannelPermissions.All(context.Channel); if (!perms.Has(ChannelPermission.Value)) - return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}"); + return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}."); } return PreconditionResult.FromSuccess(); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index 90af035e4..8797f7daf 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -1,28 +1,40 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { + /// + /// Defines the type of command context (i.e. where the command is being executed). + /// [Flags] public enum ContextType { + /// + /// Specifies the command to be executed within a guild. + /// Guild = 0x01, + /// + /// Specifies the command to be executed within a DM. + /// DM = 0x02, + /// + /// Specifies the command to be executed within a group. + /// Group = 0x04 } /// - /// Require that the command be invoked in a specified context. + /// Requires the command to be invoked in a specified context (e.g. in guild, DM). /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RequireContextAttribute : PreconditionAttribute { - public ContextType Contexts { get; } - /// - /// Require that the command be invoked in a specified context. + /// Gets the context required to execute the command. /// + public ContextType Contexts { get; } + + /// Requires the command to be invoked in the specified context. /// The type of context the command can be invoked in. Multiple contexts can be specified by ORing the contexts together. /// /// diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs index 273c764bd..d97db8290 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs @@ -4,11 +4,12 @@ using System.Threading.Tasks; namespace Discord.Commands { /// - /// Require that the command is invoked in a channel marked NSFW + /// Requires the command to be invoked in a channel marked NSFW. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RequireNsfwAttribute : PreconditionAttribute { + /// public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { if (context.Channel is ITextChannel text && text.IsNsfw) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs index 93e3cbe18..3ea35a9fd 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -4,20 +4,21 @@ using System.Threading.Tasks; namespace Discord.Commands { /// - /// Require that the command is invoked by the owner of the bot. + /// Requires the command to be invoked by the owner of the bot. /// /// This precondition will only work if the bot is a bot account. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RequireOwnerAttribute : PreconditionAttribute { + /// public override async Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { switch (context.Client.TokenType) { case TokenType.Bot: - var application = await context.Client.GetApplicationInfoAsync(); + var application = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false); if (context.User.Id != application.Owner.Id) - return PreconditionResult.FromError("Command can only be run by the owner of the bot"); + return PreconditionResult.FromError("Command can only be run by the owner of the bot."); return PreconditionResult.FromSuccess(); default: return PreconditionResult.FromError($"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}."); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index 14121f35b..f2bd717e4 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -1,47 +1,52 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.Commands { /// - /// This attribute requires that the user invoking the command has a specified permission. + /// Requires the user invoking the command to have a specified permission. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RequireUserPermissionAttribute : PreconditionAttribute { + /// + /// Gets the specified of the precondition. + /// public GuildPermission? GuildPermission { get; } + /// + /// Gets the specified of the precondition. + /// public ChannelPermission? ChannelPermission { get; } /// - /// Require that the user invoking the command has a specified GuildPermission + /// Requires that the user invoking the command to have a specific . /// - /// This precondition will always fail if the command is being invoked in a private channel. - /// The GuildPermission that the user must have. Multiple permissions can be specified by ORing the permissions together. + /// + /// This precondition will always fail if the command is being invoked in a . + /// + /// + /// The that the user must have. Multiple permissions can be + /// specified by ORing the permissions together. + /// public RequireUserPermissionAttribute(GuildPermission permission) { GuildPermission = permission; ChannelPermission = null; } /// - /// Require that the user invoking the command has a specified ChannelPermission. + /// Requires that the user invoking the command to have a specific . /// - /// The ChannelPermission that the user must have. Multiple permissions can be specified by ORing the permissions together. - /// - /// - /// [Command("permission")] - /// [RequireUserPermission(ChannelPermission.ReadMessageHistory | ChannelPermission.ReadMessages)] - /// public async Task HasPermission() - /// { - /// await ReplyAsync("You can read messages and the message history!"); - /// } - /// - /// + /// + /// The that the user must have. Multiple permissions can be + /// specified by ORing the permissions together. + /// public RequireUserPermissionAttribute(ChannelPermission permission) { ChannelPermission = permission; GuildPermission = null; } - + + /// public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { var guildUser = context.User as IGuildUser; @@ -49,9 +54,9 @@ namespace Discord.Commands if (GuildPermission.HasValue) { if (guildUser == null) - return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel")); + return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel.")); if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) - return Task.FromResult(PreconditionResult.FromError($"User requires guild permission {GuildPermission.Value}")); + return Task.FromResult(PreconditionResult.FromError($"User requires guild permission {GuildPermission.Value}.")); } if (ChannelPermission.HasValue) @@ -63,7 +68,7 @@ namespace Discord.Commands perms = ChannelPermissions.All(context.Channel); if (!perms.Has(ChannelPermission.Value)) - return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}")); + return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}.")); } return Task.FromResult(PreconditionResult.FromSuccess()); diff --git a/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs b/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs index 353e96e41..75ffd2585 100644 --- a/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs @@ -2,14 +2,20 @@ using System; namespace Discord.Commands { - /// Sets priority of commands + /// + /// Sets priority of commands. + /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class PriorityAttribute : Attribute { - /// The priority which has been set for the command + /// + /// Gets the priority which has been set for the command. + /// public int Priority { get; } - /// Creates a new with the given priority. + /// + /// Initializes a new attribute with the given priority. + /// public PriorityAttribute(int priority) { Priority = priority; diff --git a/src/Discord.Net.Commands/Attributes/RemainderAttribute.cs b/src/Discord.Net.Commands/Attributes/RemainderAttribute.cs index 56938f167..33e07f0d9 100644 --- a/src/Discord.Net.Commands/Attributes/RemainderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/RemainderAttribute.cs @@ -2,6 +2,9 @@ using System; namespace Discord.Commands { + /// + /// Marks the input to not be parsed by the parser. + /// [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class RemainderAttribute : Attribute { diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 17a170775..3f1ca883a 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -118,6 +118,7 @@ namespace Discord.Commands.Builders return this; } + /// Only the last parameter in a command may have the Remainder or Multiple flag. internal CommandInfo Build(ModuleInfo info, CommandService service) { //Default name to primary alias diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index cbe02aafb..f5b005b29 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -34,7 +34,7 @@ namespace Discord.Commands } else if (IsLoadableModule(typeInfo)) { - await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}."); + await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}.").ConfigureAwait(false); } } diff --git a/src/Discord.Net.Commands/CommandContext.cs b/src/Discord.Net.Commands/CommandContext.cs index 05bde56b1..393cdf97a 100644 --- a/src/Discord.Net.Commands/CommandContext.cs +++ b/src/Discord.Net.Commands/CommandContext.cs @@ -1,15 +1,27 @@ -namespace Discord.Commands +namespace Discord.Commands { + /// The context of a command which may contain the client, user, guild, channel, and message. public class CommandContext : ICommandContext { + /// public IDiscordClient Client { get; } + /// public IGuild Guild { get; } + /// public IMessageChannel Channel { get; } + /// public IUser User { get; } + /// public IUserMessage Message { get; } + /// Indicates whether the channel that the command is executed in is a private channel. public bool IsPrivate => Channel is IPrivateChannel; - + + /// + /// Initializes a new class with the provided client and message. + /// + /// The underlying client. + /// The underlying message. public CommandContext(IDiscordClient client, IUserMessage msg) { Client = client; diff --git a/src/Discord.Net.Commands/CommandError.cs b/src/Discord.Net.Commands/CommandError.cs index abfc14e1d..40d678137 100644 --- a/src/Discord.Net.Commands/CommandError.cs +++ b/src/Discord.Net.Commands/CommandError.cs @@ -1,26 +1,51 @@ -namespace Discord.Commands +namespace Discord.Commands { + /// Defines the type of error a command can throw. public enum CommandError { //Search + /// + /// Thrown when the command is unknown. + /// UnknownCommand = 1, //Parse + /// + /// Thrown when the command fails to be parsed. + /// ParseFailed, + /// + /// Thrown when the input text has too few or too many arguments. + /// BadArgCount, //Parse (Type Reader) //CastFailed, + /// + /// Thrown when the object cannot be found by the . + /// ObjectNotFound, + /// + /// Thrown when more than one objects is matched by . + /// MultipleMatches, //Preconditions + /// + /// Thrown when the command fails to meet a 's conditions. + /// UnmetPrecondition, //Execute + /// + /// Thrown when an exception occurs mid-command execution. + /// Exception, //Runtime + /// + /// Thrown when the command is not successfully executed on runtime. + /// Unsuccessful } } diff --git a/src/Discord.Net.Commands/CommandException.cs b/src/Discord.Net.Commands/CommandException.cs index d5300841a..f8fbda290 100644 --- a/src/Discord.Net.Commands/CommandException.cs +++ b/src/Discord.Net.Commands/CommandException.cs @@ -2,11 +2,24 @@ using System; namespace Discord.Commands { + /// + /// Describes an exception that occurred during a command execution. + /// public class CommandException : Exception { + /// Gets the command that caused the exception. public CommandInfo Command { get; } + /// Gets the command context of the exception. public ICommandContext Context { get; } + /// + /// Initializes a new instance of the class using a + /// information, a context, and the exception that + /// interrupted the execution. + /// + /// The command information. + /// The context of the command. + /// The exception that interrupted the command execution. public CommandException(CommandInfo command, ICommandContext context, Exception ex) : base($"Error occurred executing {command.GetLogText(context)}.", ex) { diff --git a/src/Discord.Net.Commands/CommandMatch.cs b/src/Discord.Net.Commands/CommandMatch.cs index d922a2229..c15a33228 100644 --- a/src/Discord.Net.Commands/CommandMatch.cs +++ b/src/Discord.Net.Commands/CommandMatch.cs @@ -1,13 +1,14 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { public struct CommandMatch { + /// The command that matches the search result. public CommandInfo Command { get; } + /// The alias of the command. public string Alias { get; } public CommandMatch(CommandInfo command, string alias) diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 9ce4e1469..49df92a31 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -170,7 +170,7 @@ namespace Discord.Commands if (isEscaping) return ParseResult.FromError(CommandError.ParseFailed, "Input text may not end on an incomplete escape."); if (curPart == ParserPart.QuotedParameter) - return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete"); + return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete."); //Add missing optionals for (int i = argList.Count; i < command.Parameters.Count; i++) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index f75653ccf..0c23f79d0 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -13,9 +13,15 @@ namespace Discord.Commands { public class CommandService { + /// + /// Occurs when a command-related information is received. + /// public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); + /// + /// Occurs when a command is successfully executed without any runtime error. + /// public event Func CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } } internal readonly AsyncEvent> _commandExecutedEvent = new AsyncEvent>(); @@ -34,11 +40,33 @@ namespace Discord.Commands internal readonly LogManager _logManager; internal readonly IReadOnlyDictionary _quotationMarkAliasMap; + /// + /// Represents all modules loaded within . + /// public IEnumerable Modules => _moduleDefs.Select(x => x); + + /// + /// Represents all commands loaded within . + /// public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); + + /// + /// Represents all loaded within . + /// public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); + /// + /// Initializes a new class. + /// public CommandService() : this(new CommandServiceConfig()) { } + + /// + /// Initializes a new class with the provided configuration. + /// + /// The configuration class. + /// + /// The cannot be set to . + /// public CommandService(CommandServiceConfig config) { _caseSensitive = config.CaseSensitiveCommands; @@ -102,12 +130,33 @@ namespace Discord.Commands } /// - /// Add a command module from a type + /// Add a command module from a . /// - /// The type of module - /// An IServiceProvider for your dependency injection solution, if using one - otherwise, pass null - /// A built module + /// The type of module. + /// + /// The for your dependency injection solution, if using one - otherwise, pass + /// null. + /// + /// + /// A built module. + /// + /// This module has already been added. + /// The fails to be built; an invalid type may have been provided. public Task AddModuleAsync(IServiceProvider services) => AddModuleAsync(typeof(T), services); + + /// + /// Adds a command module from a . + /// + /// The type of module. + /// + /// The for your dependency injection solution, if using one - otherwise, pass + /// null. + /// + /// + /// A built module. + /// + /// This module has already been added. + /// The fails to be built; an invalid type may have been provided. public async Task AddModuleAsync(Type type, IServiceProvider services) { services = services ?? EmptyServiceProvider.Instance; @@ -118,7 +167,7 @@ namespace Discord.Commands var typeInfo = type.GetTypeInfo(); if (_typedModuleDefs.ContainsKey(type)) - throw new ArgumentException($"This module has already been added."); + throw new ArgumentException("This module has already been added."); var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); @@ -135,11 +184,16 @@ namespace Discord.Commands } } /// - /// Add command modules from an assembly + /// Add command modules from an . /// - /// The assembly containing command modules - /// An IServiceProvider for your dependency injection solution, if using one - otherwise, pass null - /// A collection of built modules + /// The containing command modules. + /// + /// An for your dependency injection solution, if using one - otherwise, pass + /// null. + /// + /// + /// A collection of built modules. + /// public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services) { services = services ?? EmptyServiceProvider.Instance; @@ -175,7 +229,13 @@ namespace Discord.Commands return module; } - + /// + /// Removes the command module. + /// + /// The to be removed from the service. + /// + /// Returns whether the module is successfully removed. + /// public async Task RemoveModuleAsync(ModuleInfo module) { await _moduleLock.WaitAsync().ConfigureAwait(false); @@ -188,7 +248,21 @@ namespace Discord.Commands _moduleLock.Release(); } } + /// + /// Removes the command module. + /// + /// The of the module. + /// + /// Returns whether the module is successfully removed. + /// public Task RemoveModuleAsync() => RemoveModuleAsync(typeof(T)); + /// + /// Removes the command module. + /// + /// The of the module. + /// + /// Returns whether the module is successfully removed. + /// public async Task RemoveModuleAsync(Type type) { await _moduleLock.WaitAsync().ConfigureAwait(false); @@ -222,44 +296,56 @@ namespace Discord.Commands //Type Readers /// - /// Adds a custom to this for the supplied object type. - /// If is a , a will also be added. - /// If a default exists for , a warning will be logged and the default will be replaced. + /// Adds a custom to this for the supplied object type. + /// If is a , a nullable will also be + /// added. + /// If a default exists for , a warning will be logged and the + /// default will be replaced. /// - /// The object type to be read by the . - /// An instance of the to be added. + /// The object type to be read by the . + /// An instance of the to be added. public void AddTypeReader(TypeReader reader) => AddTypeReader(typeof(T), reader); /// - /// Adds a custom to this for the supplied object type. - /// If is a , a for the value type will also be added. - /// If a default exists for , a warning will be logged and the default will be replaced. + /// Adds a custom to this for the supplied object type. + /// If is a , a nullable for the value + /// type will also be added. + /// If a default exists for , a warning will be logged and the + /// default will be replaced. /// - /// A instance for the type to be read. - /// An instance of the to be added. + /// A instance for the type to be read. + /// An instance of the to be added. public void AddTypeReader(Type type, TypeReader reader) { if (_defaultTypeReaders.ContainsKey(type)) _ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." + - $"To suppress this message, use AddTypeReader(reader, true)."); + "To suppress this message, use AddTypeReader(reader, true)."); AddTypeReader(type, reader, true); } /// - /// Adds a custom to this for the supplied object type. - /// If is a , a will also be added. + /// Adds a custom to this for the supplied object type. + /// If is a , a nullable will also be + /// added. /// - /// The object type to be read by the . - /// An instance of the to be added. - /// If should replace the default for if one exists. + /// The object type to be read by the . + /// An instance of the to be added. + /// + /// Defines whether the should replace the default one for + /// if it exists. + /// public void AddTypeReader(TypeReader reader, bool replaceDefault) => AddTypeReader(typeof(T), reader, replaceDefault); /// - /// Adds a custom to this for the supplied object type. - /// If is a , a for the value type will also be added. + /// Adds a custom to this for the supplied object type. + /// If is a , a nullable for the value + /// type will also be added. /// - /// A instance for the type to be read. - /// An instance of the to be added. - /// If should replace the default for if one exists. + /// A instance for the type to be read. + /// An instance of the to be added. + /// + /// Defines whether the should replace the default one for + /// if it exists. + /// public void AddTypeReader(Type type, TypeReader reader, bool replaceDefault) { if (replaceDefault && _defaultTypeReaders.ContainsKey(type)) @@ -321,8 +407,20 @@ namespace Discord.Commands } //Execution + /// + /// Searches for the command. + /// + /// The context of the command. + /// The position of which the command starts at. + /// The result containing the matching commands. public SearchResult Search(ICommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); + /// + /// Searches for the command. + /// + /// The context of the command. + /// The command string. + /// The result containing the matching commands. public SearchResult Search(ICommandContext context, string input) { string searchInput = _caseSensitive ? input : input.ToLowerInvariant(); @@ -334,8 +432,24 @@ namespace Discord.Commands return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } + /// + /// Executes the command. + /// + /// The context of the command. + /// The position of which the command starts at. + /// The service to be used in the command's dependency injection. + /// The handling mode when multiple command matches are found. + /// The result of the command execution. public Task ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) => ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling); + /// + /// Executes the command. + /// + /// The context of the command. + /// The command string. + /// The service to be used in the command's dependency injection. + /// The handling mode when multiple command matches are found. + /// The result of the command execution. public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { services = services ?? EmptyServiceProvider.Instance; diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index a6dd9e33c..f9c90f08e 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -3,23 +3,35 @@ using System.Collections.Generic; namespace Discord.Commands { + /// + /// Represents a configuration class for . + /// public class CommandServiceConfig { - /// Gets or sets the default RunMode commands should have, if one is not specified on the Command attribute or builder. + /// + /// Gets or sets the default commands should have, if one is not specified on the + /// Command attribute or builder. + /// public RunMode DefaultRunMode { get; set; } = RunMode.Sync; /// - /// The delimiter which separates command parameters. + /// Gets or sets the that separates an argument with another. /// public char SeparatorChar { get; set; } = ' '; - /// Determines whether commands should be case-sensitive. + /// + /// Gets or sets whether commands should be case-sensitive. + /// public bool CaseSensitiveCommands { get; set; } = false; - /// Gets or sets the minimum log level severity that will be sent to the Log event. + /// + /// Gets or sets the minimum log level severity that will be sent to the event. + /// public LogSeverity LogLevel { get; set; } = LogSeverity.Info; - /// Determines whether RunMode.Sync commands should push exceptions up to the caller. + /// + /// Gets or sets whether commands should push exceptions up to the caller. + /// public bool ThrowOnError { get; set; } = true; /// diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs index 096b03f6b..e4a9f7de4 100644 --- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs @@ -1,9 +1,15 @@ -using System; +using System; namespace Discord.Commands { + /// + /// Extension methods for that related to commands. + /// public static class MessageExtensions { + /// + /// Gets whether the message starts with the provided character. + /// public static bool HasCharPrefix(this IUserMessage msg, char c, ref int argPos) { var text = msg.Content; @@ -14,6 +20,9 @@ namespace Discord.Commands } return false; } + /// + /// Gets whether the message starts with the provided string. + /// public static bool HasStringPrefix(this IUserMessage msg, string str, ref int argPos, StringComparison comparisonType = StringComparison.Ordinal) { var text = msg.Content; @@ -24,6 +33,9 @@ namespace Discord.Commands } return false; } + /// + /// Gets whether the message starts with the user's mention string. + /// public static bool HasMentionPrefix(this IUserMessage msg, IUser user, ref int argPos) { var text = msg.Content; @@ -43,4 +55,4 @@ namespace Discord.Commands return false; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index df0bb297b..0f0572be7 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -8,10 +8,16 @@ using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { + /// + /// Provides the information of a command. + /// + /// + /// This object contains the information of a command. This can include the module of the command, various + /// descriptions regarding the command, and its . + /// [DebuggerDisplay("{Name,nq}")] public class CommandInfo { @@ -21,18 +27,63 @@ namespace Discord.Commands private readonly CommandService _commandService; private readonly Func _action; + /// + /// Gets the module that the command belongs in. + /// public ModuleInfo Module { get; } + /// + /// Gets the name of the command. If none is set, the first alias is used. + /// public string Name { get; } + /// + /// Gets the summary of the command. + /// + /// + /// This field returns the summary of the command. and can be + /// useful in help commands and various implementation that fetches details of the command for the user. + /// public string Summary { get; } + /// + /// Gets the remarks of the command. + /// + /// + /// This field returns the summary of the command. and can be + /// useful in help commands and various implementation that fetches details of the command for the user. + /// public string Remarks { get; } + /// + /// Gets the priority of the command. This is used when there are multiple overloads of the command. + /// public int Priority { get; } + /// + /// Indicates whether the command accepts a [] for its + /// parameter. + /// public bool HasVarArgs { get; } + /// + /// Indicates whether extra arguments should be ignored for this command. + /// public bool IgnoreExtraArgs { get; } + /// + /// Gets the that is being used for the command. + /// public RunMode RunMode { get; } + /// + /// Gets a list of aliases defined by the of the command. + /// public IReadOnlyList Aliases { get; } + /// + /// Gets a list of information about the parameters of the command. + /// public IReadOnlyList Parameters { get; } + /// + /// Gets a list of preconditions defined by the of the command. + /// public IReadOnlyList Preconditions { get; } + /// + /// Gets a list of attributes of the command. + /// public IReadOnlyList Attributes { get; } internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service) @@ -100,11 +151,11 @@ namespace Discord.Commands return PreconditionGroupResult.FromSuccess(); } - var moduleResult = await CheckGroups(Module.Preconditions, "Module"); + var moduleResult = await CheckGroups(Module.Preconditions, "Module").ConfigureAwait(false); if (!moduleResult.IsSuccess) return moduleResult; - var commandResult = await CheckGroups(Preconditions, "Command"); + var commandResult = await CheckGroups(Preconditions, "Command").ConfigureAwait(false); if (!commandResult.IsSuccess) return commandResult; @@ -124,7 +175,7 @@ namespace Discord.Commands return await CommandParser.ParseArgsAsync(this, context, _commandService._ignoreExtraArgs, services, input, 0, _commandService._quotationMarkAliasMap).ConfigureAwait(false); } - + public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) { if (!parseResult.IsSuccess) @@ -248,11 +299,11 @@ namespace Discord.Commands foreach (object arg in argList) { if (i == argCount) - throw new InvalidOperationException("Command was invoked with too many parameters"); + throw new InvalidOperationException("Command was invoked with too many parameters."); array[i++] = arg; } if (i < argCount) - throw new InvalidOperationException("Command was invoked with too few parameters"); + throw new InvalidOperationException("Command was invoked with too few parameters."); if (HasVarArgs) { diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 4a56415e5..150a98144 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -2,11 +2,12 @@ using Discord.Commands.Builders; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class ParameterInfo { private readonly TypeReader _reader; @@ -65,4 +66,4 @@ namespace Discord.Commands public override string ToString() => Name; private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}"; } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Commands/Map/CommandMap.cs b/src/Discord.Net.Commands/Map/CommandMap.cs index bcff800d3..dfc366333 100644 --- a/src/Discord.Net.Commands/Map/CommandMap.cs +++ b/src/Discord.Net.Commands/Map/CommandMap.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Discord.Commands { @@ -6,7 +6,6 @@ namespace Discord.Commands { private readonly CommandService _service; private readonly CommandMapNode _root; - private static readonly string[] _blankAliases = new[] { "" }; public CommandMap(CommandService service) { diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index 863409207..4f798e718 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -7,7 +7,7 @@ namespace Discord.Commands { internal class CommandMapNode { - private static readonly char[] _whitespaceChars = new[] { ' ', '\r', '\n' }; + private static readonly char[] WhitespaceChars = new[] { ' ', '\r', '\n' }; private readonly ConcurrentDictionary _nodes; private readonly string _name; @@ -23,6 +23,7 @@ namespace Discord.Commands _commands = ImmutableArray.Create(); } + /// Cannot add commands to the root node. public void AddCommand(CommandService service, string text, int index, CommandInfo command) { int nextSegment = NextSegment(text, index, service._separatorChar); @@ -100,7 +101,7 @@ namespace Discord.Commands } //Check if this is the last command segment before args - nextSegment = NextSegment(text, index, _whitespaceChars, service._separatorChar); + nextSegment = NextSegment(text, index, WhitespaceChars, service._separatorChar); if (nextSegment != -1) { name = text.Substring(index, nextSegment - index); diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index 3e6fbbd9b..7239cac60 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -9,27 +9,45 @@ namespace Discord.Commands public abstract class ModuleBase : IModuleBase where T : class, ICommandContext { + /// + /// The underlying context of the command. + /// + /// + /// public T Context { get; private set; } /// - /// Sends a message to the source channel + /// Sends a message to the source channel. /// - /// Contents of the message; optional only if is specified - /// Specifies if Discord should read this message aloud using TTS - /// An embed to be displayed alongside the message + /// + /// Contents of the message; optional only if is specified. + /// + /// Specifies if Discord should read this aloud using text-to-speech. + /// An embed to be displayed alongside the . protected virtual async Task ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) { return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); } - + /// + /// The method to execute before executing the command. + /// + /// The of the command to be executed. protected virtual void BeforeExecute(CommandInfo command) { } - + /// + /// The method to execute after executing the command. + /// + /// The of the command to be executed. protected virtual void AfterExecute(CommandInfo command) { } + /// + /// The method to execute when building the module. + /// + /// The used to create the module. + /// The builder used to build the module. protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder) { } @@ -38,7 +56,7 @@ namespace Discord.Commands void IModuleBase.SetContext(ICommandContext context) { var newValue = context as T; - Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}"); + Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}."); } void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command); void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); diff --git a/src/Discord.Net.Commands/MultiMatchHandling.cs b/src/Discord.Net.Commands/MultiMatchHandling.cs index 89dcf1c06..319e58edd 100644 --- a/src/Discord.Net.Commands/MultiMatchHandling.cs +++ b/src/Discord.Net.Commands/MultiMatchHandling.cs @@ -1,8 +1,13 @@ -namespace Discord.Commands +namespace Discord.Commands { + /// + /// Specifies the behavior when multiple matches are found during the command parsing stage. + /// public enum MultiMatchHandling { + /// Indicates that when multiple results are found, an exception should be thrown. Exception, + /// Indicates that when multiple results are found, the best result should be chosen. Best } } diff --git a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs index cd7a9d744..6cb9ca6e6 100644 --- a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs @@ -6,19 +6,23 @@ using System.Threading.Tasks; namespace Discord.Commands { + /// + /// A for parsing objects implementing . + /// + /// The type to be checked; must implement . public class ChannelTypeReader : TypeReader where T : class, IChannel { + /// public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { if (context.Guild != null) { var results = new Dictionary(); var channels = await context.Guild.GetChannelsAsync(CacheMode.CacheOnly).ConfigureAwait(false); - ulong id; //By Mention (1.0) - if (MentionUtils.TryParseChannel(input, out id)) + if (MentionUtils.TryParseChannel(input, out ulong id)) AddResult(results, await context.Guild.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); //By Id (0.9) diff --git a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs index c097e6189..d0f93aed7 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -53,14 +53,14 @@ namespace Discord.Commands if (_enumsByValue.TryGetValue(baseValue, out enumValue)) return Task.FromResult(TypeReaderResult.FromSuccess(enumValue)); else - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}")); + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}.")); } else { if (_enumsByName.TryGetValue(input.ToLower(), out enumValue)) return Task.FromResult(TypeReaderResult.FromSuccess(enumValue)); else - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}")); + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}.")); } } } diff --git a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs index a87cfbe43..acec2f12d 100644 --- a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs @@ -4,15 +4,18 @@ using System.Threading.Tasks; namespace Discord.Commands { + /// + /// A for parsing objects implementing . + /// + /// The type to be checked; must implement . public class MessageTypeReader : TypeReader where T : class, IMessage { + /// public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { - ulong id; - //By Id (1.0) - if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out ulong id)) { if (await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) is T msg) return TypeReaderResult.FromSuccess(msg); diff --git a/src/Discord.Net.Commands/Readers/NullableTypeReader.cs b/src/Discord.Net.Commands/Readers/NullableTypeReader.cs index 109689e15..1a64dc198 100644 --- a/src/Discord.Net.Commands/Readers/NullableTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/NullableTypeReader.cs @@ -28,7 +28,7 @@ namespace Discord.Commands { if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase)) return TypeReaderResult.FromSuccess(new T?()); - return await _baseTypeReader.ReadAsync(context, input, services); + return await _baseTypeReader.ReadAsync(context, input, services).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs index b19a6bd69..cb74139df 100644 --- a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.Commands @@ -17,14 +17,16 @@ namespace Discord.Commands private readonly TryParseDelegate _tryParse; private readonly float _score; + /// must be within the range [0, 1]. public PrimitiveTypeReader() : this(PrimitiveParsers.Get(), 1) { } + /// must be within the range [0, 1]. public PrimitiveTypeReader(TryParseDelegate tryParse, float score) { if (score < 0 || score > 1) - throw new ArgumentOutOfRangeException(nameof(score), score, "Scores must be within the range [0, 1]"); + throw new ArgumentOutOfRangeException(nameof(score), score, "Scores must be within the range [0, 1]."); _tryParse = tryParse; _score = score; @@ -34,7 +36,7 @@ namespace Discord.Commands { if (_tryParse(input, out T value)) return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score))); - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}")); + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}.")); } } } diff --git a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs index 508624103..4c9aaf4d8 100644 --- a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs @@ -6,20 +6,23 @@ using System.Threading.Tasks; namespace Discord.Commands { + /// + /// A for parsing objects implementing . + /// + /// The type to be checked; must implement . public class RoleTypeReader : TypeReader where T : class, IRole { + /// public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { - ulong id; - if (context.Guild != null) { var results = new Dictionary(); var roles = context.Guild.Roles; //By Mention (1.0) - if (MentionUtils.TryParseRole(input, out id)) + if (MentionUtils.TryParseRole(input, out var id)) AddResult(results, context.Guild.GetRole(id) as T, 1.00f); //By Id (0.9) diff --git a/src/Discord.Net.Commands/Readers/TypeReader.cs b/src/Discord.Net.Commands/Readers/TypeReader.cs index af45a0aac..037213ae9 100644 --- a/src/Discord.Net.Commands/Readers/TypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TypeReader.cs @@ -1,10 +1,22 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.Commands { + /// + /// Defines a reader class that parses user input into a specified type. + /// public abstract class TypeReader { + /// + /// Attempts to parse the into the desired type. + /// + /// The context of the command. + /// The raw input of the command. + /// The service collection used for dependency injection. + /// + /// An awaitable Task containing the result of the type reading process. + /// public abstract Task ReadAsync(ICommandContext context, string input, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index 425c2ccb7..6d9f1dd8c 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -7,21 +7,25 @@ using System.Threading.Tasks; namespace Discord.Commands { + /// + /// A for parsing objects implementing . + /// + /// The type to be checked; must implement . public class UserTypeReader : TypeReader where T : class, IUser { + /// public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { var results = new Dictionary(); IAsyncEnumerable channelUsers = context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten(); // it's better IReadOnlyCollection guildUsers = ImmutableArray.Create(); - ulong id; if (context.Guild != null) guildUsers = await context.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false); //By Mention (1.0) - if (MentionUtils.TryParseUser(input, out id)) + if (MentionUtils.TryParseUser(input, out var id)) { if (context.Guild != null) AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); @@ -46,7 +50,7 @@ namespace Discord.Commands if (ushort.TryParse(input.Substring(index + 1), out ushort discriminator)) { var channelUser = await channelUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator && - string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase)); + string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false); AddResult(results, channelUser as T, channelUser?.Username == username ? 0.85f : 0.75f); var guildUser = guildUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator && @@ -59,7 +63,8 @@ namespace Discord.Commands { await channelUsers .Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase)) - .ForEachAsync(channelUser => AddResult(results, channelUser as T, channelUser.Username == input ? 0.65f : 0.55f)); + .ForEachAsync(channelUser => AddResult(results, channelUser as T, channelUser.Username == input ? 0.65f : 0.55f)) + .ConfigureAwait(false); foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase))) AddResult(results, guildUser as T, guildUser.Username == input ? 0.60f : 0.50f); @@ -69,10 +74,11 @@ namespace Discord.Commands { await channelUsers .Where(x => string.Equals(input, (x as IGuildUser)?.Nickname, StringComparison.OrdinalIgnoreCase)) - .ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f)); + .ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f)) + .ConfigureAwait(false); - foreach (var guildUser in guildUsers.Where(x => string.Equals(input, (x as IGuildUser).Nickname, StringComparison.OrdinalIgnoreCase))) - AddResult(results, guildUser as T, (guildUser as IGuildUser).Nickname == input ? 0.60f : 0.50f); + foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase))) + AddResult(results, guildUser as T, guildUser.Nickname == input ? 0.60f : 0.50f); } if (results.Count > 0) diff --git a/src/Discord.Net.Commands/Results/ExecuteResult.cs b/src/Discord.Net.Commands/Results/ExecuteResult.cs index bad39e230..7eb252be5 100644 --- a/src/Discord.Net.Commands/Results/ExecuteResult.cs +++ b/src/Discord.Net.Commands/Results/ExecuteResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace Discord.Commands diff --git a/src/Discord.Net.Commands/Results/IResult.cs b/src/Discord.Net.Commands/Results/IResult.cs index 928d1139e..eacea3cfe 100644 --- a/src/Discord.Net.Commands/Results/IResult.cs +++ b/src/Discord.Net.Commands/Results/IResult.cs @@ -1,9 +1,13 @@ -namespace Discord.Commands +namespace Discord.Commands { + /// Represents information of the command execution result. public interface IResult { + /// Describes the error type that may have occurred during the operation. CommandError? Error { get; } + /// Describes the reason for the error. string ErrorReason { get; } + /// Indicates whether the operation was successful or not. bool IsSuccess { get; } } } diff --git a/src/Discord.Net.Commands/Results/ParseResult.cs b/src/Discord.Net.Commands/Results/ParseResult.cs index d4a9af521..96ec667fe 100644 --- a/src/Discord.Net.Commands/Results/ParseResult.cs +++ b/src/Discord.Net.Commands/Results/ParseResult.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; namespace Discord.Commands @@ -9,9 +9,12 @@ namespace Discord.Commands public IReadOnlyList ArgValues { get; } public IReadOnlyList ParamValues { get; } + /// public CommandError? Error { get; } + /// public string ErrorReason { get; } + /// public bool IsSuccess => !Error.HasValue; private ParseResult(IReadOnlyList argValues, IReadOnlyList paramValues, CommandError? error, string errorReason) @@ -21,7 +24,7 @@ namespace Discord.Commands Error = error; ErrorReason = errorReason; } - + public static ParseResult FromSuccess(IReadOnlyList argValues, IReadOnlyList paramValues) { for (int i = 0; i < argValues.Count; i++) diff --git a/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs b/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs index 1d7f29122..cb7b860cd 100644 --- a/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs +++ b/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; namespace Discord.Commands @@ -14,11 +14,11 @@ namespace Discord.Commands PreconditionResults = (preconditions ?? new List(0)).ToReadOnlyCollection(); } - public static new PreconditionGroupResult FromSuccess() + public new static PreconditionGroupResult FromSuccess() => new PreconditionGroupResult(null, null, null); public static PreconditionGroupResult FromError(string reason, ICollection preconditions) => new PreconditionGroupResult(CommandError.UnmetPrecondition, reason, preconditions); - public static new PreconditionGroupResult FromError(IResult result) //needed? + public new static PreconditionGroupResult FromError(IResult result) //needed? => new PreconditionGroupResult(result.Error, result.ErrorReason, null); public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; diff --git a/src/Discord.Net.Commands/Results/PreconditionResult.cs b/src/Discord.Net.Commands/Results/PreconditionResult.cs index ca65a373e..dd6adf31f 100644 --- a/src/Discord.Net.Commands/Results/PreconditionResult.cs +++ b/src/Discord.Net.Commands/Results/PreconditionResult.cs @@ -1,28 +1,55 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Discord.Commands { + /// + /// Represents a result type for command preconditions. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class PreconditionResult : IResult { + /// public CommandError? Error { get; } + /// public string ErrorReason { get; } + /// public bool IsSuccess => !Error.HasValue; + /// + /// Initializes a new class with the command type + /// and reason. + /// + /// The type of failure. + /// The reason of failure. protected PreconditionResult(CommandError? error, string errorReason) { Error = error; ErrorReason = errorReason; } + /// + /// Returns a with no errors. + /// public static PreconditionResult FromSuccess() => new PreconditionResult(null, null); + /// + /// Returns a with and the + /// specified reason. + /// + /// The reason of failure. public static PreconditionResult FromError(string reason) => new PreconditionResult(CommandError.UnmetPrecondition, reason); + /// + /// Returns a with the specified type. + /// + /// The result of failure. public static PreconditionResult FromError(IResult result) => new PreconditionResult(result.Error, result.ErrorReason); + /// + /// Returns a string indicating whether the is successful. + /// public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; } diff --git a/src/Discord.Net.Commands/Results/RuntimeResult.cs b/src/Discord.Net.Commands/Results/RuntimeResult.cs index 2a326a7a3..e4c86fc23 100644 --- a/src/Discord.Net.Commands/Results/RuntimeResult.cs +++ b/src/Discord.Net.Commands/Results/RuntimeResult.cs @@ -1,24 +1,30 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Text; namespace Discord.Commands { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class RuntimeResult : IResult { + /// + /// Initializes a new class with the type of error and reason. + /// + /// The type of failure, or null if none. + /// The reason of failure. protected RuntimeResult(CommandError? error, string reason) { Error = error; Reason = reason; } + /// public CommandError? Error { get; } + /// Describes the execution reason or result. public string Reason { get; } + /// public bool IsSuccess => !Error.HasValue; + /// string IResult.ErrorReason => Reason; public override string ToString() => Reason ?? (IsSuccess ? "Successful" : "Unsuccessful"); diff --git a/src/Discord.Net.Commands/Results/SearchResult.cs b/src/Discord.Net.Commands/Results/SearchResult.cs index 87d900d4d..65f627ab0 100644 --- a/src/Discord.Net.Commands/Results/SearchResult.cs +++ b/src/Discord.Net.Commands/Results/SearchResult.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; namespace Discord.Commands @@ -9,9 +9,12 @@ namespace Discord.Commands public string Text { get; } public IReadOnlyList Commands { get; } + /// public CommandError? Error { get; } + /// public string ErrorReason { get; } + /// public bool IsSuccess => !Error.HasValue; private SearchResult(string text, IReadOnlyList commands, CommandError? error, string errorReason) diff --git a/src/Discord.Net.Commands/Results/TypeReaderResult.cs b/src/Discord.Net.Commands/Results/TypeReaderResult.cs index 639ca3ac1..ae0f89d6c 100644 --- a/src/Discord.Net.Commands/Results/TypeReaderResult.cs +++ b/src/Discord.Net.Commands/Results/TypeReaderResult.cs @@ -27,10 +27,15 @@ namespace Discord.Commands { public IReadOnlyCollection Values { get; } + /// public CommandError? Error { get; } + /// public string ErrorReason { get; } + /// public bool IsSuccess => !Error.HasValue; + + /// TypeReaderResult was not successful. public object BestMatch => IsSuccess ? (Values.Count == 1 ? Values.Single().Value : Values.OrderByDescending(v => v.Score).First().Value) : throw new InvalidOperationException("TypeReaderResult was not successful."); diff --git a/src/Discord.Net.Commands/RunMode.cs b/src/Discord.Net.Commands/RunMode.cs index ecb6a4b58..8e230b500 100644 --- a/src/Discord.Net.Commands/RunMode.cs +++ b/src/Discord.Net.Commands/RunMode.cs @@ -1,9 +1,23 @@ -namespace Discord.Commands +namespace Discord.Commands { + /// + /// Specifies the behavior of the command execution workflow. + /// + /// + /// public enum RunMode { + /// + /// The default behaviour set in . + /// Default, + /// + /// Executes the command on the same thread as gateway one. + /// Sync, + /// + /// Executes the command on a different thread from the gateway one. + /// Async } } diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 30dd7c36b..1eeff29b5 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { @@ -38,7 +37,7 @@ namespace Discord.Commands } catch (Exception ex) { - throw new Exception($"Failed to create \"{ownerType.FullName}\"", ex); + throw new Exception($"Failed to create \"{ownerType.FullName}\".", ex); } } @@ -46,14 +45,14 @@ namespace Discord.Commands { var constructors = ownerType.DeclaredConstructors.Where(x => !x.IsStatic).ToArray(); if (constructors.Length == 0) - throw new InvalidOperationException($"No constructor found for \"{ownerType.FullName}\""); + throw new InvalidOperationException($"No constructor found for \"{ownerType.FullName}\"."); else if (constructors.Length > 1) - throw new InvalidOperationException($"Multiple constructors found for \"{ownerType.FullName}\""); + throw new InvalidOperationException($"Multiple constructors found for \"{ownerType.FullName}\"."); return constructors[0]; } - private static System.Reflection.PropertyInfo[] GetProperties(TypeInfo ownerType) + private static PropertyInfo[] GetProperties(TypeInfo ownerType) { - var result = new List(); + var result = new List(); while (ownerType != _objectTypeInfo) { foreach (var prop in ownerType.DeclaredProperties) @@ -71,7 +70,7 @@ namespace Discord.Commands return commands; if (memberType == typeof(IServiceProvider) || memberType == services.GetType()) return services; - var service = services?.GetService(memberType); + var service = services.GetService(memberType); if (service != null) return service; throw new InvalidOperationException($"Failed to create \"{ownerType.FullName}\", dependency \"{memberType.Name}\" was not found."); diff --git a/src/Discord.Net.Core/Audio/AudioOutStream.cs b/src/Discord.Net.Core/Audio/AudioOutStream.cs index 7019ba8cd..cbc3167a2 100644 --- a/src/Discord.Net.Core/Audio/AudioOutStream.cs +++ b/src/Discord.Net.Core/Audio/AudioOutStream.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; namespace Discord.Audio @@ -7,8 +7,17 @@ namespace Discord.Audio { public override bool CanWrite => true; - public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } - public override void SetLength(long value) { throw new NotSupportedException(); } - public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + /// + /// Reading this stream is not supported. + public override int Read(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + /// + /// Setting the length to this stream is not supported. + public override void SetLength(long value) => + throw new NotSupportedException(); + /// + /// Seeking this stream is not supported.. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException(); } } diff --git a/src/Discord.Net.Core/Audio/AudioStream.cs b/src/Discord.Net.Core/Audio/AudioStream.cs index 97820ea73..d5b36aef2 100644 --- a/src/Discord.Net.Core/Audio/AudioStream.cs +++ b/src/Discord.Net.Core/Audio/AudioStream.cs @@ -11,10 +11,8 @@ namespace Discord.Audio public override bool CanSeek => false; public override bool CanWrite => false; - public virtual void WriteHeader(ushort seq, uint timestamp, bool missed) - { - throw new InvalidOperationException("This stream does not accept headers"); - } + public virtual void WriteHeader(ushort seq, uint timestamp, bool missed) => + throw new InvalidOperationException("This stream does not accept headers."); public override void Write(byte[] buffer, int offset, int count) { WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); @@ -30,15 +28,30 @@ namespace Discord.Audio public virtual Task ClearAsync(CancellationToken cancellationToken) { return Task.Delay(0); } - public override long Length { get { throw new NotSupportedException(); } } + /// + /// Reading stream length is not supported. + public override long Length => + throw new NotSupportedException(); + + /// + /// Getting or setting this stream position is not supported. public override long Position { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } - public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } - public override void SetLength(long value) { throw new NotSupportedException(); } - public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + /// + /// Reading this stream is not supported. + public override int Read(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + /// + /// Setting the length to this stream is not supported. + public override void SetLength(long value) => + throw new NotSupportedException(); + /// + /// Seeking this stream is not supported.. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException(); } } diff --git a/src/Discord.Net.Core/Audio/IAudioClient.cs b/src/Discord.Net.Core/Audio/IAudioClient.cs index 9be8ceef5..018c8bc05 100644 --- a/src/Discord.Net.Core/Audio/IAudioClient.cs +++ b/src/Discord.Net.Core/Audio/IAudioClient.cs @@ -15,7 +15,7 @@ namespace Discord.Audio /// Gets the current connection state of this client. ConnectionState ConnectionState { get; } - /// Gets the estimated round-trip latency, in milliseconds, to the voice websocket server. + /// Gets the estimated round-trip latency, in milliseconds, to the voice WebSocket server. int Latency { get; } /// Gets the estimated round-trip latency, in milliseconds, to the voice UDP server. int UdpLatency { get; } diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index 6bac241aa..0fefc9a0d 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -2,10 +2,32 @@ using System; namespace Discord { + /// + /// Represents a class containing the strings related to various Content Delivery Networks (CDNs). + /// public static class CDN { + /// + /// Returns an application icon URL. + /// + /// The application identifier. + /// The icon identifier. + /// + /// A URL pointing to the application's icon. + /// public static string GetApplicationIconUrl(ulong appId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; + + /// + /// Returns a user avatar URL. + /// + /// The user snowflake identifier. + /// The avatar identifier. + /// The size of the image to return in. This can be any power of two between 16 and 2048. + /// The format to return. + /// + /// A URL pointing to the user's avatar in the specified size. + /// public static string GetUserAvatarUrl(ulong userId, string avatarId, ushort size, ImageFormat format) { if (avatarId == null) @@ -13,27 +35,90 @@ namespace Discord string extension = FormatToExtension(format, avatarId); return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}"; } + /// + /// Returns the default user avatar URL. + /// + /// The discriminator value of a user. + /// + /// A URL pointing to the user's default avatar when one isn't set. + /// public static string GetDefaultUserAvatarUrl(ushort discriminator) { return $"{DiscordConfig.CDNUrl}embed/avatars/{discriminator % 5}.png"; } + /// + /// Returns an icon URL. + /// + /// The guild snowflake identifier. + /// The icon identifier. + /// + /// A URL pointing to the guild's icon. + /// public static string GetGuildIconUrl(ulong guildId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null; + /// + /// Returns a guild splash URL. + /// + /// The guild snowflake identifier. + /// The splash icon identifier. + /// + /// A URL pointing to the guild's icon. + /// public static string GetGuildSplashUrl(ulong guildId, string splashId) => splashId != null ? $"{DiscordConfig.CDNUrl}splashes/{guildId}/{splashId}.jpg" : null; + /// + /// Returns a channel icon URL. + /// + /// The channel snowflake identifier. + /// The icon identifier. + /// + /// A URL pointing to the channel's icon. + /// public static string GetChannelIconUrl(ulong channelId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}channel-icons/{channelId}/{iconId}.jpg" : null; + /// + /// Returns an emoji URL. + /// + /// The emoji snowflake identifier. + /// Whether this emoji is animated. + /// + /// A URL pointing to the custom emote. + /// public static string GetEmojiUrl(ulong emojiId, bool animated) => $"{DiscordConfig.CDNUrl}emojis/{emojiId}.{(animated ? "gif" : "png")}"; + /// + /// Returns a Rich Presence asset URL. + /// + /// The application identifier. + /// The asset identifier. + /// The size of the image to return in. This can be any power of two between 16 and 2048. + /// The format to return. + /// + /// A URL pointing to the asset image in the specified size. + /// public static string GetRichAssetUrl(ulong appId, string assetId, ushort size, ImageFormat format) { string extension = FormatToExtension(format, ""); return $"{DiscordConfig.CDNUrl}app-assets/{appId}/{assetId}.{extension}?size={size}"; } + /// + /// Returns a Spotify album URL. + /// + /// The identifier for the album art (e.g. 6be8f4c8614ecf4f1dd3ebba8d8692d8ce4951ac). + /// + /// A URL pointing to the Spotify album art. + /// public static string GetSpotifyAlbumArtUrl(string albumArtId) => $"https://i.scdn.co/image/{albumArtId}"; + /// + /// Returns a Spotify direct URL for a track. + /// + /// The identifier for the track (e.g. 4uLU6hMCjMI75M1A2tKUQC). + /// + /// A URL pointing to the Spotify track. + /// public static string GetSpotifyDirectUrl(string trackId) => $"https://open.spotify.com/track/{trackId}"; diff --git a/src/Discord.Net.Core/Commands/ICommandContext.cs b/src/Discord.Net.Core/Commands/ICommandContext.cs index ac1424339..d56eb38a0 100644 --- a/src/Discord.Net.Core/Commands/ICommandContext.cs +++ b/src/Discord.Net.Core/Commands/ICommandContext.cs @@ -1,11 +1,29 @@ -namespace Discord.Commands +namespace Discord.Commands { + /// + /// Represents a context of a command. This may include the client, guild, channel, user, and message. + /// public interface ICommandContext { + /// + /// Gets the that the command is executed with. + /// IDiscordClient Client { get; } + /// + /// Gets the that the command is executed in. + /// IGuild Guild { get; } + /// + /// Gets the that the command is executed in. + /// IMessageChannel Channel { get; } + /// + /// Gets the who executed the command. + /// IUser User { get; } + /// + /// Gets the that the command is interpreted from. + /// IUserMessage Message { get; } } } diff --git a/src/Discord.Net.Core/ConnectionState.cs b/src/Discord.Net.Core/ConnectionState.cs index 42c505ccd..fadbc4065 100644 --- a/src/Discord.Net.Core/ConnectionState.cs +++ b/src/Discord.Net.Core/ConnectionState.cs @@ -1,10 +1,15 @@ -namespace Discord +namespace Discord { + /// Specifies the connection state of a client. public enum ConnectionState : byte { + /// The client has disconnected from Discord. Disconnected, + /// The client is connecting to Discord. Connecting, + /// The client has established a connection to Discord. Connected, + /// The client is disconnecting from Discord. Disconnecting } } diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index 5cdf6a77e..489219161 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -2,34 +2,135 @@ using System.Reflection; namespace Discord { + /// + /// Defines various behaviors of Discord.Net. + /// public class DiscordConfig { + /// + /// Returns the API version Discord.Net uses. + /// + /// + /// A 32-bit integer representing the API version that Discord.Net uses to communicate with Discord. + /// A list of available API version can be seen on the official + /// Discord API documentation + /// . + /// public const int APIVersion = 6; + /// + /// Gets the Discord.Net version, including the build number. + /// + /// + /// A string containing the detailed version information, including its build number; Unknown when + /// the version fails to be fetched. + /// public static string Version { get; } = typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion ?? typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? "Unknown"; - + + /// + /// Gets the user agent that Discord.Net uses in its clients. + /// + /// + /// The user agent used in each Discord.Net request. + /// public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; + /// + /// Returns the base Discord API URL. + /// + /// + /// The Discord API URL using . + /// public static readonly string APIUrl = $"https://discordapp.com/api/v{APIVersion}/"; + /// + /// Returns the base Discord CDN URL. + /// + /// + /// The base Discord Content Delivery Network (CDN) URL. + /// public const string CDNUrl = "https://cdn.discordapp.com/"; + /// + /// Returns the base Discord invite URL. + /// + /// + /// The base Discord invite URL. + /// public const string InviteUrl = "https://discord.gg/"; + /// + /// Returns the default timeout for requests. + /// + /// + /// The amount of time it takes in milliseconds before a request is timed out. + /// public const int DefaultRequestTimeout = 15000; + /// + /// Returns the max length for a Discord message. + /// + /// + /// The maximum length of a message allowed by Discord. + /// public const int MaxMessageSize = 2000; + /// + /// Returns the max messages allowed to be in a request. + /// + /// + /// The maximum number of messages that can be gotten per-batch. + /// public const int MaxMessagesPerBatch = 100; + /// + /// Returns the max users allowed to be in a request. + /// + /// + /// The maximum number of users that can be gotten per-batch. + /// public const int MaxUsersPerBatch = 1000; + /// + /// Returns the max guilds allowed to be in a request. + /// + /// + /// The maximum number of guilds that can be gotten per-batch. + /// public const int MaxGuildsPerBatch = 100; + /// + /// Returns the max user reactions allowed to be in a request. + /// + /// + /// The maximum number of user reactions that can be gotten per-batch. + /// public const int MaxUserReactionsPerBatch = 100; + /// + /// Returns the max audit log entries allowed to be in a request. + /// + /// + /// The maximum number of audit log entries that can be gotten per-batch. + /// public const int MaxAuditLogEntriesPerBatch = 100; - /// Gets or sets how a request should act in the case of an error, by default. + /// + /// Gets or sets how a request should act in the case of an error, by default. + /// + /// + /// The currently set . + /// public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; - - /// Gets or sets the minimum log level severity that will be sent to the Log event. + + /// + /// Gets or sets the minimum log level severity that will be sent to the Log event. + /// + /// + /// The currently set for logging level. + /// public LogSeverity LogLevel { get; set; } = LogSeverity.Info; - /// Gets or sets whether the initial log entry should be printed. + /// + /// Gets or sets whether the initial log entry should be printed. + /// + /// + /// If set to true, the library will attempt to print the current version of the library, as well as + /// the API version it uses on startup. + /// internal bool DisplayInitialLog { get; set; } = true; } } diff --git a/src/Discord.Net.Core/Entities/Activities/ActivityType.cs b/src/Discord.Net.Core/Entities/Activities/ActivityType.cs index c7db7b247..67235c499 100644 --- a/src/Discord.Net.Core/Entities/Activities/ActivityType.cs +++ b/src/Discord.Net.Core/Entities/Activities/ActivityType.cs @@ -1,10 +1,25 @@ -namespace Discord +namespace Discord { + /// + /// Specifies a Discord user's activity type. + /// public enum ActivityType { + /// + /// The user is playing a game. + /// Playing = 0, + /// + /// The user is streaming online. + /// Streaming = 1, + /// + /// The user is listening to a song. + /// Listening = 2, + /// + /// The user is watching a media. + /// Watching = 3 } } diff --git a/src/Discord.Net.Core/Entities/Activities/Game.cs b/src/Discord.Net.Core/Entities/Activities/Game.cs index 179ad4eaa..62b2853d5 100644 --- a/src/Discord.Net.Core/Entities/Activities/Game.cs +++ b/src/Discord.Net.Core/Entities/Activities/Game.cs @@ -2,19 +2,30 @@ using System.Diagnostics; namespace Discord { + /// + /// A user's game status. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Game : IActivity { + /// public string Name { get; internal set; } + /// public ActivityType Type { get; internal set; } internal Game() { } + /// + /// Creates a with the provided and . + /// + /// The name of the game. + /// The type of activity. Default is . public Game(string name, ActivityType type = ActivityType.Playing) { Name = name; Type = type; } + /// Returns the name of the . public override string ToString() => Name; private string DebuggerDisplay => Name; } diff --git a/src/Discord.Net.Core/Entities/Activities/GameAsset.cs b/src/Discord.Net.Core/Entities/Activities/GameAsset.cs index 02c29ba41..467b2885f 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameAsset.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameAsset.cs @@ -1,14 +1,26 @@ namespace Discord { + /// + /// An asset for a object containing the text and image. + /// public class GameAsset { internal GameAsset() { } internal ulong? ApplicationId { get; set; } - + + /// + /// Gets the description of the asset. + /// public string Text { get; internal set; } + /// + /// Gets the image ID of the asset. + /// public string ImageId { get; internal set; } - + + /// + /// Returns the image URL of the asset, or null when the application ID does not exist. + /// public string GetImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => ApplicationId.HasValue ? CDN.GetRichAssetUrl(ApplicationId.Value, ImageId, size, format) : null; } diff --git a/src/Discord.Net.Core/Entities/Activities/GameParty.cs b/src/Discord.Net.Core/Entities/Activities/GameParty.cs index 54e6deef4..c3449df36 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameParty.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameParty.cs @@ -1,11 +1,20 @@ namespace Discord { + /// + /// Party information for a object. + /// public class GameParty { internal GameParty() { } + /// + /// Gets the ID of the party. + /// public string Id { get; internal set; } public long Members { get; internal set; } + /// + /// Gets the party's current and maximum size. + /// public long Capacity { get; internal set; } } } diff --git a/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs b/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs index e9d988ba9..595b8851c 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs @@ -1,9 +1,21 @@ -namespace Discord +namespace Discord { + /// + /// Party secret for a object. + /// public class GameSecrets { + /// + /// Gets the secret for a specific instanced match. + /// public string Match { get; } + /// + /// Gets the secret for joining a party. + /// public string Join { get; } + /// + /// Gets the secret for spectating a game. + /// public string Spectate { get; } internal GameSecrets(string match, string join, string spectate) @@ -13,4 +25,4 @@ Spectate = spectate; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs b/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs index 8c8c992fa..a41388afb 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs @@ -1,10 +1,19 @@ -using System; +using System; namespace Discord { + /// + /// Timestamps for a object. + /// public class GameTimestamps { + /// + /// Gets when the activity started. + /// public DateTimeOffset? Start { get; } + /// + /// Gets when the activity ends. + /// public DateTimeOffset? End { get; } internal GameTimestamps(DateTimeOffset? start, DateTimeOffset? end) @@ -13,4 +22,4 @@ namespace Discord End = end; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Activities/IActivity.cs b/src/Discord.Net.Core/Entities/Activities/IActivity.cs index 1f158217d..30d936952 100644 --- a/src/Discord.Net.Core/Entities/Activities/IActivity.cs +++ b/src/Discord.Net.Core/Entities/Activities/IActivity.cs @@ -1,8 +1,17 @@ -namespace Discord +namespace Discord { + /// + /// A user's activity status, typically a . + /// public interface IActivity { + /// + /// Gets the name of the activity. + /// string Name { get; } + /// + /// Gets the type of the activity. + /// ActivityType Type { get; } } } diff --git a/src/Discord.Net.Core/Entities/Activities/RichGame.cs b/src/Discord.Net.Core/Entities/Activities/RichGame.cs index fc3f68cf0..2455fd557 100644 --- a/src/Discord.Net.Core/Entities/Activities/RichGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/RichGame.cs @@ -2,20 +2,50 @@ using System.Diagnostics; namespace Discord { + /// + /// A user's Rich Presence status. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RichGame : Game { internal RichGame() { } + /// + /// Gets what the player is currently doing. + /// public string Details { get; internal set; } + /// + /// Gets the user's current party status. + /// public string State { get; internal set; } + /// + /// Gets the application ID for the game. + /// public ulong ApplicationId { get; internal set; } + /// + /// Gets the small image for the presence and their hover texts. + /// public GameAsset SmallAsset { get; internal set; } + /// + /// Gets the large image for the presence and their hover texts. + /// public GameAsset LargeAsset { get; internal set; } + /// + /// Gets the information for the current party of the player. + /// public GameParty Party { get; internal set; } + /// + /// Gets the secrets for Rich Presence joining and spectating. + /// public GameSecrets Secrets { get; internal set; } + /// + /// Gets the timestamps for start and/or end of the game. + /// public GameTimestamps Timestamps { get; internal set; } - + + /// + /// Returns the name of the Rich Presence. + /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} (Rich)"; } diff --git a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs index 8b966d68b..23f88687d 100644 --- a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs @@ -4,22 +4,85 @@ using System.Diagnostics; namespace Discord { + /// + /// A user's activity for listening to a song on Spotify. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SpotifyGame : Game { + /// + /// Gets the song's artist(s). + /// + /// + /// A collection of string containing all artists featured in the track (e.g. Avicii; Rita Ora). + /// public IReadOnlyCollection Artists { get; internal set; } + /// + /// Gets the Spotify album title of the song. + /// + /// + /// A string containing the name of the album (e.g. AVĪCI (01)). + /// public string AlbumTitle { get; internal set; } + /// + /// Gets the track title of the song. + /// + /// + /// A string containing the name of the song (e.g. Lonely Together (feat. Rita Ora)). + /// public string TrackTitle { get; internal set; } + /// + /// Gets the duration of the song. + /// + /// + /// A containing the duration of the song. + /// public TimeSpan? Duration { get; internal set; } + /// + /// Gets the track ID of the song. + /// + /// + /// A string containing the Spotify ID of the track (e.g. 7DoN0sCGIT9IcLrtBDm4f0). + /// public string TrackId { get; internal set; } + /// + /// Gets the session ID of the song. + /// + /// + /// The purpose of this property is currently unknown. + /// + /// + /// A string containing the session ID. + /// public string SessionId { get; internal set; } + /// + /// Gets the URL of the album art. + /// + /// + /// A URL pointing to the album art of the track (e.g. + /// https://i.scdn.co/image/ba2fd8823d42802c2f8738db0b33a4597f2f39e7). + /// public string AlbumArtUrl { get; internal set; } + /// + /// Gets the direct Spotify URL of the track. + /// + /// + /// A URL pointing directly to the track on Spotify. (e.g. + /// https://open.spotify.com/track/7DoN0sCGIT9IcLrtBDm4f0). + /// public string TrackUrl { get; internal set; } internal SpotifyGame() { } + /// + /// Gets the full information of the song. + /// + /// + /// A string containing the full information of the song (e.g. + /// Avicii, Rita Ora - Lonely Together (feat. Rita Ora) (3:08) + /// public override string ToString() => $"{string.Join(", ", Artists)} - {TrackTitle} ({Duration})"; private string DebuggerDisplay => $"{Name} (Spotify)"; } diff --git a/src/Discord.Net.Core/Entities/Activities/StreamingGame.cs b/src/Discord.Net.Core/Entities/Activities/StreamingGame.cs index afbc24cd9..127ae0b7f 100644 --- a/src/Discord.Net.Core/Entities/Activities/StreamingGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/StreamingGame.cs @@ -1,12 +1,23 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Discord { + /// + /// A user's activity for streaming on services such as Twitch. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class StreamingGame : Game { + /// + /// Gets the URL of the stream. + /// public string Url { get; internal set; } + /// + /// Creates a new based on the on the stream URL. + /// + /// The name of the stream. + /// The URL of the stream. public StreamingGame(string name, string url) { Name = name; @@ -14,7 +25,10 @@ namespace Discord Type = ActivityType.Streaming; } + /// + /// Gets the name of the stream. + /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Url})"; } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs index 47aaffb26..da28b53a5 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// - /// Represents data applied to an + /// Represents data applied to an . /// public interface IAuditLogData { } diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs index b85730a1d..d8f7f1a9f 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs @@ -1,34 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// - /// Represents an entry in an audit log + /// Represents a generic audit log entry. /// public interface IAuditLogEntry : IEntity { /// - /// The action which occured to create this entry + /// Gets the action which occurred to create this entry. /// + /// + /// The type of action for this audit log entry. + /// ActionType Action { get; } /// - /// The data for this entry. May be if no data was available. + /// Gets the data for this entry. /// + /// + /// An for this audit log entry; null if no data is available. + /// IAuditLogData Data { get; } /// - /// The user responsible for causing the changes + /// Gets the user responsible for causing the changes. /// + /// + /// A user object. + /// IUser User { get; } /// - /// The reason behind the change. May be if no reason was provided. + /// Gets the reason behind the change. /// + /// + /// A string containing the reason for the change; null if none is provided. + /// string Reason { get; } } } diff --git a/src/Discord.Net.Core/Entities/CacheMode.cs b/src/Discord.Net.Core/Entities/CacheMode.cs index a047bd616..503a80479 100644 --- a/src/Discord.Net.Core/Entities/CacheMode.cs +++ b/src/Discord.Net.Core/Entities/CacheMode.cs @@ -1,8 +1,17 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the cache mode that should be used. + /// public enum CacheMode { + /// + /// Allows the object to be downloaded if it does not exist in the current cache. + /// AllowDownload, + /// + /// Only allows the object to be pulled from the existing cache. + /// CacheOnly } } diff --git a/src/Discord.Net.Core/Entities/Channels/Direction.cs b/src/Discord.Net.Core/Entities/Channels/Direction.cs index 5d8d5e621..6d5f7cd6b 100644 --- a/src/Discord.Net.Core/Entities/Channels/Direction.cs +++ b/src/Discord.Net.Core/Entities/Channels/Direction.cs @@ -1,9 +1,21 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the direction of where message(s) should be gotten from. + /// public enum Direction { + /// + /// The message(s) should be retrieved before a message. + /// Before, + /// + /// The message(s) should be retrieved after a message. + /// After, + /// + /// The message(s) should be retrieved around a message. + /// Around } } diff --git a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs index 2ac6c8d52..a0edfc796 100644 --- a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs @@ -1,33 +1,25 @@ -namespace Discord +namespace Discord { /// - /// Modify an IGuildChannel with the specified changes. + /// Properties that are used to modify an with the specified changes. /// - /// - /// - /// await (Context.Channel as ITextChannel)?.ModifyAsync(x => - /// { - /// x.Name = "do-not-enter"; - /// }); - /// - /// + /// public class GuildChannelProperties { /// - /// Set the channel to this name + /// Gets or sets the channel to this name. /// /// - /// When modifying an ITextChannel, the Name MUST be alphanumeric with dashes. - /// It must match the following RegEx: [a-z0-9-_]{2,100} + /// When modifying an , the + /// MUST be alphanumeric with dashes. It must match the following RegEx: [a-z0-9-_]{2,100} /// - /// A BadRequest will be thrown if the name does not match the above RegEx. public Optional Name { get; set; } /// - /// Move the channel to the following position. This is 0-based! + /// Moves the channel to the following position. This property is zero-based. /// public Optional Position { get; set; } /// - /// Sets the category for this channel + /// Gets or sets the category ID for this channel. /// public Optional CategoryId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs index afb81d92f..6295a6829 100644 --- a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs @@ -1,12 +1,17 @@ -using Discord.Audio; +using Discord.Audio; using System; using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic audio channel. + /// public interface IAudioChannel : IChannel { - /// Connects to this audio channel. + /// + /// Connects to this audio channel. + /// Task ConnectAsync(Action configAction = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs b/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs index c004cafd5..838908b68 100644 --- a/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs @@ -1,11 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { + /// + /// Represents a generic category channel. + /// public interface ICategoryChannel : IGuildChannel { } diff --git a/src/Discord.Net.Core/Entities/Channels/IChannel.cs b/src/Discord.Net.Core/Entities/Channels/IChannel.cs index ea930e112..4f14431c5 100644 --- a/src/Discord.Net.Core/Entities/Channels/IChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs @@ -1,17 +1,26 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic channel. + /// public interface IChannel : ISnowflakeEntity { - /// Gets the name of this channel. + /// + /// Gets the name of this channel. + /// string Name { get; } - /// Gets a collection of all users in this channel. + /// + /// Gets a collection of all users in this channel. + /// IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets a user in this channel with the provided id. + /// + /// Gets a user in this channel with the provided ID. + /// Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs b/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs index 1608d1543..49e58038a 100644 --- a/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs @@ -1,13 +1,21 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic direct-message channel. + /// public interface IDMChannel : IMessageChannel, IPrivateChannel { - /// Gets the recipient of all messages in this channel. + /// + /// Gets the recipient of all messages in this channel. + /// IUser Recipient { get; } - /// Closes this private channel, removing it from your channel list. + /// + /// Closes this private channel, removing it from your channel list. + /// + /// The options to be used when sending the request. Task CloseAsync(RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs index d6cb2c182..8ee2b622c 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs @@ -1,10 +1,16 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic private group channel. + /// public interface IGroupChannel : IMessageChannel, IPrivateChannel, IAudioChannel { - /// Leaves this group. + /// + /// Leaves this group. + /// + /// The options to be used when sending the request. Task LeaveAsync(RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs index 6514d46cd..0c8ae3b58 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -4,45 +4,130 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic guild channel. + /// + /// + /// + /// public interface IGuildChannel : IChannel, IDeletable { - /// Gets the position of this channel in the guild's channel list, relative to others of the same type. + /// + /// Gets the position of this channel. + /// + /// + /// The position of this channel in the guild's channel list, relative to others of the same type. + /// int Position { get; } - /// Gets the guild this channel is a member of. + /// + /// Gets the guild associated with this channel. + /// + /// + /// The guild that this channel belongs to. + /// IGuild Guild { get; } - /// Gets the id of the guild this channel is a member of. + /// + /// Gets the guild ID associated with this channel. + /// + /// + /// The guild ID that this channel belongs to. + /// ulong GuildId { get; } - /// Gets a collection of permission overwrites for this channel. + /// + /// Gets a collection of permission overwrites for this channel. + /// + /// + /// A collection of overwrites associated with this channel. + /// IReadOnlyCollection PermissionOverwrites { get; } - /// Creates a new invite to this channel. - /// The time (in seconds) until the invite expires. Set to null to never expire. - /// The max amount of times this invite may be used. Set to null to have unlimited uses. - /// If true, a user accepting this invite will be kicked from the guild after closing their client. + /// + /// Creates a new invite to this channel. + /// + /// + /// The time (in seconds) until the invite expires. Set to null to never expire. + /// + /// + /// The max amount of times this invite may be used. Set to null to have unlimited uses. + /// + /// + /// If true, a user accepting this invite will be kicked from the guild after closing their client. + /// + /// + /// If true, don't try to reuse a similar invite (useful for creating many unique one time use invites). + /// + /// + /// The options to be used when sending the request. + /// Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); - /// Returns a collection of all invites to this channel. + /// + /// Returns a collection of all invites to this channel. + /// + /// The options to be used when sending the request. Task> GetInvitesAsync(RequestOptions options = null); - /// Modifies this guild channel. + /// + /// Modifies this guild channel. + /// + /// The properties to modify the channel with. + /// The options to be used when sending the request. Task ModifyAsync(Action func, RequestOptions options = null); - /// Gets the permission overwrite for a specific role, or null if one does not exist. + /// + /// Gets the permission overwrite for a specific role, or null if one does not exist. + /// + /// The role to get the overwrite from. OverwritePermissions? GetPermissionOverwrite(IRole role); - /// Gets the permission overwrite for a specific user, or null if one does not exist. + /// + /// Gets the permission overwrite for a specific user, or null if one does not exist. + /// + /// The user to get the overwrite from. OverwritePermissions? GetPermissionOverwrite(IUser user); - /// Removes the permission overwrite for the given role, if one exists. + /// + /// Removes the permission overwrite for the given role, if one exists. + /// + /// The role to remove the overwrite from. + /// The options to be used when sending the request. Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null); - /// Removes the permission overwrite for the given user, if one exists. + /// + /// Removes the permission overwrite for the given user, if one exists. + /// + /// The user to remove the overwrite from. + /// The options to be used when sending the request. Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null); - /// Adds or updates the permission overwrite for the given role. + + /// + /// Adds or updates the permission overwrite for the given role. + /// + /// The role to add the overwrite to. + /// The overwrite to add to the role. + /// The options to be used when sending the request. Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null); - /// Adds or updates the permission overwrite for the given user. + /// + /// Adds or updates the permission overwrite for the given user. + /// + /// The user to add the overwrite to. + /// The overwrite to add to the user. + /// The options to be used when sending the request. Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null); - /// Gets a collection of all users in this channel. + /// + /// Gets a collection of all users in this channel. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. new IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets a user in this channel with the provided id. + /// + /// Gets a user in this channel with the provided ID. + /// + /// The ID of the user. + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. new Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index ef5a6fa7a..803e07ff6 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -5,28 +5,118 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic channel that can send and receive messages. + /// public interface IMessageChannel : IChannel { - /// Sends a message to this message channel. + /// + /// Sends a message to this message channel. + /// + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// An awaitable Task containing the message sent to the channel. + /// Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); - /// Sends a file to this text channel, with an optional caption. + /// + /// Sends a file to this message channel with an optional caption. + /// + /// The file path of the file. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); - - /// Sends a file to this text channel, with an optional caption. + /// + /// Sends a file to this message channel with an optional caption. + /// + /// The of the file to be sent. + /// The name of the attachment. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); - /// Gets a message from this message channel with the given id, or null if not found. + /// + /// Gets a message from this message channel with the given id, or null if not found. + /// + /// The ID of the message. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// The message gotten from either the cache or the download, or null if none is found. + /// Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets the last N messages from this message channel. + + /// + /// Gets the last N messages from this message channel. + /// + /// The numbers of message to be gotten from. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. Flattening the paginated response into a collection of messages with + /// is required if you wish to access the messages. + /// IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets a collection of messages in this channel. + /// + /// Gets a collection of messages in this channel. + /// + /// The ID of the starting message to get the messages from. + /// The direction of the messages to be gotten from. + /// The numbers of message to be gotten from. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. Flattening the paginated response into a collection of messages with + /// is required if you wish to access the messages. + /// IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets a collection of messages in this channel. + + /// + /// Gets a collection of messages in this channel. + /// + /// The starting message to get the messages from. + /// The direction of the messages to be gotten from. + /// The numbers of message to be gotten from. + /// The that determines whether the object should be fetched from + /// cache. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. Flattening the paginated response into a collection of messages with + /// is required if you wish to access the messages. + /// IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets a collection of pinned messages in this channel. + /// + /// Gets a collection of pinned messages in this channel. + /// + /// The options to be used when sending the request. + /// + /// An awaitable Task containing a collection of messages. + /// Task> GetPinnedMessagesAsync(RequestOptions options = null); /// Deletes a message based on the message ID in this channel. @@ -34,9 +124,16 @@ namespace Discord /// Deletes a message based on the provided message in this channel. Task DeleteMessageAsync(IMessage message, RequestOptions options = null); - /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. + /// + /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. + /// + /// The options to be used when sending the request. Task TriggerTypingAsync(RequestOptions options = null); - /// Continuously broadcasts the "user is typing" message to all users in this channel until the returned object is disposed. + /// + /// Continuously broadcasts the "user is typing" message to all users in this channel until the returned + /// object is disposed. + /// + /// The options to be used when sending the request. IDisposable EnterTypingState(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/IPrivateChannel.cs b/src/Discord.Net.Core/Entities/Channels/IPrivateChannel.cs index 9a3289794..41b409141 100644 --- a/src/Discord.Net.Core/Entities/Channels/IPrivateChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IPrivateChannel.cs @@ -1,9 +1,18 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Discord { + /// + /// Represents a generic channel that is private to select recipients. + /// public interface IPrivateChannel : IChannel { + /// + /// Gets the users that can access this channel. + /// + /// + /// A collection of users that can access this channel. + /// IReadOnlyCollection Recipients { get; } } } diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index 2aa070b03..92f5b140a 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -5,27 +5,83 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic channel in a guild that can send and receive messages. + /// public interface ITextChannel : IMessageChannel, IMentionable, INestedChannel { - /// Checks if the channel is NSFW. + /// + /// Determines whether the channel is NSFW. + /// + /// + /// true if the channel has the NSFW flag enabled; otherwise, false. + /// bool IsNsfw { get; } - /// Gets the current topic for this text channel. + /// + /// Gets the current topic for this text channel. + /// + /// + /// The topic set in the channel, or null if none is set. + /// string Topic { get; } - /// Bulk deletes multiple messages. + /// + /// Bulk-deletes multiple messages. + /// + /// + /// + /// This method can only remove messages that are posted within 14 days! + /// + /// + /// The messages to be bulk-deleted. + /// The options to be used when sending the request. Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); - /// Bulk deletes multiple messages. + /// + /// Bulk-deletes multiple messages. + /// + /// + /// + /// This method can only remove messages that are posted within 14 days! + /// + /// + /// The IDs of the messages to be bulk-deleted. + /// The options to be used when sending the request. Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null); - /// Modifies this text channel. + /// + /// Modifies this text channel. + /// + /// The properties to modify the channel with. + /// The options to be used when sending the request. Task ModifyAsync(Action func, RequestOptions options = null); - /// Creates a webhook in this text channel. + /// + /// Creates a webhook in this text channel. + /// + /// The name of the webhook. + /// The avatar of the webhook. + /// The options to be used when sending the request. + /// + /// The created webhook. + /// Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null); - /// Gets the webhook in this text channel with the provided id, or null if not found. + /// + /// Gets the webhook in this text channel with the provided ID. + /// + /// The ID of the webhook. + /// The options to be used when sending the request. + /// + /// A webhook associated with the , or null if not found. + /// Task GetWebhookAsync(ulong id, RequestOptions options = null); - /// Gets the webhooks for this text channel. + /// + /// Gets the webhooks for this text channel. + /// + /// The options to be used when sending the request. + /// + /// A collection of webhooks. + /// Task> GetWebhooksAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs index 2e345bfda..3119a5ae7 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -3,14 +3,26 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic voice channel in a guild. + /// public interface IVoiceChannel : INestedChannel, IAudioChannel { - /// Gets the bitrate, in bits per second, clients in this voice channel are requested to use. + /// + /// Gets the bitrate, in bits per second, clients in this voice channel are requested to use. + /// int Bitrate { get; } - /// Gets the max amount of users allowed to be connected to this channel at one time. + /// + /// Gets the max amount of users allowed to be connected to this channel at one time, or + /// null if none is set. + /// int? UserLimit { get; } - /// Modifies this voice channel. + /// + /// Modifies this voice channel. + /// + /// The properties to modify the channel with. + /// The options to be used when sending the request. Task ModifyAsync(Action func, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs index 31f814334..255279fa8 100644 --- a/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs @@ -1,12 +1,22 @@ -namespace Discord +namespace Discord { + /// + /// Properties that are used to reorder an . + /// public class ReorderChannelProperties { - /// The id of the channel to apply this position to. + /// + /// Gets the ID of the channel to apply this position to. + /// public ulong Id { get; } - /// The new zero-based position of this channel. + /// + /// Gets the new zero-based position of this channel. + /// public int Position { get; } + /// Creates a used to reorder a channel. + /// Sets the ID of the channel to apply this position to. + /// Sets the new zero-based position of this channel. public ReorderChannelProperties(ulong id, int position) { Id = id; diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs index b7b568133..03b56b26c 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -1,14 +1,19 @@ -namespace Discord +using System; + +namespace Discord { - /// + /// + /// Properties that are used to modify an with the specified changes. + /// + /// public class TextChannelProperties : GuildChannelProperties { /// - /// What the topic of the channel should be set to. + /// Gets or sets the topic of the channel. /// public Optional Topic { get; set; } /// - /// Should this channel be flagged as NSFW? + /// Gets or sets whether this channel should be flagged as NSFW. /// public Optional IsNsfw { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs index 81dd8063e..46e8f8550 100644 --- a/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs @@ -1,14 +1,16 @@ -namespace Discord +namespace Discord { - /// + /// + /// Properties that are used to modify an with the specified changes. + /// public class VoiceChannelProperties : GuildChannelProperties { /// - /// The bitrate of the voice connections in this channel. Must be greater than 8000 + /// Gets or sets the bitrate of the voice connections in this channel. Must be greater than 8000. /// public Optional Bitrate { get; set; } /// - /// The maximum number of users that can be present in a channel. + /// Gets or sets the maximum number of users that can be present in a channel, or null if none. /// public Optional UserLimit { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs index c2dfc31ad..c7a2dc5eb 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs @@ -1,28 +1,32 @@ -namespace Discord +namespace Discord { /// - /// A unicode emoji + /// A Unicode emoji. /// public class Emoji : IEmote { - // TODO: need to constrain this to unicode-only emojis somehow + // TODO: need to constrain this to Unicode-only emojis somehow + /// + public string Name { get; } /// - /// The unicode representation of this emote. + /// Gets the Unicode representation of this emote. /// - public string Name { get; } - public override string ToString() => Name; /// - /// Creates a unicode emoji. + /// Creates a Unicode emoji. /// - /// The pure UTF-8 encoding of an emoji + /// The pure UTF-8 encoding of an emoji. public Emoji(string unicode) { Name = unicode; } + /// + /// Determines whether the specified emoji is equal to the current emoji. + /// + /// The object to compare with the current object. public override bool Equals(object other) { if (other == null) return false; @@ -34,6 +38,7 @@ return string.Equals(Name, otherEmoji.Name); } + /// public override int GetHashCode() => Name.GetHashCode(); } } diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs index e3a228c83..32d49fede 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -1,26 +1,30 @@ -using System; +using System; +using System.Diagnostics; using System.Globalization; namespace Discord { /// - /// A custom image-based emote + /// A custom image-based emote. /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Emote : IEmote, ISnowflakeEntity { - /// - /// The display name (tooltip) of this emote - /// + /// public string Name { get; } - /// - /// The ID of this emote - /// + /// public ulong Id { get; } /// - /// Is this emote animated? + /// Gets whether this emote is animated. /// public bool Animated { get; } + /// + /// Gets the date when this emote is created. + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// + /// Gets the image URL of this emote. + /// public string Url => CDN.GetEmojiUrl(Id, Animated); internal Emote(ulong id, string name, bool animated) @@ -30,6 +34,10 @@ namespace Discord Animated = animated; } + /// + /// Determines whether the specified emote is equal to the current emote. + /// + /// The object to compare with the current object. public override bool Equals(object other) { if (other == null) return false; @@ -41,6 +49,7 @@ namespace Discord return string.Equals(Name, otherEmote.Name) && Id == otherEmote.Id; } + /// public override int GetHashCode() { unchecked @@ -49,18 +58,20 @@ namespace Discord } } - /// - /// Parse an Emote from its raw format - /// - /// The raw encoding of an emote; for example, <:dab:277855270321782784> - /// An emote + /// Parses an from its raw format. + /// The raw encoding of an emote; for example, <:dab:277855270321782784>. + /// An emote. + /// Invalid emote format. public static Emote Parse(string text) { if (TryParse(text, out Emote result)) return result; - throw new ArgumentException("Invalid emote format", nameof(text)); + throw new ArgumentException("Invalid emote format.", nameof(text)); } + /// Tries to parse an from its raw format. + /// The raw encoding of an emote; for example, <:dab:277855270321782784>. + /// An emote. public static bool TryParse(string text, out Emote result) { result = null; @@ -85,6 +96,9 @@ namespace Discord } private string DebuggerDisplay => $"{Name} ({Id})"; + /// + /// Returns the raw representation of the emote. + /// public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; } } diff --git a/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs b/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs index be24d306c..de457a0dc 100644 --- a/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs +++ b/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs @@ -1,10 +1,20 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Discord { + /// + /// Properties that are used to modify an with the specified changes. + /// + /// public class EmoteProperties { + /// + /// Gets or sets the name of the . + /// public Optional Name { get; set; } + /// + /// Gets or sets the roles that can access this . + /// public Optional> Roles { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs b/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs index 95b062bd2..f734c3648 100644 --- a/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs @@ -1,16 +1,25 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; namespace Discord { /// - /// An image-based emote that is attached to a guild + /// An image-based emote that is attached to a guild. /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class GuildEmote : Emote { + /// + /// Gets whether this emoji is managed. + /// public bool IsManaged { get; } + /// + /// Gets whether this emoji must be wrapped in colons. + /// public bool RequireColons { get; } + /// + /// Gets the roles this emoji is whitelisted to. + /// public IReadOnlyList RoleIds { get; } internal GuildEmote(ulong id, string name, bool animated, bool isManaged, bool requireColons, IReadOnlyList roleIds) : base(id, name, animated) @@ -21,6 +30,9 @@ namespace Discord } private string DebuggerDisplay => $"{Name} ({Id})"; + /// + /// Gets the raw representation of the emote. + /// public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; } } diff --git a/src/Discord.Net.Core/Entities/Emotes/IEmote.cs b/src/Discord.Net.Core/Entities/Emotes/IEmote.cs index fac61402a..e39601852 100644 --- a/src/Discord.Net.Core/Entities/Emotes/IEmote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/IEmote.cs @@ -1,12 +1,12 @@ -namespace Discord +namespace Discord { /// - /// A general container for any type of emote in a message. + /// Represents a general container for any type of emote in a message. /// public interface IEmote { /// - /// The display name or unicode representation of this emote + /// Gets the display name or Unicode representation of this emote. /// string Name { get; } } diff --git a/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs b/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs index a5cabc117..ffcd28cee 100644 --- a/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs +++ b/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs @@ -1,10 +1,17 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the default message notification behavior the guild uses. + /// public enum DefaultMessageNotifications { - /// By default, all messages will trigger notifications. + /// + /// By default, all messages will trigger notifications. + /// AllMessages = 0, - /// By default, only mentions will trigger notifications. + /// + /// By default, only mentions will trigger notifications. + /// MentionsOnly = 1 } } diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs index a2b2ec4fc..2977cd10c 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs @@ -1,20 +1,20 @@ -namespace Discord +namespace Discord { /// - /// Modify the widget of an IGuild with the specified parameters + /// Properties that are used to modify the widget of an with the specified changes. /// public class GuildEmbedProperties { /// - /// Should the widget be enabled? + /// Sets whether the widget should be enabled. /// public Optional Enabled { get; set; } /// - /// What channel should the invite place users in, if not null. + /// Sets the channel that the invite should place its users in, if not null. /// public Optional Channel { get; set; } /// - /// What channel should the invite place users in, if not null. + /// Sets the channel the invite should place its users in, if not null. /// public Optional ChannelId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildIntegrationProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildIntegrationProperties.cs index f329e78e6..c8b5b3072 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildIntegrationProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildIntegrationProperties.cs @@ -1,9 +1,21 @@ -namespace Discord +namespace Discord { + /// + /// Properties used to modify an with the specified changes. + /// public class GuildIntegrationProperties { + /// + /// Gets or sets the behavior when an integration subscription lapses. + /// public Optional ExpireBehavior { get; set; } + /// + /// Gets or sets the period (in seconds) where the integration will ignore lapsed subscriptions. + /// public Optional ExpireGracePeriod { get; set; } + /// + /// Gets or sets whether emoticons should be synced for this integration. + /// public Optional EnableEmoticons { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs index 1b406ef7f..eccd852dd 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs @@ -1,78 +1,69 @@ -namespace Discord +namespace Discord { /// - /// Modify an IGuild with the specified changes + /// Properties that are used to modify an with the specified changes. /// - /// - /// - /// await Context.Guild.ModifyAsync(async x => - /// { - /// x.Name = "aaaaaah"; - /// x.RegionId = (await Context.Client.GetOptimalVoiceRegionAsync()).Id; - /// }); - /// - /// - /// + /// public class GuildProperties { - public Optional Username { get; set; } /// - /// The name of the Guild + /// Gets or sets the name of the guild. Must be within 100 characters. /// public Optional Name { get; set; } /// - /// The region for the Guild's voice connections + /// Gets or sets the region for the guild's voice connections. /// public Optional Region { get; set; } /// - /// The ID of the region for the Guild's voice connections + /// Gets or sets the ID of the region for the guild's voice connections. /// public Optional RegionId { get; set; } /// - /// What verification level new users need to achieve before speaking + /// Gets or sets the verification level new users need to achieve before speaking. /// public Optional VerificationLevel { get; set; } /// - /// The default message notification state for the guild + /// Gets or sets the default message notification state for the guild. /// public Optional DefaultMessageNotifications { get; set; } /// - /// How many seconds before a user is sent to AFK. This value MUST be one of: (60, 300, 900, 1800, 3600). + /// Gets or sets how many seconds before a user is sent to AFK. This value MUST be one of: (60, 300, 900, + /// 1800, 3600). /// public Optional AfkTimeout { get; set; } /// - /// The icon of the guild + /// Gets or sets the icon of the guild. /// public Optional Icon { get; set; } /// - /// The guild's splash image + /// Gets or sets the guild's splash image. /// /// - /// The guild must be partnered for this value to have any effect. + /// The guild must be partnered for this value to have any effect. /// public Optional Splash { get; set; } /// - /// The IVoiceChannel where AFK users should be sent. + /// Gets or sets the where AFK users should be sent. /// public Optional AfkChannel { get; set; } /// - /// The ID of the IVoiceChannel where AFK users should be sent. + /// Gets or sets the ID of the where AFK users should be sent. /// public Optional AfkChannelId { get; set; } /// - /// The ITextChannel where System messages should be sent. + /// Gets or sets the where system messages should be sent. /// public Optional SystemChannel { get; set; } /// - /// The ID of the ITextChannel where System messages should be sent. + /// Gets or sets the ID of the where system messages should be sent. /// public Optional SystemChannelId { get; set; } /// - /// The owner of this guild. + /// Gets or sets the owner of this guild. /// public Optional Owner { get; set; } /// - /// The ID of the owner of this guild. + /// Gets or sets the ID of the owner of this guild. /// public Optional OwnerId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IBan.cs b/src/Discord.Net.Core/Entities/Guilds/IBan.cs index 05ab0c00f..103cfcfd9 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IBan.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IBan.cs @@ -1,8 +1,23 @@ -namespace Discord +namespace Discord { + /// + /// Represents a generic ban object. + /// public interface IBan { + /// + /// Gets the banned user. + /// + /// + /// A generic object that was banned. + /// IUser User { get; } + /// + /// Gets the reason why the user is banned if specified. + /// + /// + /// A string containing the reason behind the ban; null if none is specified. + /// string Reason { get; } } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 4c1c274d0..8d6c090b5 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -5,156 +5,621 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic guild/server. + /// public interface IGuild : IDeletable, ISnowflakeEntity { - /// Gets the name of this guild. + /// + /// Gets the name of this guild. + /// + /// + /// A string containing the name of this guild. + /// string Name { get; } - /// Gets the amount of time (in seconds) a user must be inactive in a voice channel for until they are automatically moved to the AFK voice channel, if one is set. + /// + /// Gets the amount of time (in seconds) a user must be inactive in a voice channel for until they are + /// automatically moved to the AFK voice channel. + /// + /// + /// An representing the amount of time in seconds for a user to be marked as inactive + /// and moved into the AFK voice channel. + /// int AFKTimeout { get; } - /// Returns true if this guild is embeddable (e.g. widget) + /// + /// Determines if this guild is embeddable (i.e. can use widget). + /// + /// + /// true if this guild can be embedded via widgets; otherwise false. + /// bool IsEmbeddable { get; } - /// Gets the default message notifications for users who haven't explicitly set their notification settings. + /// + /// Gets the default message notifications for users who haven't explicitly set their notification settings. + /// DefaultMessageNotifications DefaultMessageNotifications { get; } - /// Gets the level of mfa requirements a user must fulfill before being allowed to perform administrative actions in this guild. + /// + /// Gets the level of Multi-Factor Authentication requirements a user must fulfill before being allowed to + /// perform administrative actions in this guild. + /// + /// + /// The level of MFA requirement. + /// MfaLevel MfaLevel { get; } - /// Gets the level of requirements a user must fulfill before being allowed to post messages in this guild. + /// + /// Gets the level of requirements a user must fulfill before being allowed to post messages in this guild. + /// + /// + /// The level of requirements. + /// VerificationLevel VerificationLevel { get; } - /// Returns the id of this guild's icon, or null if one is not set. + /// + /// Gets the ID of this guild's icon. + /// + /// + /// An identifier for the splash image; null if none is set. + /// string IconId { get; } - /// Returns the url to this guild's icon, or null if one is not set. + /// + /// Gets the URL of this guild's icon. + /// + /// + /// A URL pointing to the guild's icon; null if none is set. + /// string IconUrl { get; } - /// Returns the id of this guild's splash image, or null if one is not set. + /// + /// Gets the ID of this guild's splash image. + /// + /// + /// An identifier for the splash image; null if none is set. + /// string SplashId { get; } - /// Returns the url to this guild's splash image, or null if one is not set. + /// + /// Gets the URL of this guild's splash image. + /// + /// + /// A URL pointing to the guild's splash image; null if none is set. + /// string SplashUrl { get; } - /// Returns true if this guild is currently connected and ready to be used. Only applies to the WebSocket client. + /// + /// Determines if this guild is currently connected and ready to be used. + /// + /// + /// + /// This property only applies to a WebSocket-based client. + /// + /// This boolean is used to determine if the guild is currently connected to the WebSocket and is ready to be used/accessed. + /// + /// + /// true if this guild is currently connected and ready to be used; otherwise false. + /// bool Available { get; } - /// Gets the id of the AFK voice channel for this guild if set, or null if not. + /// + /// Gets the ID of the AFK voice channel for this guild. + /// + /// + /// An representing the snowflake identifier of the AFK voice channel; null if + /// none is set. + /// ulong? AFKChannelId { get; } - /// Gets the id of the the default channel for this guild. + /// + /// Gets the ID of the the default channel for this guild. + /// ulong DefaultChannelId { get; } - /// Gets the id of the embed channel for this guild if set, or null if not. + /// + /// Gets the ID of the embed channel set in the widget settings of this guild, or null if none is set. + /// ulong? EmbedChannelId { get; } - /// Gets the id of the channel where randomized welcome messages are sent, or null if not. + /// + /// Gets the ID of the channel where randomized welcome messages are sent, or null if none is set. + /// ulong? SystemChannelId { get; } - /// Gets the id of the user that created this guild. + /// + /// Gets the ID of the user that created this guild. + /// ulong OwnerId { get; } - /// Gets the id of the region hosting this guild's voice channels. + /// + /// Gets the ID of the region hosting this guild's voice channels. + /// string VoiceRegionId { get; } - /// Gets the IAudioClient currently associated with this guild. + /// + /// Gets the currently associated with this guild. + /// + /// + /// currently associated with this guild. + /// IAudioClient AudioClient { get; } - /// Gets the built-in role containing all users in this guild. + /// + /// Gets the built-in role containing all users in this guild. + /// + /// + /// Built-in role that represents an @everyone role in this guild. + /// IRole EveryoneRole { get; } - /// Gets a collection of all custom emojis for this guild. + /// + /// Gets a collection of all custom emotes for this guild. + /// + /// + /// A collection of all custom emotes for this guild. + /// IReadOnlyCollection Emotes { get; } - /// Gets a collection of all extra features added to this guild. + /// + /// Gets a collection of all extra features added to this guild. + /// + /// + /// A collection of enabled features in this guild. + /// IReadOnlyCollection Features { get; } - /// Gets a collection of all roles in this guild. + /// + /// Gets a collection of all roles in this guild. + /// + /// + /// A collection of roles found within this guild. + /// IReadOnlyCollection Roles { get; } - /// Modifies this guild. + /// + /// Modifies this guild. + /// + /// The delegate containing the properties to modify the guild with. + /// The options to be used when sending the request. + /// + /// An awaitable . + /// Task ModifyAsync(Action func, RequestOptions options = null); - /// Modifies this guild's embed. + /// + /// Modifies this guild's embed channel. + /// + /// The delegate containing the properties to modify the guild widget with. + /// The options to be used when sending the request. + /// + /// An awaitable . + /// Task ModifyEmbedAsync(Action func, RequestOptions options = null); - /// Bulk modifies the channels of this guild. + /// + /// Bulk-modifies the order of channels in this guild. + /// + /// The properties used to modify the channel positions with. + /// The options to be used when sending the request. + /// + /// An awaitable . + /// Task ReorderChannelsAsync(IEnumerable args, RequestOptions options = null); - /// Bulk modifies the roles of this guild. + /// + /// Bulk-modifies the order of roles in this guild. + /// + /// The properties used to modify the role positions with. + /// The options to be used when sending the request. + /// + /// An awaitable . + /// Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null); - /// Leaves this guild. If you are the owner, use Delete instead. + /// + /// Leaves this guild. + /// + /// + /// This method will make the currently logged-in user leave the guild. If the user is the owner, use + /// instead. + /// + /// The options to be used when sending the request. + /// + /// An awaitable . + /// Task LeaveAsync(RequestOptions options = null); - /// Gets a collection of all users banned on this guild. + /// + /// Gets a collection of all users banned on this guild. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of banned users with reasons. + /// Task> GetBansAsync(RequestOptions options = null); /// /// Gets a ban object for a banned user. /// /// The banned user. + /// The options to be used when sending the request. /// /// An awaitable containing the ban object, which contains the user information and the - /// reason for the ban; if the ban entry cannot be found. + /// reason for the ban; null if the ban entry cannot be found. /// Task GetBanAsync(IUser user, RequestOptions options = null); /// /// Gets a ban object for a banned user. /// /// The snowflake identifier for the banned user. + /// The options to be used when sending the request. /// /// An awaitable containing the ban object, which contains the user information and the - /// reason for the ban; if the ban entry cannot be found. + /// reason for the ban; null if the ban entry cannot be found. /// Task GetBanAsync(ulong userId, RequestOptions options = null); - /// Bans the provided user from this guild and optionally prunes their recent messages. - /// The number of days to remove messages from this user for - must be between [0, 7] + /// + /// Bans the user from this guild and optionally prunes their recent messages. + /// + /// The user to ban. + /// + /// The number of days to remove messages from this for - must be between [0, 7]. + /// + /// The reason of the ban to be written in the audit log. + /// The options to be used when sending the request. + /// is not between 0 to 7. + /// + /// An awaitable . + /// Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null); - /// Bans the provided user id from this guild and optionally prunes their recent messages. - /// The number of days to remove messages from this user for - must be between [0, 7] + /// + /// Bans the user from this guild and optionally prunes their recent messages. + /// + /// The snowflake ID of the user to ban. + /// + /// The number of days to remove messages from this user for - must be between [0, 7]. + /// + /// The reason of the ban to be written in the audit log. + /// The options to be used when sending the request. + /// is not between 0 to 7. + /// + /// An awaitable . + /// Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null); - /// Unbans the provided user if it is currently banned. + /// + /// Unbans the provided user if they are currently banned. + /// + /// The user to be unbanned. + /// The options to be used when sending the request. + /// + /// An awaitable . + /// Task RemoveBanAsync(IUser user, RequestOptions options = null); - /// Unbans the provided user id if it is currently banned. + /// + /// Unbans the provided user ID if it is currently banned. + /// + /// The snowflake ID of the user to be unbanned. + /// The options to be used when sending the request. + /// + /// An awaitable . + /// Task RemoveBanAsync(ulong userId, RequestOptions options = null); - /// Gets a collection of all channels in this guild. + /// + /// Gets a collection of all channels in this guild. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of generic channels found within this guild. + /// Task> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets the channel in this guild with the provided id, or null if not found. + /// + /// Gets the channel in this guild with the provided ID. + /// + /// The channel ID. + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the generic channel with the specified ID, or + /// null if none is found. + /// Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a collection of all text channels in this guild. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of text channels found within this guild. + /// Task> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a text channel in this guild with the provided ID, or null if not found. + /// + /// The text channel ID. + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the text channel with the specified ID, or + /// null if none is found. + /// Task GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a collection of all voice channels in this guild. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of voice channels found within this guild. + /// Task> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a collection of all category channels in this guild. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of category channels found within this guild. + /// Task> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets the voice channel in this guild with the provided ID. + /// + /// The text channel ID. + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the voice channel with the specified ID, or + /// null if none is found. + /// Task GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets the AFK voice channel in this guild. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the AFK voice channel set within this guild, or + /// null if none is set. + /// Task GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets the default system text channel in this guild with the provided ID. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the system channel within this guild, or + /// null if none is set. + /// Task GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets the top viewable text channel in this guild with the provided ID. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the first viewable text channel in this guild, or + /// null if none is found. + /// Task GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the embed channel set within the server's widget settings, or + /// null if none is set. + /// Task GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Creates a new text channel. + + /// + /// Creates a new text channel. + /// + /// The new name for the text channel. + /// The delegate containing the properties to be applied to the channel upon its creation. + /// The options to be used when sending the request. + /// + /// An awaitable containing the newly created text channel. + /// Task CreateTextChannelAsync(string name, Action func = null, RequestOptions options = null); - /// Creates a new voice channel. + /// + /// Creates a new voice channel. + /// + /// The new name for the voice channel. + /// The delegate containing the properties to be applied to the channel upon its creation. + /// The options to be used when sending the request. + /// + /// An awaitable containing the newly created voice channel. + /// Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null); - /// Creates a new channel category. + /// + /// Creates a new channel category. + /// + /// The new name for the category. + /// The options to be used when sending the request. + /// + /// An awaitable containing the newly created category channel. + /// Task CreateCategoryAsync(string name, RequestOptions options = null); Task> GetIntegrationsAsync(RequestOptions options = null); Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null); - /// Gets a collection of all invites to this guild. + /// + /// Gets a collection of all invites to this guild. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of invites found within this guild. + /// Task> GetInvitesAsync(RequestOptions options = null); - /// Gets the role in this guild with the provided id, or null if not found. + /// + /// Gets a role in this guild. + /// + /// The role ID. + /// + /// A role that matches the provided snowflake identifier; null if none is found. + /// IRole GetRole(ulong id); - /// Creates a new role. + /// + /// Creates a new role with the provided name. + /// + /// The new name for the role. + /// The guild permission that the role should possess. + /// The color of the role. + /// Whether the role is separated from others on the sidebar. + /// The options to be used when sending the request. + /// + /// An awaitable containing the newly crated role. + /// Task CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null); - /// Gets a collection of all users in this guild. - Task> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); //TODO: shouldnt this be paged? - /// Gets the user in this guild with the provided id, or null if not found. + /// + /// Gets a collection of all users in this guild. + /// + /// + /// This method retrieves all users found within this guild. + /// + /// This may return an incomplete list on the WebSocket implementation. + /// + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of users found within this guild. + /// + Task> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a user found in this guild. + /// + /// The user ID to search for. + /// The that determines whether the object should be fetched from + /// cache. + /// The options to be used when sending the request. + /// + /// An awaitable containing the guild user with the specified ID; null if none is + /// found. + /// Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets the current user for this guild. + /// + /// Gets the current user for this guild. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// An awaitable containing the currently logged-in user within this guild. + /// Task GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets the owner of this guild. + /// + /// Gets the owner of this guild. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// An awaitable containing the owner of this guild. + /// Task GetOwnerAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Downloads all users for this guild if the current list is incomplete. + /// + /// Downloads all users for this guild if the current list is incomplete. + /// + /// + /// An awaitable . + /// Task DownloadUsersAsync(); - /// Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. + /// + /// Prunes inactive users. + /// + /// + /// This method removes all users that have not logged on in the provided number of days or, if + /// is true, returns the number of users that would be removed. + /// + /// The number of days required for the users to be kicked. + /// Whether this prune action is a simulation. + /// The options to be used when sending the request. + /// + /// An awaitable containing the number of users to be or has been removed from this + /// guild. + /// Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); - /// Gets the specified number of audit log entries for this guild. + /// + /// Gets the specified number of audit log entries for this guild. + /// + /// The number of audit log entries to fetch. + /// + /// The that determines whether the object should be fetched from cache. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of requested audit log entries. + /// Task> GetAuditLogAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Gets the webhook in this guild with the provided id, or null if not found. + /// + /// Gets a webhook found within this guild. + /// + /// The webhook ID. + /// The options to be used when sending the request. + /// + /// An awaitable containing the webhook with the specified ID; null if none is + /// found. + /// Task GetWebhookAsync(ulong id, RequestOptions options = null); - /// Gets a collection of all webhooks for this guild. + /// + /// Gets a collection of all webhook from this guild. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of webhooks found within the guild. + /// Task> GetWebhooksAsync(RequestOptions options = null); - - /// Gets a specific emote from this guild. + + /// + /// Gets a specific emote from this guild. + /// + /// The guild emote ID. + /// The options to be used when sending the request. + /// + /// An awaitable containing the emote found with the specified ID; null if none is + /// found. + /// Task GetEmoteAsync(ulong id, RequestOptions options = null); - /// Creates a new emote in this guild. + /// + /// Creates a new in this guild. + /// + /// The name of the guild emote. + /// The image of the new emote. + /// The roles to limit the emote usage to. + /// The options to be used when sending the request. + /// + /// An awaitable containing the created emote. + /// Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null); - /// Modifies an existing emote in this guild. + + /// + /// Modifies an existing in this guild. + /// + /// The emote to be modified. + /// The delegate containing the properties to modify the emote with. + /// The options to be used when sending the request. + /// + /// An awaitable containing the newly modified emote. + /// Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null); - /// Deletes an existing emote from this guild. + /// + /// Deletes an existing from this guild. + /// + /// The emote to delete. + /// The options to be used when sending the request. + /// + /// An awaitable . + /// Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs index b27db9377..b6685edf6 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs @@ -1,14 +1,22 @@ -namespace Discord +namespace Discord { public interface IUserGuild : IDeletable, ISnowflakeEntity { - /// Gets the name of this guild. + /// + /// Gets the name of this guild. + /// string Name { get; } - /// Returns the url to this guild's icon, or null if one is not set. + /// + /// Gets the icon URL associated with this guild, or null if one is not set. + /// string IconUrl { get; } - /// Returns true if the current user owns this guild. + /// + /// Returns true if the current user owns this guild. + /// bool IsOwner { get; } - /// Returns the current user's permissions for this guild. + /// + /// Returns the current user's permissions for this guild. + /// GuildPermissions Permissions { get; } } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs index 67bedbc3d..8516036f1 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs @@ -1,18 +1,33 @@ namespace Discord { + /// + /// Represents a region of which the user connects to when using voice. + /// public interface IVoiceRegion { - /// Gets the unique identifier for this voice region. + /// + /// Gets the unique identifier for this voice region. + /// string Id { get; } - /// Gets the name of this voice region. + /// + /// Gets the name of this voice region. + /// string Name { get; } - /// Returns true if this voice region is exclusive to VIP accounts. + /// + /// Returns true if this voice region is exclusive to VIP accounts. + /// bool IsVip { get; } - /// Returns true if this voice region is the closest to your machine. + /// + /// Returns true if this voice region is the closest to your machine. + /// bool IsOptimal { get; } - /// Returns true if this is a deprecated voice region (avoid switching to these). + /// + /// Returns true if this is a deprecated voice region (avoid switching to these). + /// bool IsDeprecated { get; } - /// Returns true if this is a custom voice region (used for events/etc) + /// + /// Returns true if this is a custom voice region (used for events/etc). + /// bool IsCustom { get; } } } diff --git a/src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs b/src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs index 1dfef17d5..57edac2b0 100644 --- a/src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs +++ b/src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs @@ -1,10 +1,17 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the guild's Multi-Factor Authentication (MFA) level requirement. + /// public enum MfaLevel { - /// Users have no additional MFA restriction on this guild. + /// + /// Users have no additional MFA restriction on this guild. + /// Disabled = 0, - /// Users must have MFA enabled on their account to perform administrative actions. + /// + /// Users must have MFA enabled on their account to perform administrative actions. + /// Enabled = 1 } } diff --git a/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs b/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs index 96595fb69..3da2fb147 100644 --- a/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs +++ b/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs @@ -1,8 +1,17 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the target of the permission. + /// public enum PermissionTarget { + /// + /// The target of the permission is a role. + /// Role, + /// + /// The target of the permission is a user. + /// User } } diff --git a/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs b/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs index ac51fe927..3a5ae0468 100644 --- a/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs +++ b/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs @@ -1,16 +1,29 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the verification level the guild uses. + /// public enum VerificationLevel { - /// Users have no additional restrictions on sending messages to this guild. + /// + /// Users have no additional restrictions on sending messages to this guild. + /// None = 0, - /// Users must have a verified email on their account. + /// + /// Users must have a verified email on their account. + /// Low = 1, - /// Users must fulfill the requirements of Low, and be registered on Discord for at least 5 minutes. + /// + /// Users must fulfill the requirements of Low and be registered on Discord for at least 5 minutes. + /// Medium = 2, - /// Users must fulfill the requirements of Medium, and be a member of this guild for at least 10 minutes. + /// + /// Users must fulfill the requirements of Medium and be a member of this guild for at least 10 minutes. + /// High = 3, - /// Users must fulfill the requirements of High, and must have a verified phone on their Discord account. + /// + /// Users must fulfill the requirements of High and must have a verified phone on their Discord account. + /// Extreme = 4 } } diff --git a/src/Discord.Net.Core/Entities/IApplication.cs b/src/Discord.Net.Core/Entities/IApplication.cs index 4fb1e4b91..eb9fddd89 100644 --- a/src/Discord.Net.Core/Entities/IApplication.cs +++ b/src/Discord.Net.Core/Entities/IApplication.cs @@ -1,13 +1,31 @@ -namespace Discord +namespace Discord { + /// + /// Represents a Discord application created via the developer portal. + /// public interface IApplication : ISnowflakeEntity { + /// + /// Gets the name of the application. + /// string Name { get; } + /// + /// Gets the description of the application. + /// string Description { get; } + /// + /// Gets the RPC origins of the application. + /// string[] RPCOrigins { get; } ulong Flags { get; } + /// + /// Gets the icon URL of the application. + /// string IconUrl { get; } + /// + /// Gets the partial user object containing info on the owner of the application. + /// IUser Owner { get; } } } diff --git a/src/Discord.Net.Core/Entities/IDeletable.cs b/src/Discord.Net.Core/Entities/IDeletable.cs index ba22a537a..9696eb838 100644 --- a/src/Discord.Net.Core/Entities/IDeletable.cs +++ b/src/Discord.Net.Core/Entities/IDeletable.cs @@ -1,10 +1,16 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Discord { + /// + /// Determines whether the object is deletable or not. + /// public interface IDeletable { - /// Deletes this object and all its children. + /// + /// Deletes this object and all its children. + /// + /// The options to be used when sending the request. Task DeleteAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/IEntity.cs b/src/Discord.Net.Core/Entities/IEntity.cs index 711fd0555..0cd692a41 100644 --- a/src/Discord.Net.Core/Entities/IEntity.cs +++ b/src/Discord.Net.Core/Entities/IEntity.cs @@ -8,7 +8,9 @@ namespace Discord ///// Gets the IDiscordClient that created this object. //IDiscordClient Discord { get; } - /// Gets the unique identifier for this object. + /// + /// Gets the unique identifier for this object. + /// TId Id { get; } } diff --git a/src/Discord.Net.Core/Entities/IMentionable.cs b/src/Discord.Net.Core/Entities/IMentionable.cs index abccc4480..225806723 100644 --- a/src/Discord.Net.Core/Entities/IMentionable.cs +++ b/src/Discord.Net.Core/Entities/IMentionable.cs @@ -1,8 +1,16 @@ -namespace Discord +namespace Discord { + /// + /// Determines whether the object is mentionable or not. + /// public interface IMentionable { - /// Returns a special string used to mention this object. + /// + /// Returns a special string used to mention this object. + /// + /// + /// A string that is recognized by Discord as a mention (e.g. <@168693960628371456>). + /// string Mention { get; } } } diff --git a/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs b/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs index 5b099b5ac..f5dd2ab07 100644 --- a/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs +++ b/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs @@ -2,8 +2,10 @@ using System; namespace Discord { + /// Represents a Discord snowflake entity. public interface ISnowflakeEntity : IEntity { + /// Gets when the snowflake is created. DateTimeOffset CreatedAt { get; } } } diff --git a/src/Discord.Net.Core/Entities/IUpdateable.cs b/src/Discord.Net.Core/Entities/IUpdateable.cs index b0f51aee7..3ae4613f5 100644 --- a/src/Discord.Net.Core/Entities/IUpdateable.cs +++ b/src/Discord.Net.Core/Entities/IUpdateable.cs @@ -1,10 +1,15 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Discord { + /// + /// Defines whether the object is updateable or not. + /// public interface IUpdateable { - /// Updates this object's properties with its current state. + /// + /// Updates this object's properties with its current state. + /// Task UpdateAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Image.cs b/src/Discord.Net.Core/Entities/Image.cs index 3b946ce80..5c775f9f4 100644 --- a/src/Discord.Net.Core/Entities/Image.cs +++ b/src/Discord.Net.Core/Entities/Image.cs @@ -2,27 +2,54 @@ using System.IO; namespace Discord { /// - /// An image that will be uploaded to Discord. + /// An image that will be uploaded to Discord. /// public struct Image { + /// + /// Gets the stream to be uploaded to Discord. + /// public Stream Stream { get; } /// - /// Create the image with a Stream. + /// Create the image with a . /// - /// This must be some type of stream with the contents of a file in it. + /// + /// The to create the image with. Note that this must be some type of stream + /// with the contents of a file in it. + /// public Image(Stream stream) { Stream = stream; } /// - /// Create the image from a file path. + /// Create the image from a file path. /// /// - /// This file path is NOT validated, and is passed directly into a + /// This file path is NOT validated and is passed directly into a + /// . /// /// The path to the file. + /// + /// is a zero-length string, contains only white space, or contains one or more invalid + /// characters as defined by . + /// + /// is null. + /// + /// 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. + /// + /// is in an invalid format. + /// + /// The specified is invalid, (for example, it is on an unmapped drive). + /// + /// + /// specified a directory.-or- The caller does not have the required permission. + /// + /// The file specified in was not found. + /// + /// An I/O error occurred while opening the file. public Image(string path) { Stream = File.OpenRead(path); diff --git a/src/Discord.Net.Core/Entities/ImageFormat.cs b/src/Discord.Net.Core/Entities/ImageFormat.cs index 302da79c8..9c04328f4 100644 --- a/src/Discord.Net.Core/Entities/ImageFormat.cs +++ b/src/Discord.Net.Core/Entities/ImageFormat.cs @@ -1,11 +1,29 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the type of format the image should return in. + /// public enum ImageFormat { + /// + /// Use automatically detected format. + /// Auto, + /// + /// Use Google's WebP image format. + /// WebP, + /// + /// Use PNG. + /// Png, + /// + /// Use JPEG. + /// Jpeg, + /// + /// Use GIF. + /// Gif, } } diff --git a/src/Discord.Net.Core/Entities/Invites/IInvite.cs b/src/Discord.Net.Core/Entities/Invites/IInvite.cs index 0ebb65679..0902d437c 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInvite.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInvite.cs @@ -1,30 +1,83 @@ -using System.Threading.Tasks; - namespace Discord { + /// + /// Represents a generic invite object. + /// public interface IInvite : IEntity, IDeletable { - /// Gets the unique identifier for this invite. + /// + /// Gets the unique identifier for this invite. + /// + /// + /// A string containing the invite code (e.g. FTqNnyS). + /// string Code { get; } - /// Gets the url used to accept this invite, using Code. + /// + /// Gets the URL used to accept this invite using . + /// + /// + /// A string containing the full invite URL (e.g. https://discord.gg/FTqNnyS). + /// string Url { get; } - /// Gets the channel this invite is linked to. + /// + /// Gets the channel this invite is linked to. + /// + /// + /// A generic channel that the invite points to. + /// IChannel Channel { get; } - /// Gets the id of the channel this invite is linked to. + /// + /// Gets the ID of the channel this invite is linked to. + /// + /// + /// An representing the channel snowflake identifier that the invite points to. + /// ulong ChannelId { get; } - /// Gets the name of the channel this invite is linked to. + /// + /// Gets the name of the channel this invite is linked to. + /// + /// + /// A string containing the name of the channel that the invite points to. + /// string ChannelName { get; } - /// Gets the guild this invite is linked to. + /// + /// Gets the guild this invite is linked to. + /// + /// + /// A generic representing the guild that the invite points to. + /// IGuild Guild { get; } - /// Gets the id of the guild this invite is linked to. + /// + /// Gets the ID of the guild this invite is linked to. + /// + /// + /// An representing the guild snowflake identifier that the invite points to. + /// ulong GuildId { get; } - /// Gets the name of the guild this invite is linked to. + /// + /// Gets the name of the guild this invite is linked to. + /// + /// + /// A string containing the name of the guild that the invite points to. + /// string GuildName { get; } - /// Gets the approximated count of online members in the guild. + /// + /// Gets the approximated count of online members in the guild. + /// + /// + /// An representing the approximated online member count of the guild that the + /// invite points to; null if one cannot be obtained. + /// int? PresenceCount { get; } - /// Gets the approximated count of total members in the guild. + /// + /// Gets the approximated count of total members in the guild. + /// + /// + /// An representing the approximated total member count of the guild that the + /// invite points to; null if one cannot be obtained. + /// int? MemberCount { get; } } } diff --git a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs index 1136e1678..c3c4c857d 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs @@ -1,22 +1,64 @@ -using System; +using System; namespace Discord { + /// + /// Represents additional information regarding the generic invite object. + /// public interface IInviteMetadata : IInvite { - /// Gets the user that created this invite. + /// + /// Gets the user that created this invite. + /// + /// + /// A generic that created this invite. + /// IUser Inviter { get; } - /// Returns true if this invite was revoked. + /// + /// Determines whether the invite has been revoked. + /// + /// + /// true if this invite was revoked; otherwise false. + /// bool IsRevoked { get; } - /// Returns true if users accepting this invite will be removed from the guild when they log off. + /// + /// Determines whether the invite is a temporary one (i.e. whether the invite will be removed from the guild + /// when the user logs off). + /// + /// + /// true if users accepting this invite will be removed from the guild when they log off; otherwise + /// false. + /// bool IsTemporary { get; } - /// Gets the time (in seconds) until the invite expires, or null if it never expires. + /// + /// Gets the time (in seconds) until the invite expires. + /// + /// + /// An representing the time in seconds until this invite expires; null if this + /// invite never expires. + /// int? MaxAge { get; } - /// Gets the max amount of times this invite may be used, or null if there is no limit. + /// + /// Gets the max number of uses this invite may have. + /// + /// + /// An representing the number of uses this invite may be accepted until it is removed + /// from the guild; null if none is set. + /// int? MaxUses { get; } - /// Gets the amount of times this invite has been used. + /// + /// Gets the number of times this invite has been used. + /// + /// + /// An representing the number of times this invite has been used. + /// int Uses { get; } - /// Gets when this invite was created. + /// + /// Gets when this invite was created. + /// + /// + /// A representing the time of which the invite was first created. + /// DateTimeOffset CreatedAt { get; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Messages/Embed.cs b/src/Discord.Net.Core/Entities/Messages/Embed.cs index dc62066eb..b10473c00 100644 --- a/src/Discord.Net.Core/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Core/Entities/Messages/Embed.cs @@ -5,22 +5,38 @@ using System.Linq; namespace Discord { + /// + /// Represents an embed object seen in an . + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Embed : IEmbed { + /// public EmbedType Type { get; } + /// public string Description { get; internal set; } + /// public string Url { get; internal set; } + /// public string Title { get; internal set; } + /// public DateTimeOffset? Timestamp { get; internal set; } + /// public Color? Color { get; internal set; } + /// public EmbedImage? Image { get; internal set; } + /// public EmbedVideo? Video { get; internal set; } + /// public EmbedAuthor? Author { get; internal set; } + /// public EmbedFooter? Footer { get; internal set; } + /// public EmbedProvider? Provider { get; internal set; } + /// public EmbedThumbnail? Thumbnail { get; internal set; } + /// public ImmutableArray Fields { get; internal set; } internal Embed(EmbedType type) @@ -57,6 +73,9 @@ namespace Discord Fields = fields; } + /// + /// Gets the total length of all embed properties. + /// public int Length { get @@ -70,6 +89,9 @@ namespace Discord } } + /// + /// Gets the title of the embed. + /// public override string ToString() => Title; private string DebuggerDisplay => $"{Title} ({Type})"; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs index c59473704..e596c0707 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs @@ -1,14 +1,28 @@ -using System; using System.Diagnostics; namespace Discord { + /// + /// A author field of an . + /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedAuthor { + /// + /// Gets the name of the author field. + /// public string Name { get; internal set; } + /// + /// Gets the URL of the author field. + /// public string Url { get; internal set; } + /// + /// Gets the icon URL of the author field. + /// public string IconUrl { get; internal set; } + /// + /// Gets the proxified icon URL of the author field. + /// public string ProxyIconUrl { get; internal set; } internal EmbedAuthor(string name, string url, string iconUrl, string proxyIconUrl) @@ -20,6 +34,12 @@ namespace Discord } private string DebuggerDisplay => $"{Name} ({Url})"; + /// + /// Gets the name of the author field. + /// + /// + /// + /// public override string ToString() => Name; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 04f4f6884..034d7eb73 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -5,6 +5,9 @@ using System.Linq; namespace Discord { + /// + /// Represents a builder class for creating a . + /// public class EmbedBuilder { private string _title; @@ -14,16 +17,33 @@ namespace Discord private EmbedThumbnail? _thumbnail; private List _fields; + /// + /// Returns the maximum number of fields allowed by Discord. + /// public const int MaxFieldCount = 25; + /// + /// Returns the maximum length of title allowed by Discord. + /// public const int MaxTitleLength = 256; + /// + /// Returns the maximum length of description allowed by Discord. + /// public const int MaxDescriptionLength = 2048; + /// + /// Returns the maximum length of total characters allowed by Discord. + /// public const int MaxEmbedLength = 6000; + /// Initializes a new class. public EmbedBuilder() { Fields = new List(); } + /// Gets or sets the title of an . + /// Title length exceeds . + /// + /// The title of the embed. public string Title { get => _title; @@ -33,6 +53,10 @@ namespace Discord _title = value; } } + + /// Gets or sets the description of an . + /// Description length exceeds . + /// The description of the embed. public string Description { get => _description; @@ -43,49 +67,96 @@ namespace Discord } } + /// Gets or sets the URL of an . + /// Url is not a well-formed . + /// The URL of the embed. public string Url { get => _url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI.", nameof(Url)); _url = value; } } + /// Gets or sets the thumbnail URL of an . + /// Url is not a well-formed . + /// The thumbnail URL of the embed. public string ThumbnailUrl { get => _thumbnail?.Url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI.", nameof(ThumbnailUrl)); _thumbnail = new EmbedThumbnail(value, null, null, null); } } + /// Gets or sets the image URL of an . + /// Url is not a well-formed . + /// The image URL of the embed. public string ImageUrl { get => _image?.Url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI.", nameof(ImageUrl)); _image = new EmbedImage(value, null, null, null); } } + + /// Gets or sets the list of of an . + /// An embed builder's fields collection is set to + /// null. + /// Description length exceeds . + /// + /// The list of existing . public List Fields { get => _fields; set { - if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields)); + if (value == null) throw new ArgumentNullException(nameof(Fields), "Cannot set an embed builder's fields collection to null."); if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields)); _fields = value; } } + /// + /// Gets or sets the timestamp of an . + /// + /// + /// The timestamp of the embed, or null if none is set. + /// public DateTimeOffset? Timestamp { get; set; } + /// + /// Gets or sets the sidebar color of an . + /// + /// + /// The color of the embed, or null if none is set. + /// public Color? Color { get; set; } + /// + /// Gets or sets the of an . + /// + /// + /// The author field builder of the embed, or null if none is set. + /// public EmbedAuthorBuilder Author { get; set; } + /// + /// Gets or sets the of an . + /// + /// + /// The footer field builder of the embed, or null if none is set. + /// public EmbedFooterBuilder Footer { get; set; } + /// + /// Gets the total length of all embed properties. + /// + /// + /// The combined length of , , , + /// , , and . + /// public int Length { get @@ -100,52 +171,121 @@ namespace Discord } } + /// + /// Sets the title of an . + /// + /// The title to be set. + /// + /// The current builder. + /// public EmbedBuilder WithTitle(string title) { Title = title; return this; } + /// + /// Sets the description of an . + /// + /// The description to be set. + /// + /// The current builder. + /// public EmbedBuilder WithDescription(string description) { Description = description; return this; } + /// + /// Sets the URL of an . + /// + /// The URL to be set. + /// + /// The current builder. + /// public EmbedBuilder WithUrl(string url) { Url = url; return this; } + /// + /// Sets the thumbnail URL of an . + /// + /// The thumbnail URL to be set. + /// + /// The current builder. + /// public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) { ThumbnailUrl = thumbnailUrl; return this; } + /// + /// Sets the image URL of an . + /// + /// The image URL to be set. + /// + /// The current builder. + /// public EmbedBuilder WithImageUrl(string imageUrl) { ImageUrl = imageUrl; return this; } + /// + /// Sets the timestamp of an to the current time. + /// + /// + /// The current builder. + /// public EmbedBuilder WithCurrentTimestamp() { Timestamp = DateTimeOffset.UtcNow; return this; } + /// + /// Sets the timestamp of an . + /// + /// The timestamp to be set. + /// + /// The current builder. + /// public EmbedBuilder WithTimestamp(DateTimeOffset dateTimeOffset) { Timestamp = dateTimeOffset; return this; } + /// + /// Sets the sidebar color of an . + /// + /// The color to be set. + /// + /// The current builder. + /// public EmbedBuilder WithColor(Color color) { Color = color; return this; } + /// + /// Sets the of an . + /// + /// The author builder class containing the author field properties. + /// + /// The current builder. + /// public EmbedBuilder WithAuthor(EmbedAuthorBuilder author) { Author = author; return this; } + /// + /// Sets the author field of an with the provided properties. + /// + /// The delegate containing the author field properties. + /// + /// The current builder. + /// public EmbedBuilder WithAuthor(Action action) { var author = new EmbedAuthorBuilder(); @@ -153,6 +293,15 @@ namespace Discord Author = author; return this; } + /// + /// Sets the author field of an with the provided name, icon URL, and URL. + /// + /// The title of the author field. + /// The icon URL of the author field. + /// The URL of the author field. + /// + /// The current builder. + /// public EmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null) { var author = new EmbedAuthorBuilder @@ -164,11 +313,25 @@ namespace Discord Author = author; return this; } + /// + /// Sets the of an . + /// + /// The footer builder class containing the footer field properties. + /// + /// The current builder. + /// public EmbedBuilder WithFooter(EmbedFooterBuilder footer) { Footer = footer; return this; } + /// + /// Sets the footer field of an with the provided properties. + /// + /// The delegate containing the footer field properties. + /// + /// The current builder. + /// public EmbedBuilder WithFooter(Action action) { var footer = new EmbedFooterBuilder(); @@ -176,6 +339,14 @@ namespace Discord Footer = footer; return this; } + /// + /// Sets the footer field of an with the provided name, icon URL. + /// + /// The title of the footer field. + /// The icon URL of the footer field. + /// + /// The current builder. + /// public EmbedBuilder WithFooter(string text, string iconUrl = null) { var footer = new EmbedFooterBuilder @@ -187,6 +358,15 @@ namespace Discord return this; } + /// + /// Adds an field with the provided name and value. + /// + /// The title of the field. + /// The value of the field. + /// Indicates whether the field is in-line or not. + /// + /// The current builder. + /// public EmbedBuilder AddField(string name, object value, bool inline = false) { var field = new EmbedFieldBuilder() @@ -196,6 +376,16 @@ namespace Discord AddField(field); return this; } + + /// + /// Adds a field with the provided to an + /// . + /// + /// The field builder class containing the field properties. + /// Field count exceeds . + /// + /// The current builder. + /// public EmbedBuilder AddField(EmbedFieldBuilder field) { if (Fields.Count >= MaxFieldCount) @@ -206,6 +396,13 @@ namespace Discord Fields.Add(field); return this; } + /// + /// Adds an field with the provided properties. + /// + /// The delegate containing the field properties. + /// + /// The current builder. + /// public EmbedBuilder AddField(Action action) { var field = new EmbedFieldBuilder(); @@ -214,10 +411,17 @@ namespace Discord return this; } + /// + /// Builds the into a Rich Embed ready to be sent. + /// + /// + /// The built embed object. + /// + /// Total embed length exceeds . public Embed Build() { if (Length > MaxEmbedLength) - throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}"); + throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}."); var fields = ImmutableArray.CreateBuilder(Fields.Count); for (int i = 0; i < Fields.Count; i++) @@ -227,64 +431,144 @@ namespace Discord } } + /// + /// Represents a builder class for an embed field. + /// public class EmbedFieldBuilder { private string _name; private string _value; + /// + /// Gets the maximum field length for name allowed by Discord. + /// public const int MaxFieldNameLength = 256; + /// + /// Gets the maximum field length for value allowed by Discord. + /// public const int MaxFieldValueLength = 1024; + /// + /// Gets or sets the field name. + /// + /// + /// Field name is null, empty or entirely whitespace. + /// - or - + /// Field name length exceeds . + /// + /// + /// The name of the field. + /// public string Name { get => _name; set { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name)); + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Field name must not be null, empty or entirely whitespace.", nameof(Name)); if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); _name = value; } } + /// + /// Gets or sets the field value. + /// + /// + /// Field value is null, empty or entirely whitespace. + /// - or - + /// Field value length exceeds . + /// + /// + /// The value of the field. + /// public object Value { get => _value; set { var stringValue = value?.ToString(); - if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); + if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException("Field value must not be null or empty.", nameof(Value)); if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); _value = stringValue; } } + /// + /// Determines whether the field should be in-line with each other. + /// public bool IsInline { get; set; } + /// + /// Sets the field name. + /// + /// The name to set the field name to. + /// + /// The current builder. + /// public EmbedFieldBuilder WithName(string name) { Name = name; return this; } + /// + /// Sets the field value. + /// + /// The value to set the field value to. + /// + /// The current builder. + /// public EmbedFieldBuilder WithValue(object value) { Value = value; return this; } + /// + /// Determines whether the field should be in-line with each other. + /// + /// + /// The current builder. + /// public EmbedFieldBuilder WithIsInline(bool isInline) { IsInline = isInline; return this; } + /// + /// Builds the field builder into a class. + /// + /// + /// The current builder. + /// + /// + /// or is null, empty or entirely whitespace. + /// - or - + /// or exceeds the maximum length allowed by Discord. + /// public EmbedField Build() => new EmbedField(Name, Value.ToString(), IsInline); } + /// + /// Represents a builder class for a author field. + /// public class EmbedAuthorBuilder { private string _name; private string _url; private string _iconUrl; + /// + /// Gets the maximum author name length allowed by Discord. + /// public const int MaxAuthorNameLength = 256; + /// + /// Gets or sets the author name. + /// + /// + /// Author name length is longer than . + /// + /// + /// The author name. + /// public string Name { get => _name; @@ -294,52 +578,115 @@ namespace Discord _name = value; } } + /// + /// Gets or sets the URL of the author field. + /// + /// Url is not a well-formed . + /// + /// The URL of the author field. + /// public string Url { get => _url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI.", nameof(Url)); _url = value; } } + /// + /// Gets or sets the icon URL of the author field. + /// + /// Url is not a well-formed . + /// + /// The icon URL of the author field. + /// public string IconUrl { get => _iconUrl; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI.", nameof(IconUrl)); _iconUrl = value; } } + /// + /// Sets the name of the author field. + /// + /// The name of the author field. + /// + /// The current builder. + /// public EmbedAuthorBuilder WithName(string name) { Name = name; return this; } + /// + /// Sets the URL of the author field. + /// + /// The URL of the author field. + /// + /// The current builder. + /// public EmbedAuthorBuilder WithUrl(string url) { Url = url; return this; } + /// + /// Sets the icon URL of the author field. + /// + /// The icon URL of the author field. + /// + /// The current builder. + /// public EmbedAuthorBuilder WithIconUrl(string iconUrl) { IconUrl = iconUrl; return this; } + /// + /// Builds the author field to be used. + /// + /// + /// Author name length is longer than . + /// - or - + /// is not a well-formed . + /// - or - + /// is not a well-formed . + /// + /// + /// The built author field. + /// public EmbedAuthor Build() => new EmbedAuthor(Name, Url, IconUrl, null); } + /// + /// Represents a builder class for an embed footer. + /// public class EmbedFooterBuilder { private string _text; private string _iconUrl; + /// + /// Gets the maximum footer length allowed by Discord. + /// public const int MaxFooterTextLength = 2048; + /// + /// Gets or sets the footer text. + /// + /// + /// Author name length is longer than . + /// + /// + /// The footer text. + /// public string Text { get => _text; @@ -349,27 +696,60 @@ namespace Discord _text = value; } } + /// + /// Gets or sets the icon URL of the footer field. + /// + /// Url is not a well-formed . + /// + /// The icon URL of the footer field. + /// public string IconUrl { get => _iconUrl; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI.", nameof(IconUrl)); _iconUrl = value; } } + /// + /// Sets the name of the footer field. + /// + /// The text of the footer field. + /// + /// The current builder. + /// public EmbedFooterBuilder WithText(string text) { Text = text; return this; } + /// + /// Sets the icon URL of the footer field. + /// + /// The icon URL of the footer field. + /// + /// The current builder. + /// public EmbedFooterBuilder WithIconUrl(string iconUrl) { IconUrl = iconUrl; return this; } + /// + /// Builds the footer field to be used. + /// + /// + /// + /// length is longer than . + /// - or - + /// is not a well-formed . + /// + /// + /// A built footer field. + /// public EmbedFooter Build() => new EmbedFooter(Text, IconUrl, null); } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedField.cs b/src/Discord.Net.Core/Entities/Messages/EmbedField.cs index f7c1f8348..5d8fd3c6b 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedField.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedField.cs @@ -1,12 +1,24 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Discord { + /// + /// A field for an . + /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedField { + /// + /// Gets the name of the field. + /// public string Name { get; internal set; } + /// + /// Gets the value of the field. + /// public string Value { get; internal set; } + /// + /// Determines whether the field should be in-line with each other. + /// public bool Inline { get; internal set; } internal EmbedField(string name, string value, bool inline) @@ -17,6 +29,9 @@ namespace Discord } private string DebuggerDisplay => $"{Name} ({Value}"; + /// + /// Gets the name of the field. + /// public override string ToString() => Name; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs index 29d85cd90..cd3839ac6 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs @@ -1,13 +1,16 @@ -using System; using System.Diagnostics; namespace Discord { + /// A footer field for an . [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedFooter { + /// Gets the text of the footer. public string Text { get; internal set; } + /// Gets the icon URL of the footer. public string IconUrl { get; internal set; } + /// Gets the proxified icon URL of the footer. public string ProxyUrl { get; internal set; } internal EmbedFooter(string text, string iconUrl, string proxyUrl) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index f21d42c0c..b71e77721 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -1,14 +1,18 @@ -using System; using System.Diagnostics; namespace Discord { + /// An image for an . [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedImage { + /// Gets the URL of the image. public string Url { get; } + /// Gets the proxified URL of the image. public string ProxyUrl { get; } + /// Gets the height of the image if any is set. public int? Height { get; } + /// Gets the width of the image if any is set. public int? Width { get; } internal EmbedImage(string url, string proxyUrl, int? height, int? width) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs index 24722b158..365f6b85f 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs @@ -1,12 +1,14 @@ -using System; using System.Diagnostics; namespace Discord { + /// A provider field for an . [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedProvider { + /// Gets the name of the provider. public string Name { get; } + /// Gets the URL of the provider. public string Url { get; } internal EmbedProvider(string name, string url) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 209a93e37..3d00cfb2e 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -1,14 +1,18 @@ -using System; using System.Diagnostics; namespace Discord { + /// A thumbnail featured in an . [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedThumbnail { + /// Gets the URL of the thumbnail. public string Url { get; } + /// Gets the proxified URL of the thumbnail. public string ProxyUrl { get; } + /// Gets the height of the thumbnail if any is set. public int? Height { get; } + /// Gets the width of the thumbnail if any is set. public int? Width { get; } internal EmbedThumbnail(string url, string proxyUrl, int? height, int? width) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedType.cs b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs index 5bb2653e2..978f45bc2 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedType.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs @@ -1,15 +1,45 @@ namespace Discord { + /// + /// Specifies the type of embed. + /// public enum EmbedType { + /// + /// An unknown embed type. + /// Unknown = -1, + /// + /// A rich embed type. + /// Rich, + /// + /// A link embed type. + /// Link, + /// + /// A video embed type. + /// Video, + /// + /// An image embed type. + /// Image, + /// + /// A GIFV embed type. + /// Gifv, + /// + /// An article embed type. + /// Article, + /// + /// A tweet embed type. + /// Tweet, + /// + /// A HTML embed type. + /// Html, } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index f00681d89..d02b2cdc3 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -1,13 +1,24 @@ -using System; using System.Diagnostics; namespace Discord { + /// + /// A video featured in an . + /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedVideo { + /// + /// Gets the URL of the video. + /// public string Url { get; } + /// + /// Gets the height of the video, or null if none. + /// public int? Height { get; } + /// + /// Gets the weight of the video, or null if none. + /// public int? Width { get; } internal EmbedVideo(string url, int? height, int? width) @@ -18,6 +29,9 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + /// + /// Gets the URL of the video. + /// + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/IAttachment.cs b/src/Discord.Net.Core/Entities/Messages/IAttachment.cs index 225e9cf2e..5d4d32cfa 100644 --- a/src/Discord.Net.Core/Entities/Messages/IAttachment.cs +++ b/src/Discord.Net.Core/Entities/Messages/IAttachment.cs @@ -1,14 +1,59 @@ -namespace Discord +namespace Discord { + /// + /// Represents a Discord attachment. + /// public interface IAttachment { + /// + /// Gets the ID of this attachment. + /// + /// + /// A snowflake ID associated with this attachment. + /// ulong Id { get; } + /// + /// Gets the filename of this attachment. + /// + /// + /// A string containing the full filename of this attachment (e.g. textFile.txt). + /// string Filename { get; } + /// + /// Gets the URL of this attachment. + /// + /// + /// A string containing the URL of this attachment. + /// string Url { get; } + /// + /// Gets a proxied URL of this attachment. + /// + /// + /// A string containing the proxied URL of this attachment. + /// string ProxyUrl { get; } + /// + /// Gets the file size of this attachment. + /// + /// + /// The size of this attachment in bytes. + /// int Size { get; } + /// + /// Gets the height of this attachment. + /// + /// + /// The height of this attachment if it is a picture; otherwise null. + /// int? Height { get; } + /// + /// Gets the width of this attachment. + /// + /// + /// The width of this attachment if it is a picture; otherwise null. + /// int? Width { get; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index f390c4c28..4c1029a10 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -1,22 +1,104 @@ -using System; +using System; using System.Collections.Immutable; namespace Discord { + /// + /// Represents a Discord embed object. + /// public interface IEmbed { + /// + /// Gets the title URL of this embed. + /// + /// + /// A string containing the URL set in a title of the embed. + /// string Url { get; } + /// + /// Gets the title of this embed. + /// + /// + /// The title of the embed. + /// string Title { get; } + /// + /// Gets the description of this embed. + /// + /// + /// The description field of the embed. + /// string Description { get; } + /// + /// Gets the type of this embed. + /// + /// + /// The type of the embed. + /// EmbedType Type { get; } + /// + /// Gets the timestamp of this embed. + /// + /// + /// A based on the timestamp present at the bottom left of the embed, or + /// null if none is set. + /// DateTimeOffset? Timestamp { get; } + /// + /// Gets the color of this embed. + /// + /// + /// The color of the embed present on the side of the embed, or null if none is set. + /// Color? Color { get; } + /// + /// Gets the image of this embed. + /// + /// + /// The image of the embed, or null if none is set. + /// EmbedImage? Image { get; } + /// + /// Gets the video of this embed. + /// + /// + /// The video of the embed, or null if none is set. + /// EmbedVideo? Video { get; } + /// + /// Gets the author field of this embed. + /// + /// + /// The author field of the embed, or null if none is set. + /// EmbedAuthor? Author { get; } + /// + /// Gets the footer field of this embed. + /// + /// + /// The author field of the embed, or null if none is set. + /// EmbedFooter? Footer { get; } + /// + /// Gets the provider of this embed. + /// + /// + /// The source of the embed, or null if none is set. + /// EmbedProvider? Provider { get; } + /// + /// Gets the thumbnail featured in this embed. + /// + /// + /// The thumbnail featured in the embed, or null if none is set. + /// EmbedThumbnail? Thumbnail { get; } + /// + /// Gets the fields of the embed. + /// + /// + /// An array of the fields of the embed. + /// ImmutableArray Fields { get; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 4266f893a..edbe4f4b6 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -1,41 +1,95 @@ -using System; +using System; using System.Collections.Generic; namespace Discord { + /// + /// Represents a message object. + /// public interface IMessage : ISnowflakeEntity, IDeletable { - /// Gets the type of this system message. + /// + /// Gets the type of this system message. + /// MessageType Type { get; } - /// Gets the source of this message. + /// + /// Gets the source type of this message. + /// MessageSource Source { get; } - /// Returns true if this message was sent as a text-to-speech message. + /// + /// Returns true if this message was sent as a text-to-speech message. + /// bool IsTTS { get; } - /// Returns true if this message was added to its channel's pinned messages. + /// + /// Returns true if this message was added to its channel's pinned messages. + /// bool IsPinned { get; } - /// Returns the content for this message. + /// + /// Returns the content for this message. + /// string Content { get; } - /// Gets the time this message was sent. + /// + /// Gets the time this message was sent. + /// + /// + /// Time of when the message was sent. + /// DateTimeOffset Timestamp { get; } - /// Gets the time of this message's last edit, if any. + /// + /// Gets the time of this message's last edit. + /// + /// + /// Time of when the message was last edited; null when the message is never edited. + /// DateTimeOffset? EditedTimestamp { get; } - /// Gets the channel this message was sent to. + /// + /// Gets the source channel of the message. + /// IMessageChannel Channel { get; } - /// Gets the author of this message. + /// + /// Gets the author of this message. + /// IUser Author { get; } - /// Returns all attachments included in this message. + /// + /// Returns all attachments included in this message. + /// + /// + /// Collection of attachments. + /// IReadOnlyCollection Attachments { get; } - /// Returns all embeds included in this message. + /// + /// Returns all embeds included in this message. + /// + /// + /// Collection of embed objects. + /// IReadOnlyCollection Embeds { get; } - /// Returns all tags included in this message's content. + /// + /// Returns all tags included in this message's content. + /// IReadOnlyCollection Tags { get; } - /// Returns the ids of channels mentioned in this message. + /// + /// Returns the IDs of channels mentioned in this message. + /// + /// + /// Collection of channel IDs. + /// IReadOnlyCollection MentionedChannelIds { get; } - /// Returns the ids of roles mentioned in this message. + /// + /// Returns the IDs of roles mentioned in this message. + /// + /// + /// Collection of role IDs. + /// IReadOnlyCollection MentionedRoleIds { get; } - /// Returns the ids of users mentioned in this message. + /// + /// Returns the IDs of users mentioned in this message. + /// + /// + /// Collection of user IDs. + /// IReadOnlyCollection MentionedUserIds { get; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/IReaction.cs b/src/Discord.Net.Core/Entities/Messages/IReaction.cs index 37ead42ae..b7d7128c0 100644 --- a/src/Discord.Net.Core/Entities/Messages/IReaction.cs +++ b/src/Discord.Net.Core/Entities/Messages/IReaction.cs @@ -1,7 +1,13 @@ -namespace Discord +namespace Discord { + /// + /// Represents a generic reaction object. + /// public interface IReaction { + /// + /// The used in the reaction. + /// IEmote Emote { get; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs b/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs index 2dfaf8f2d..89cd17a35 100644 --- a/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs @@ -1,5 +1,8 @@ -namespace Discord +namespace Discord { + /// + /// Represents a generic message sent by the system. + /// public interface ISystemMessage : IMessage { } diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index 36ee725ff..bad4e36c7 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -4,28 +4,49 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic message sent by a user. + /// public interface IUserMessage : IMessage { - /// Modifies this message. + /// + /// Modifies this message. + /// Task ModifyAsync(Action func, RequestOptions options = null); - /// Adds this message to its channel's pinned messages. + /// + /// Adds this message to its channel's pinned messages. + /// Task PinAsync(RequestOptions options = null); - /// Removes this message from its channel's pinned messages. + /// + /// Removes this message from its channel's pinned messages. + /// Task UnpinAsync(RequestOptions options = null); - /// Returns all reactions included in this message. + /// + /// Returns all reactions included in this message. + /// IReadOnlyDictionary Reactions { get; } - /// Adds a reaction to this message. + /// + /// Adds a reaction to this message. + /// Task AddReactionAsync(IEmote emote, RequestOptions options = null); - /// Removes a reaction from message. + /// + /// Removes a reaction from message. + /// Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null); - /// Removes all reactions from this message. + /// + /// Removes all reactions from this message. + /// Task RemoveAllReactionsAsync(RequestOptions options = null); - /// Gets all users that reacted to a message with a given emote + /// + /// Gets all users that reacted to a message with a given emote. + /// IAsyncEnumerable> GetReactionUsersAsync(IEmote emoji, int limit, RequestOptions options = null); - /// Transforms this message's text into a human readable form by resolving its tags. + /// + /// Transforms this message's text into a human-readable form by resolving its tags. + /// string Resolve( TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, diff --git a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs index b3f3a9c89..2cc0eab8e 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs @@ -1,36 +1,23 @@ -namespace Discord +namespace Discord { /// - /// Modify a message with the specified parameters. + /// Properties that are used to modify an with the specified changes. /// /// - /// The content of a message can be cleared with String.Empty; if and only if an Embed is present. + /// The content of a message can be cleared with if and only if an is present. /// - /// - /// - /// var message = await ReplyAsync("abc"); - /// await message.ModifyAsync(x => - /// { - /// x.Content = ""; - /// x.Embed = new EmbedBuilder() - /// .WithColor(new Color(40, 40, 120)) - /// .WithAuthor(a => a.Name = "foxbot") - /// .WithTitle("Embed!") - /// .WithDescription("This is an embed."); - /// }); - /// - /// + /// public class MessageProperties { /// - /// The content of the message + /// Gets or sets the content of the message. /// /// - /// This must be less than 2000 characters. + /// This must be less than the constant defined by . /// public Optional Content { get; set; } /// - /// The embed the message should display + /// Gets or sets the embed the message should display. /// public Optional Embed { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Messages/MessageSource.cs b/src/Discord.Net.Core/Entities/Messages/MessageSource.cs index 1cb2f8b94..bd4f23727 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageSource.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageSource.cs @@ -1,10 +1,25 @@ namespace Discord { + /// + /// Specifies the source of the Discord message. + /// public enum MessageSource { + /// + /// The message is sent by the system. + /// System, + /// + /// The message is sent by a user. + /// User, + /// + /// The message is sent by a bot. + /// Bot, + /// + /// The message is sent by a webhook. + /// Webhook } } diff --git a/src/Discord.Net.Core/Entities/Messages/MessageType.cs b/src/Discord.Net.Core/Entities/Messages/MessageType.cs index 687e69e14..5056da8ea 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageType.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageType.cs @@ -1,13 +1,37 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the type of message. + /// public enum MessageType { + /// + /// The default message type. + /// Default = 0, + /// + /// The message when a recipient is added. + /// RecipientAdd = 1, + /// + /// The message when a recipient is removed. + /// RecipientRemove = 2, + /// + /// The message when a user is called. + /// Call = 3, + /// + /// The message when a channel name is changed. + /// ChannelNameChange = 4, + /// + /// The message when a channel icon is changed. + /// ChannelIconChange = 5, + /// + /// The message when another message is pinned. + /// ChannelPinnedMessage = 6 } } diff --git a/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs b/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs index 005276202..8f2678cd9 100644 --- a/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs +++ b/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs @@ -1,11 +1,18 @@ -namespace Discord +namespace Discord { + /// + /// A metadata containing reaction information. + /// public struct ReactionMetadata { - /// Gets the number of reactions + /// + /// Gets the number of reactions. + /// public int ReactionCount { get; internal set; } - /// Returns true if the current user has used this reaction + /// + /// Returns true if the current user has used this reaction. + /// public bool IsMe { get; internal set; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/TagHandling.cs b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs index 492f05879..eaadd6400 100644 --- a/src/Discord.Net.Core/Entities/Messages/TagHandling.cs +++ b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs @@ -1,13 +1,39 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the handling type the tag should use. + /// + /// + /// public enum TagHandling { - Ignore = 0, //<@53905483156684800> -> <@53905483156684800> - Remove, //<@53905483156684800> -> - Name, //<@53905483156684800> -> @Voltana - NameNoPrefix, //<@53905483156684800> -> Voltana - FullName, //<@53905483156684800> -> @Voltana#8252 - FullNameNoPrefix, //<@53905483156684800> -> Voltana#8252 - Sanitize //<@53905483156684800> -> <@53905483156684800> (w/ nbsp) + /// + /// Tag handling is ignored (e.g. <@53905483156684800> -> <@53905483156684800>). + /// + Ignore = 0, + /// + /// Removes the tag entirely. + /// + Remove, + /// + /// Resolves to username (e.g. <@53905483156684800> -> @Voltana). + /// + Name, + /// + /// Resolves to username without mention prefix (e.g. <@53905483156684800> -> Voltana). + /// + NameNoPrefix, + /// + /// Resolves to username with discriminator value. (e.g. <@53905483156684800> -> @Voltana#8252). + /// + FullName, + /// + /// Resolves to username with discriminator value without mention prefix. (e.g. <@53905483156684800> -> Voltana#8252). + /// + FullNameNoPrefix, + /// + /// Sanitizes the tag (e.g. <@53905483156684800> -> <@53905483156684800> (w/ nbsp)). + /// + Sanitize } } diff --git a/src/Discord.Net.Core/Entities/Messages/TagType.cs b/src/Discord.Net.Core/Entities/Messages/TagType.cs index 2d93bb3e3..177157251 100644 --- a/src/Discord.Net.Core/Entities/Messages/TagType.cs +++ b/src/Discord.Net.Core/Entities/Messages/TagType.cs @@ -1,12 +1,19 @@ -namespace Discord +namespace Discord { + /// Specifies the type of Discord tag. public enum TagType { + /// The object is an user mention. UserMention, + /// The object is a channel mention. ChannelMention, + /// The object is a role mention. RoleMention, + /// The object is an everyone mention. EveryoneMention, + /// The object is a here mention. HereMention, + /// The object is an emoji. Emoji } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index 740b6c30b..d683bd36b 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -1,38 +1,103 @@ -using System; +using System; namespace Discord { - [FlagsAttribute] + /// Defines the available permissions for a channel. + [Flags] public enum ChannelPermission : ulong { // General - CreateInstantInvite = 0x00_00_00_01, - ManageChannels = 0x00_00_00_10, + /// + /// Allows creation of instant invites. + /// + CreateInstantInvite = 0x00_00_00_01, + /// + /// Allows management and editing of channels. + /// + ManageChannels = 0x00_00_00_10, // Text - AddReactions = 0x00_00_00_40, + /// + /// Allows for the addition of reactions to messages. + /// + AddReactions = 0x00_00_00_40, + /// + /// Allows for reading of message. + /// [Obsolete("Use ViewChannel instead.")] - ReadMessages = ViewChannel, - ViewChannel = 0x00_00_04_00, - SendMessages = 0x00_00_08_00, - SendTTSMessages = 0x00_00_10_00, - ManageMessages = 0x00_00_20_00, - EmbedLinks = 0x00_00_40_00, + ReadMessages = ViewChannel, + /// + /// Allows guild members to view a channel, which includes reading messages in text channels. + /// + ViewChannel = 0x00_00_04_00, + /// + /// Allows for sending messages in a channel. + /// + SendMessages = 0x00_00_08_00, + /// + /// Allows for sending of text-to-speech messages. + /// + SendTTSMessages = 0x00_00_10_00, + /// + /// Allows for deletion of other users messages. + /// + ManageMessages = 0x00_00_20_00, + /// + /// Allows links sent by users with this permission will be auto-embedded. + /// + EmbedLinks = 0x00_00_40_00, + /// + /// Allows for uploading images and files. + /// AttachFiles = 0x00_00_80_00, - ReadMessageHistory = 0x00_01_00_00, + /// + /// Allows for reading of message history. + /// + ReadMessageHistory = 0x00_01_00_00, + /// + /// Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all + /// online users in a channel. + /// MentionEveryone = 0x00_02_00_00, + /// + /// Allows the usage of custom emojis from other servers. + /// UseExternalEmojis = 0x00_04_00_00, // Voice - Connect = 0x00_10_00_00, - Speak = 0x00_20_00_00, - MuteMembers = 0x00_40_00_00, - DeafenMembers = 0x00_80_00_00, + /// + /// Allows for joining of a voice channel. + /// + Connect = 0x00_10_00_00, + /// + /// Allows for speaking in a voice channel. + /// + Speak = 0x00_20_00_00, + /// + /// Allows for muting members in a voice channel. + /// + MuteMembers = 0x00_40_00_00, + /// + /// Allows for deafening of members in a voice channel. + /// + DeafenMembers = 0x00_80_00_00, + /// + /// Allows for moving of members between voice channels. + /// MoveMembers = 0x01_00_00_00, - UseVAD = 0x02_00_00_00, + /// + /// Allows for using voice-activity-detection in a voice channel. + /// + UseVAD = 0x02_00_00_00, // More General + /// + /// Allows management and editing of roles. + /// ManageRoles = 0x10_00_00_00, - ManageWebhooks = 0x20_00_00_00, + /// + /// Allows management and editing of webhooks. + /// + ManageWebhooks = 0x20_00_00_00, } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index fa2dfb576..753be6d51 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -7,19 +7,20 @@ namespace Discord [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct ChannelPermissions { - /// Gets a blank ChannelPermissions that grants no permissions. + /// Gets a blank that grants no permissions. public static readonly ChannelPermissions None = new ChannelPermissions(); - /// Gets a ChannelPermissions that grants all permissions for text channels. + /// Gets a that grants all permissions for text channels. public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); - /// Gets a ChannelPermissions that grants all permissions for voice channels. + /// Gets a that grants all permissions for voice channels. public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000010000_010001); - /// Gets a ChannelPermissions that grants all permissions for category channels. + /// Gets a that grants all permissions for category channels. public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); - /// Gets a ChannelPermissions that grants all permissions for direct message channels. + /// Gets a that grants all permissions for direct message channels. public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000); - /// Gets a ChannelPermissions that grants all permissions for group channels. + /// Gets a that grants all permissions for group channels. public static readonly ChannelPermissions Group = new ChannelPermissions(0b00000_1000110_0001101100000_000000); - /// Gets a ChannelPermissions that grants all permissions for a given channelType. + /// Gets a that grants all permissions for a given channel type. + /// Unknown channel type. public static ChannelPermissions All(IChannel channel) { switch (channel) @@ -29,62 +30,62 @@ namespace Discord case ICategoryChannel _: return Category; case IDMChannel _: return DM; case IGroupChannel _: return Group; - default: throw new ArgumentException("Unknown channel type", nameof(channel)); + default: throw new ArgumentException("Unknown channel type.", nameof(channel)); } } - /// Gets a packed value representing all the permissions in this ChannelPermissions. + /// Gets a packed value representing all the permissions in this . public ulong RawValue { get; } - /// If True, a user may create invites. + /// If true, a user may create invites. public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); - /// If True, a user may create, delete and modify this channel. + /// If true, a user may create, delete and modify this channel. public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannels); - /// If true, a user may add reactions. + /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); - /// If True, a user may join channels. + /// If true, a user may join channels. [Obsolete("Use ViewChannel instead.")] public bool ReadMessages => ViewChannel; - /// If True, a user may view channels. + /// If true, a user may view channels. public bool ViewChannel => Permissions.GetValue(RawValue, ChannelPermission.ViewChannel); - /// If True, a user may send messages. + /// If true, a user may send messages. public bool SendMessages => Permissions.GetValue(RawValue, ChannelPermission.SendMessages); - /// If True, a user may send text-to-speech messages. + /// If true, a user may send text-to-speech messages. public bool SendTTSMessages => Permissions.GetValue(RawValue, ChannelPermission.SendTTSMessages); - /// If True, a user may delete messages. + /// If true, a user may delete messages. public bool ManageMessages => Permissions.GetValue(RawValue, ChannelPermission.ManageMessages); - /// If True, Discord will auto-embed links sent by this user. + /// If true, Discord will auto-embed links sent by this user. public bool EmbedLinks => Permissions.GetValue(RawValue, ChannelPermission.EmbedLinks); - /// If True, a user may send files. + /// If true, a user may send files. public bool AttachFiles => Permissions.GetValue(RawValue, ChannelPermission.AttachFiles); - /// If True, a user may read previous messages. + /// If true, a user may read previous messages. public bool ReadMessageHistory => Permissions.GetValue(RawValue, ChannelPermission.ReadMessageHistory); - /// If True, a user may mention @everyone. + /// If true, a user may mention @everyone. public bool MentionEveryone => Permissions.GetValue(RawValue, ChannelPermission.MentionEveryone); - /// If True, a user may use custom emoji from other guilds. + /// If true, a user may use custom emoji from other guilds. public bool UseExternalEmojis => Permissions.GetValue(RawValue, ChannelPermission.UseExternalEmojis); - /// If True, a user may connect to a voice channel. + /// If true, a user may connect to a voice channel. public bool Connect => Permissions.GetValue(RawValue, ChannelPermission.Connect); - /// If True, a user may speak in a voice channel. + /// If true, a user may speak in a voice channel. public bool Speak => Permissions.GetValue(RawValue, ChannelPermission.Speak); - /// If True, a user may mute users. + /// If true, a user may mute users. public bool MuteMembers => Permissions.GetValue(RawValue, ChannelPermission.MuteMembers); - /// If True, a user may deafen users. + /// If true, a user may deafen users. public bool DeafenMembers => Permissions.GetValue(RawValue, ChannelPermission.DeafenMembers); - /// If True, a user may move other users between voice channels. + /// If true, a user may move other users between voice channels. public bool MoveMembers => Permissions.GetValue(RawValue, ChannelPermission.MoveMembers); - /// If True, a user may use voice-activity-detection rather than push-to-talk. + /// If true, a user may use voice-activity-detection rather than push-to-talk. public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); - /// If True, a user may adjust role permissions. This also implictly grants all other permissions. + /// If true, a user may adjust role permissions. This also implictly grants all other permissions. public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles); - /// If True, a user may edit the webhooks for this channel. + /// If true, a user may edit the webhooks for this channel. public bool ManageWebhooks => Permissions.GetValue(RawValue, ChannelPermission.ManageWebhooks); - /// Creates a new ChannelPermissions with the provided packed value. + /// Creates a new with the provided packed value. public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } private ChannelPermissions(ulong initialValue, @@ -135,7 +136,7 @@ namespace Discord RawValue = value; } - /// Creates a new ChannelPermissions with the provided permissions. + /// Creates a new with the provided permissions. public ChannelPermissions( bool createInstantInvite = false, bool manageChannel = false, @@ -162,7 +163,7 @@ namespace Discord speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { } - /// Creates a new ChannelPermissions from this one, changing the provided non-null permissions. + /// Creates a new from this one, changing the provided non-null permissions. public ChannelPermissions Modify( bool? createInstantInvite = null, bool? manageChannel = null, diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index e90b4269e..f2724778f 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -2,45 +2,126 @@ using System; namespace Discord { - [FlagsAttribute] + /// Defines the available permissions for a channel. + [Flags] public enum GuildPermission : ulong { // General + /// + /// Allows creation of instant invites. + /// CreateInstantInvite = 0x00_00_00_01, - KickMembers = 0x00_00_00_02, - BanMembers = 0x00_00_00_04, - Administrator = 0x00_00_00_08, - ManageChannels = 0x00_00_00_10, - ManageGuild = 0x00_00_00_20, + /// + /// Allows kicking members. + /// + KickMembers = 0x00_00_00_02, + /// + /// Allows banning members. + /// + BanMembers = 0x00_00_00_04, + /// + /// Allows all permissions and bypasses channel permission overwrites. + /// + Administrator = 0x00_00_00_08, + /// + /// Allows management and editing of channels. + /// + ManageChannels = 0x00_00_00_10, + /// + /// Allows management and editing of the guild. + /// + ManageGuild = 0x00_00_00_20, // Text + /// + /// Allows for the addition of reactions to messages. + /// AddReactions = 0x00_00_00_40, + /// + /// Allows for viewing of audit logs. + /// ViewAuditLog = 0x00_00_00_80, [Obsolete("Use ViewChannel instead.")] ReadMessages = ViewChannel, ViewChannel = 0x00_00_04_00, SendMessages = 0x00_00_08_00, + /// + /// Allows for sending of text-to-speech messages. + /// SendTTSMessages = 0x00_00_10_00, - ManageMessages = 0x00_00_20_00, - EmbedLinks = 0x00_00_40_00, - AttachFiles = 0x00_00_80_00, - ReadMessageHistory = 0x00_01_00_00, - MentionEveryone = 0x00_02_00_00, - UseExternalEmojis = 0x00_04_00_00, + /// + /// Allows for deletion of other users messages. + /// + ManageMessages = 0x00_00_20_00, + /// + /// Allows links sent by users with this permission will be auto-embedded. + /// + EmbedLinks = 0x00_00_40_00, + /// + /// Allows for uploading images and files. + /// + AttachFiles = 0x00_00_80_00, + /// + /// Allows for reading of message history. + /// + ReadMessageHistory = 0x00_01_00_00, + /// + /// Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all + /// online users in a channel. + /// + MentionEveryone = 0x00_02_00_00, + /// + /// Allows the usage of custom emojis from other servers. + /// + UseExternalEmojis = 0x00_04_00_00, + // Voice - Connect = 0x00_10_00_00, - Speak = 0x00_20_00_00, - MuteMembers = 0x00_40_00_00, - DeafenMembers = 0x00_80_00_00, - MoveMembers = 0x01_00_00_00, - UseVAD = 0x02_00_00_00, + /// + /// Allows for joining of a voice channel. + /// + Connect = 0x00_10_00_00, + /// + /// Allows for speaking in a voice channel. + /// + Speak = 0x00_20_00_00, + /// + /// Allows for muting members in a voice channel. + /// + MuteMembers = 0x00_40_00_00, + /// + /// Allows for deafening of members in a voice channel. + /// + DeafenMembers = 0x00_80_00_00, + /// + /// Allows for moving of members between voice channels. + /// + MoveMembers = 0x01_00_00_00, + /// + /// Allows for using voice-activity-detection in a voice channel. + /// + UseVAD = 0x02_00_00_00, // General 2 - ChangeNickname = 0x04_00_00_00, + /// + /// Allows for modification of own nickname. + /// + ChangeNickname = 0x04_00_00_00, + /// + /// Allows for modification of other users nicknames. + /// ManageNicknames = 0x08_00_00_00, + /// + /// Allows management and editing of roles. + /// ManageRoles = 0x10_00_00_00, + /// + /// Allows management and editing of webhooks. + /// ManageWebhooks = 0x20_00_00_00, + /// + /// Allows management and editing of emojis. + /// ManageEmojis = 0x40_00_00_00 } } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 7704a62d6..9ad7d34b4 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -7,32 +7,32 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct GuildPermissions { - /// Gets a blank GuildPermissions that grants no permissions. + /// Gets a blank that grants no permissions. public static readonly GuildPermissions None = new GuildPermissions(); - /// Gets a GuildPermissions that grants all guild permissions for webhook users. + /// Gets a that grants all guild permissions for webhook users. public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); - /// Gets a GuildPermissions that grants all guild permissions. + /// Gets a that grants all guild permissions. public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_1111111110011_111111); - /// Gets a packed value representing all the permissions in this GuildPermissions. + /// Gets a packed value representing all the permissions in this . public ulong RawValue { get; } - /// If True, a user may create invites. + /// If true, a user may create invites. public bool CreateInstantInvite => Permissions.GetValue(RawValue, GuildPermission.CreateInstantInvite); - /// If True, a user may ban users from the guild. + /// If true, a user may ban users from the guild. public bool BanMembers => Permissions.GetValue(RawValue, GuildPermission.BanMembers); - /// If True, a user may kick users from the guild. + /// If true, a user may kick users from the guild. public bool KickMembers => Permissions.GetValue(RawValue, GuildPermission.KickMembers); - /// If True, a user is granted all permissions, and cannot have them revoked via channel permissions. + /// If true, a user is granted all permissions, and cannot have them revoked via channel permissions. public bool Administrator => Permissions.GetValue(RawValue, GuildPermission.Administrator); - /// If True, a user may create, delete and modify channels. + /// If true, a user may create, delete and modify channels. public bool ManageChannels => Permissions.GetValue(RawValue, GuildPermission.ManageChannels); - /// If True, a user may adjust guild properties. + /// If true, a user may adjust guild properties. public bool ManageGuild => Permissions.GetValue(RawValue, GuildPermission.ManageGuild); - /// If true, a user may add reactions. + /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); - /// If true, a user may view the audit log. + /// If true, a user may view the audit log. public bool ViewAuditLog => Permissions.GetValue(RawValue, GuildPermission.ViewAuditLog); /// If True, a user may join channels. @@ -42,46 +42,46 @@ namespace Discord public bool ViewChannel => Permissions.GetValue(RawValue, GuildPermission.ViewChannel); /// If True, a user may send messages. public bool SendMessages => Permissions.GetValue(RawValue, GuildPermission.SendMessages); - /// If True, a user may send text-to-speech messages. + /// If true, a user may send text-to-speech messages. public bool SendTTSMessages => Permissions.GetValue(RawValue, GuildPermission.SendTTSMessages); - /// If True, a user may delete messages. + /// If true, a user may delete messages. public bool ManageMessages => Permissions.GetValue(RawValue, GuildPermission.ManageMessages); - /// If True, Discord will auto-embed links sent by this user. + /// If true, Discord will auto-embed links sent by this user. public bool EmbedLinks => Permissions.GetValue(RawValue, GuildPermission.EmbedLinks); - /// If True, a user may send files. + /// If true, a user may send files. public bool AttachFiles => Permissions.GetValue(RawValue, GuildPermission.AttachFiles); - /// If True, a user may read previous messages. + /// If true, a user may read previous messages. public bool ReadMessageHistory => Permissions.GetValue(RawValue, GuildPermission.ReadMessageHistory); - /// If True, a user may mention @everyone. + /// If true, a user may mention @everyone. public bool MentionEveryone => Permissions.GetValue(RawValue, GuildPermission.MentionEveryone); - /// If True, a user may use custom emoji from other guilds. + /// If true, a user may use custom emoji from other guilds. public bool UseExternalEmojis => Permissions.GetValue(RawValue, GuildPermission.UseExternalEmojis); - /// If True, a user may connect to a voice channel. + /// If true, a user may connect to a voice channel. public bool Connect => Permissions.GetValue(RawValue, GuildPermission.Connect); - /// If True, a user may speak in a voice channel. + /// If true, a user may speak in a voice channel. public bool Speak => Permissions.GetValue(RawValue, GuildPermission.Speak); - /// If True, a user may mute users. + /// If true, a user may mute users. public bool MuteMembers => Permissions.GetValue(RawValue, GuildPermission.MuteMembers); - /// If True, a user may deafen users. + /// If true, a user may deafen users. public bool DeafenMembers => Permissions.GetValue(RawValue, GuildPermission.DeafenMembers); - /// If True, a user may move other users between voice channels. + /// If true, a user may move other users between voice channels. public bool MoveMembers => Permissions.GetValue(RawValue, GuildPermission.MoveMembers); - /// If True, a user may use voice-activity-detection rather than push-to-talk. + /// If true, a user may use voice-activity-detection rather than push-to-talk. public bool UseVAD => Permissions.GetValue(RawValue, GuildPermission.UseVAD); - /// If True, a user may change their own nickname. + /// If true, a user may change their own nickname. public bool ChangeNickname => Permissions.GetValue(RawValue, GuildPermission.ChangeNickname); - /// If True, a user may change the nickname of other users. + /// If true, a user may change the nickname of other users. public bool ManageNicknames => Permissions.GetValue(RawValue, GuildPermission.ManageNicknames); - /// If True, a user may adjust roles. + /// If true, a user may adjust roles. public bool ManageRoles => Permissions.GetValue(RawValue, GuildPermission.ManageRoles); - /// If True, a user may edit the webhooks for this guild. + /// If true, a user may edit the webhooks for this guild. public bool ManageWebhooks => Permissions.GetValue(RawValue, GuildPermission.ManageWebhooks); - /// If True, a user may edit the emojis for this guild. + /// If true, a user may edit the emojis for this guild. public bool ManageEmojis => Permissions.GetValue(RawValue, GuildPermission.ManageEmojis); - /// Creates a new GuildPermissions with the provided packed value. + /// Creates a new with the provided packed value. public GuildPermissions(ulong rawValue) { RawValue = rawValue; } private GuildPermissions(ulong initialValue, @@ -148,7 +148,7 @@ namespace Discord RawValue = value; } - /// Creates a new GuildPermissions with the provided permissions. + /// Creates a new with the provided permissions. public GuildPermissions( bool createInstantInvite = false, bool kickMembers = false, @@ -209,7 +209,7 @@ namespace Discord manageEmojis: manageEmojis) { } - /// Creates a new GuildPermissions from this one, changing the provided non-null permissions. + /// Creates a new from this one, changing the provided non-null permissions. public GuildPermissions Modify( bool? createInstantInvite = null, bool? kickMembers = null, diff --git a/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs index bda67a870..790e7ea72 100644 --- a/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs +++ b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs @@ -1,15 +1,23 @@ -namespace Discord +namespace Discord { public struct Overwrite { - /// Gets the unique identifier for the object this overwrite is targeting. + /// + /// Gets the unique identifier for the object this overwrite is targeting. + /// public ulong TargetId { get; } - /// Gets the type of object this overwrite is targeting. + /// + /// Gets the type of object this overwrite is targeting. + /// public PermissionTarget TargetType { get; } - /// Gets the permissions associated with this overwrite entry. + /// + /// Gets the permissions associated with this overwrite entry. + /// public OverwritePermissions Permissions { get; } - /// Creates a new Overwrite with provided target information and modified permissions. + /// + /// Creates a new with provided target information and modified permissions. + /// public Overwrite(ulong targetId, PermissionTarget targetType, OverwritePermissions permissions) { TargetId = targetId; diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index 54a4e4035..57c42b316 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -7,18 +7,28 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct OverwritePermissions { - /// Gets a blank OverwritePermissions that inherits all permissions. + /// + /// Gets a blank that inherits all permissions. + /// public static OverwritePermissions InheritAll { get; } = new OverwritePermissions(); - /// Gets a OverwritePermissions that grants all permissions for a given channelType. + /// + /// Gets a that grants all permissions for the given channel. + /// public static OverwritePermissions AllowAll(IChannel channel) => new OverwritePermissions(ChannelPermissions.All(channel).RawValue, 0); - /// Gets a OverwritePermissions that denies all permissions for a given channelType. + /// + /// Gets a that denies all permissions for the given channel. + /// public static OverwritePermissions DenyAll(IChannel channel) => new OverwritePermissions(0, ChannelPermissions.All(channel).RawValue); - /// Gets a packed value representing all the allowed permissions in this OverwritePermissions. + /// + /// Gets a packed value representing all the allowed permissions in this . + /// public ulong AllowValue { get; } - /// Gets a packed value representing all the denied permissions in this OverwritePermissions. + /// + /// Gets a packed value representing all the denied permissions in this . + /// public ulong DenyValue { get; } /// If Allowed, a user may create invites. @@ -62,7 +72,7 @@ namespace Discord /// If Allowed, a user may use voice-activity-detection rather than push-to-talk. public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); - /// If Allowed, a user may adjust role permissions. This also implictly grants all other permissions. + /// If Allowed, a user may adjust role permissions. This also implicitly grants all other permissions. public PermValue ManageRoles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageRoles); /// If True, a user may edit the webhooks for this channel. public PermValue ManageWebhooks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageWebhooks); @@ -121,7 +131,9 @@ namespace Discord DenyValue = denyValue; } - /// Creates a new ChannelPermissions with the provided permissions. + /// + /// Creates a new with the provided permissions. + /// public OverwritePermissions( PermValue createInstantInvite = PermValue.Inherit, PermValue manageChannel = PermValue.Inherit, @@ -147,7 +159,10 @@ namespace Discord embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { } - /// Creates a new OverwritePermissions from this one, changing the provided non-null permissions. + /// + /// Creates a new from this one, changing the provided non-null + /// permissions. + /// public OverwritePermissions Modify( PermValue? createInstantInvite = null, PermValue? manageChannel = null, diff --git a/src/Discord.Net.Core/Entities/Permissions/PermValue.cs b/src/Discord.Net.Core/Entities/Permissions/PermValue.cs index fe048b016..6cea8270d 100644 --- a/src/Discord.Net.Core/Entities/Permissions/PermValue.cs +++ b/src/Discord.Net.Core/Entities/Permissions/PermValue.cs @@ -1,9 +1,13 @@ -namespace Discord +namespace Discord { + /// Specifies the permission value. public enum PermValue { + /// Allows this permission. Allow, + /// Denies this permission. Deny, + /// Inherits the permission settings. Inherit } } diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 0bb04d339..f24ac883e 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -6,6 +6,9 @@ using StandardColor = System.Drawing.Color; namespace Discord { + /// + /// Represents a color used in Discord. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Color { @@ -62,10 +65,20 @@ namespace Discord /// Gets the blue component for this color. public byte B => (byte)(RawValue); + /// + /// Initializes a struct with the given raw value. + /// + /// The raw value of the color (e.g. 0x607D8B). public Color(uint rawValue) { RawValue = rawValue; } + /// + /// Initializes a struct with the given RGB bytes. + /// + /// The byte that represents the red color. + /// The byte that represents the green color. + /// The byte that represents the blue color. public Color(byte r, byte g, byte b) { RawValue = @@ -73,27 +86,42 @@ namespace Discord ((uint)g << 8) | (uint)b; } + + /// + /// Initializes a struct with the given RGB value. + /// + /// The value that represents the red color. Must be within 0~255. + /// The value that represents the green color. Must be within 0~255. + /// The value that represents the blue color. Must be within 0~255. + /// The argument value is not between 0 to 255. public Color(int r, int g, int b) { if (r < 0 || r > 255) - throw new ArgumentOutOfRangeException(nameof(r), "Value must be within [0,255]"); + throw new ArgumentOutOfRangeException(nameof(r), "Value must be within [0,255]."); if (g < 0 || g > 255) - throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,255]"); + throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,255]."); if (b < 0 || b > 255) - throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,255]"); + throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,255]."); RawValue = ((uint)r << 16) | ((uint)g << 8) | (uint)b; } + /// + /// Initializes a struct with the given RGB float value. + /// + /// The value that represents the red color. Must be within 0~1. + /// The value that represents the green color. Must be within 0~1. + /// The value that represents the blue color. Must be within 0~1. + /// The argument value is not between 0 to 1. public Color(float r, float g, float b) { if (r < 0.0f || r > 1.0f) - throw new ArgumentOutOfRangeException(nameof(r), "Value must be within [0,1]"); + throw new ArgumentOutOfRangeException(nameof(r), "Value must be within [0,1]."); if (g < 0.0f || g > 1.0f) - throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,1]"); + throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,1]."); if (b < 0.0f || b > 1.0f) - throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,1]"); + throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,1]."); RawValue = ((uint)(r * 255.0f) << 16) | ((uint)(g * 255.0f) << 8) | @@ -107,6 +135,12 @@ namespace Discord new Color((uint)color.ToArgb() << 8 >> 8); #endif + /// + /// Gets the hexadecimal representation of the color (e.g. #000ccc). + /// + /// + /// A hexadecimal string of the color. + /// public override string ToString() => $"#{Convert.ToString(RawValue, 16)}"; private string DebuggerDisplay => diff --git a/src/Discord.Net.Core/Entities/Roles/IRole.cs b/src/Discord.Net.Core/Entities/Roles/IRole.cs index c40e0d716..c0f4e9942 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -1,29 +1,64 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic role object. + /// public interface IRole : ISnowflakeEntity, IDeletable, IMentionable, IComparable { - /// Gets the guild owning this role. + /// + /// Gets the guild owning this role. + /// IGuild Guild { get; } - /// Gets the color given to users of this role. + /// + /// Gets the color given to users of this role. + /// Color Color { get; } - /// Returns true if users of this role are separated in the user list. + /// + /// Determines whether the role can be separated in the user list. + /// + /// + /// Returns true if users of this role are separated in the user list; otherwise, returns + /// false. + /// bool IsHoisted { get; } - /// Returns true if this role is automatically managed by Discord. + /// + /// Determines whether the role is managed by Discord. + /// + /// + /// Returns true if this role is automatically managed by Discord; otherwise, returns + /// false. + /// bool IsManaged { get; } - /// Returns true if this role may be mentioned in messages. + /// + /// Determines whether the role is mentionable. + /// + /// + /// Returns true if this role may be mentioned in messages; otherwise, returns + /// false. + /// bool IsMentionable { get; } - /// Gets the name of this role. + /// + /// Gets the name of this role. + /// string Name { get; } - /// Gets the permissions granted to members of this role. + /// + /// Gets the permissions granted to members of this role. + /// GuildPermissions Permissions { get; } - /// Gets this role's position relative to other roles in the same guild. + /// + /// Gets this role's position relative to other roles in the same guild. + /// int Position { get; } - ///// Modifies this role. + /// + /// Modifies this role. + /// + /// The properties to modify the role with. + /// The options to be used when sending the request. Task ModifyAsync(Action func, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs b/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs index 0c8afa24c..e13083f1d 100644 --- a/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs +++ b/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs @@ -1,12 +1,24 @@ -namespace Discord +namespace Discord { + /// + /// Properties that are used to reorder an . + /// public class ReorderRoleProperties { - /// The id of the role to be edited + /// + /// Gets the ID of the role to be edited. + /// public ulong Id { get; } - /// The new zero-based position of the role. + /// + /// Gets the new zero-based position of the role. + /// public int Position { get; } + /// + /// Initializes a with the given role ID and position. + /// + /// The ID of the role to be edited. + /// The new zero-based position of the role. public ReorderRoleProperties(ulong id, int pos) { Id = id; diff --git a/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs b/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs index 8950a2634..54fa27bfd 100644 --- a/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs +++ b/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs @@ -1,57 +1,48 @@ -namespace Discord +namespace Discord { /// - /// Modify an IRole with the specified parameters + /// Properties that are used to modify an with the specified changes. /// - /// - /// - /// await role.ModifyAsync(x => - /// { - /// x.Color = new Color(180, 15, 40); - /// x.Hoist = true; - /// }); - /// - /// - /// + /// public class RoleProperties { /// - /// The name of the role + /// Gets or sets the name of the role. /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Name { get; set; } /// - /// The role's GuildPermissions + /// Gets or sets the role's . /// public Optional Permissions { get; set; } /// - /// The position of the role. This is 0-based! + /// Gets or sets the position of the role. This is 0-based! /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Position { get; set; } /// - /// The color of the Role. + /// Gets or sets the color of the role. /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Color { get; set; } /// - /// Whether or not this role should be displayed independently in the userlist. + /// Gets or sets whether or not this role should be displayed independently in the user list. /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Hoist { get; set; } /// - /// Whether or not this role can be mentioned. + /// Gets or sets whether or not this role can be mentioned. /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Mentionable { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs index 1c5e5482c..27a8be351 100644 --- a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs @@ -1,70 +1,75 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Discord { /// - /// Modify an IGuildUser with the following parameters. + /// Properties that are used to modify an with the following parameters. /// - /// - /// - /// await (Context.User as IGuildUser)?.ModifyAsync(x => - /// { - /// x.Nickname = $"festive {Context.User.Username}"; - /// }); - /// - /// - /// + /// public class GuildUserProperties { /// - /// Should the user be guild-muted in a voice channel? + /// Gets or sets whether the user should be muted in a voice channel. /// /// - /// If this value is set to true, no user will be able to hear this user speak in the guild. + /// If this value is set to true, no user will be able to hear this user speak in the guild. /// public Optional Mute { get; set; } /// - /// Should the user be guild-deafened in a voice channel? + /// Gets or sets whether the user should be deafened in a voice channel. /// /// - /// If this value is set to true, this user will not be able to hear anyone speak in the guild. + /// If this value is set to true, this user will not be able to hear anyone speak in the guild. /// public Optional Deaf { get; set; } /// - /// Should the user have a nickname set? + /// Gets or sets the user's nickname. /// /// - /// To clear the user's nickname, this value can be set to or . + /// To clear the user's nickname, this value can be set to null or + /// . /// public Optional Nickname { get; set; } /// - /// What roles should the user have? + /// Gets or sets the roles the user should have. /// /// - /// To add a role to a user: - /// To remove a role from a user: + /// + /// To add a role to a user: + /// + /// + /// + /// To remove a role from a user: + /// + /// /// public Optional> Roles { get; set; } /// - /// What roles should the user have? + /// Gets or sets the roles the user should have. /// /// - /// To add a role to a user: - /// To remove a role from a user: + /// + /// To add a role to a user: + /// + /// + /// + /// To remove a role from a user: + /// + /// /// public Optional> RoleIds { get; set; } /// - /// Move a user to a voice channel. + /// Moves a user to a voice channel. /// /// - /// This user MUST already be in a Voice Channel for this to work. + /// This user MUST already be in a for this to work. /// public Optional Channel { get; set; } /// - /// Move a user to a voice channel. + /// Moves a user to a voice channel. /// /// - /// This user MUST already be in a Voice Channel for this to work. + /// This user MUST already be in a for this to work. /// public Optional ChannelId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Users/IGroupUser.cs b/src/Discord.Net.Core/Entities/Users/IGroupUser.cs index dd046a5a8..ecf01f721 100644 --- a/src/Discord.Net.Core/Entities/Users/IGroupUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGroupUser.cs @@ -1,5 +1,8 @@ -namespace Discord +namespace Discord { + /// + /// Represents a Discord user that is in a group. + /// public interface IGroupUser : IUser, IVoiceState { ///// Kicks this user from this group. diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 57cad1333..4d54a49c4 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -1,41 +1,83 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Discord { - /// A Guild-User pairing. + /// + /// Represents a generic guild user. + /// public interface IGuildUser : IUser, IVoiceState { - /// Gets when this user joined this guild. + /// + /// Gets when this user joined this guild. + /// DateTimeOffset? JoinedAt { get; } - /// Gets the nickname for this user. + /// + /// Gets the nickname for this user. + /// string Nickname { get; } - /// Gets the guild-level permissions for this user. + /// + /// Gets the guild-level permissions for this user. + /// GuildPermissions GuildPermissions { get; } - /// Gets the guild for this user. + /// + /// Gets the guild for this user. + /// IGuild Guild { get; } - /// Gets the id of the guild for this user. + /// + /// Gets the ID of the guild for this user. + /// ulong GuildId { get; } - /// Returns a collection of the ids of the roles this user is a member of in this guild, including the guild's @everyone role. + /// + /// Returns a collection of the ids of the roles this user is a member of in this guild, including the + /// guild's @everyone role. + /// IReadOnlyCollection RoleIds { get; } - /// Gets the level permissions granted to this user to a given channel. + /// + /// Gets the level permissions granted to this user to a given channel. + /// + /// The channel to get the permission from. ChannelPermissions GetPermissions(IGuildChannel channel); - /// Kicks this user from this guild. + /// + /// Kicks this user from this guild. + /// + /// The reason for the kick which will be recorded in the audit log. + /// The options to be used when sending the request. Task KickAsync(string reason = null, RequestOptions options = null); - /// Modifies this user's properties in this guild. + /// + /// Modifies this user's properties in this guild. + /// + /// The properties to modify the user with. + /// The options to be used when sending the request. Task ModifyAsync(Action func, RequestOptions options = null); - /// Adds a role to this user in this guild. + /// + /// Adds a to this user in this guild. + /// + /// The role to be added to the user. + /// The options to be used when sending the request. Task AddRoleAsync(IRole role, RequestOptions options = null); - /// Adds roles to this user in this guild. + /// + /// Adds to this user in this guild. + /// + /// The roles to be added to the user. + /// The options to be used when sending the request. Task AddRolesAsync(IEnumerable roles, RequestOptions options = null); - /// Removes a role from this user in this guild. + /// + /// Removes a from this user in this guild. + /// + /// The role to be removed from the user. + /// The options to be used when sending the request. Task RemoveRoleAsync(IRole role, RequestOptions options = null); - /// Removes roles from this user in this guild. + /// + /// Removes from this user in this guild. + /// + /// The roles to be removed from the user. + /// The options to be used when sending the request. Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Users/IPresence.cs b/src/Discord.Net.Core/Entities/Users/IPresence.cs index 25adcc9c4..d40d47251 100644 --- a/src/Discord.Net.Core/Entities/Users/IPresence.cs +++ b/src/Discord.Net.Core/Entities/Users/IPresence.cs @@ -1,10 +1,17 @@ -namespace Discord +namespace Discord { + /// + /// Represents a Discord user's presence status. + /// public interface IPresence { - /// Gets the activity this user is currently doing. + /// + /// Gets the activity this user is currently doing. + /// IActivity Activity { get; } - /// Gets the current status of this user. + /// + /// Gets the current status of this user. + /// UserStatus Status { get; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Users/ISelfUser.cs b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs index 7b91d4e3a..1ead0cbba 100644 --- a/src/Discord.Net.Core/Entities/Users/ISelfUser.cs +++ b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs @@ -3,15 +3,27 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents the logged-in Discord user. + /// public interface ISelfUser : IUser { - /// Gets the email associated with this user. + /// + /// Gets the email associated with this user. + /// string Email { get; } - /// Returns true if this user's email has been verified. + /// + /// Returns true if this user's email has been verified. + /// bool IsVerified { get; } - /// Returns true if this user has enabled MFA on their account. + /// + /// Returns true if this user has enabled MFA on their account. + /// bool IsMfaEnabled { get; } + /// + /// Modifies the user's properties. + /// Task ModifyAsync(Action func, RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index c5cce7a25..96b9ae7ee 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -2,26 +2,58 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic user. + /// public interface IUser : ISnowflakeEntity, IMentionable, IPresence { - /// Gets the id of this user's avatar. + /// + /// Gets the ID of this user's avatar. + /// string AvatarId { get; } - /// Gets the url to this user's avatar. + /// + /// Returns a URL to this user's avatar. + /// + /// The format to return. + /// + /// The size of the image to return in. This can be any power of two between 16 and 2048. + /// + /// + /// User's avatar URL. + /// string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); - /// Gets the url to this user's default avatar. + /// + /// Returns the URL to this user's default avatar. + /// string GetDefaultAvatarUrl(); - /// Gets the per-username unique id for this user. + /// + /// Gets the per-username unique ID for this user. + /// string Discriminator { get; } - /// Gets the per-username unique id for this user. + /// + /// Gets the per-username unique ID for this user. + /// ushort DiscriminatorValue { get; } - /// Returns true if this user is a bot user. + /// + /// Gets true if this user is a bot user. + /// bool IsBot { get; } - /// Returns true if this user is a webhook user. + /// + /// Gets true if this user is a webhook user. + /// bool IsWebhook { get; } - /// Gets the username for this user. + /// + /// Gets the username for this user. + /// string Username { get; } - /// Returns a private message channel to this user, creating one if it does not already exist. + /// + /// Returns a direct message channel to this user, or create one if it does not already exist. + /// + /// The options to be used when sending the request. + /// + /// An awaitable Task containing the DM channel. + /// Task GetOrCreateDMChannelAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs index 428601f2a..abe06c3b3 100644 --- a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs +++ b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs @@ -1,20 +1,37 @@ -namespace Discord +namespace Discord { + /// + /// Represents a user's voice connection status. + /// public interface IVoiceState { - /// Returns true if the guild has deafened this user. + /// + /// Returns true if the guild has deafened this user. + /// bool IsDeafened { get; } - /// Returns true if the guild has muted this user. + /// + /// Returns true if the guild has muted this user. + /// bool IsMuted { get; } - /// Returns true if this user has marked themselves as deafened. + /// + /// Returns true if this user has marked themselves as deafened. + /// bool IsSelfDeafened { get; } - /// Returns true if this user has marked themselves as muted. + /// + /// Returns true if this user has marked themselves as muted. + /// bool IsSelfMuted { get; } - /// Returns true if the guild is temporarily blocking audio to/from this user. + /// + /// Returns true if the guild is temporarily blocking audio to/from this user. + /// bool IsSuppressed { get; } - /// Gets the voice channel this user is currently in, if any. + /// + /// Gets the voice channel this user is currently in, or null if none. + /// IVoiceChannel VoiceChannel { get; } - /// Gets the unique identifier for this user's voice session. + /// + /// Gets the unique identifier for this user's voice session. + /// string VoiceSessionId { get; } } } diff --git a/src/Discord.Net.Core/Entities/Users/IWebhookUser.cs b/src/Discord.Net.Core/Entities/Users/IWebhookUser.cs index be769b944..7a10c6b6b 100644 --- a/src/Discord.Net.Core/Entities/Users/IWebhookUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IWebhookUser.cs @@ -1,7 +1,9 @@ -namespace Discord +namespace Discord { + /// Represents a Webhook Discord user. public interface IWebhookUser : IGuildUser { + /// Gets the ID of a webhook. ulong WebhookId { get; } } } diff --git a/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs b/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs index 9c4162780..e2ae12ba4 100644 --- a/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs @@ -1,25 +1,17 @@ -namespace Discord +namespace Discord { /// - /// Modify the current user with the specified arguments + /// Properties that are used to modify the with the specified changes. /// - /// - /// - /// await Context.Client.CurrentUser.ModifyAsync(x => - /// { - /// x.Avatar = new Image(File.OpenRead("avatar.jpg")); - /// }); - /// - /// - /// + /// public class SelfUserProperties { /// - /// Your username + /// Gets or sets the username. /// public Optional Username { get; set; } /// - /// Your avatar + /// Gets or sets the avatar. /// public Optional Avatar { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Users/UserStatus.cs b/src/Discord.Net.Core/Entities/Users/UserStatus.cs index 74a52a0fa..09033261e 100644 --- a/src/Discord.Net.Core/Entities/Users/UserStatus.cs +++ b/src/Discord.Net.Core/Entities/Users/UserStatus.cs @@ -1,12 +1,33 @@ -namespace Discord +namespace Discord { + /// + /// Defines the available Discord user status. + /// public enum UserStatus { + /// + /// The user is offline. + /// Offline, + /// + /// The user is online. + /// Online, + /// + /// The user is idle. + /// Idle, + /// + /// The user is AFK. + /// AFK, + /// + /// The user is busy. + /// DoNotDisturb, + /// + /// The user is invisible. + /// Invisible, } } diff --git a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs index ef56f72b9..b2d017316 100644 --- a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs +++ b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs @@ -3,32 +3,55 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a webhook object on Discord. + /// public interface IWebhook : IDeletable, ISnowflakeEntity { - /// Gets the token of this webhook. + /// + /// Gets the token of this webhook. + /// string Token { get; } - /// Gets the default name of this webhook. + /// + /// Gets the default name of this webhook. + /// string Name { get; } - /// Gets the id of this webhook's default avatar. + /// + /// Gets the ID of this webhook's default avatar. + /// string AvatarId { get; } - /// Gets the url to this webhook's default avatar. + /// + /// Gets the URL to this webhook's default avatar. + /// string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); - /// Gets the channel for this webhook. + /// + /// Gets the channel for this webhook. + /// ITextChannel Channel { get; } - /// Gets the id of the channel for this webhook. + /// + /// Gets the ID of the channel for this webhook. + /// ulong ChannelId { get; } - /// Gets the guild owning this webhook. + /// + /// Gets the guild owning this webhook. + /// IGuild Guild { get; } - /// Gets the id of the guild owning this webhook. + /// + /// Gets the ID of the guild owning this webhook. + /// ulong? GuildId { get; } - /// Gets the user that created this webhook. + /// + /// Gets the user that created this webhook. + /// IUser Creator { get; } - /// Modifies this webhook. + /// + /// Modifies this webhook. + /// Task ModifyAsync(Action func, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs b/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs index 8759a1729..00af9f9ef 100644 --- a/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs +++ b/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs @@ -1,40 +1,31 @@ -namespace Discord +namespace Discord { /// - /// Modify an with the specified parameters. + /// Properties used to modify an with the specified changes. /// - /// - /// - /// await webhook.ModifyAsync(x => - /// { - /// x.Name = "Bob"; - /// x.Avatar = new Image("avatar.jpg"); - /// }); - /// - /// - /// + /// public class WebhookProperties { /// - /// The default name of the webhook. + /// Gets or sets the default name of the webhook. /// public Optional Name { get; set; } /// - /// The default avatar of the webhook. + /// Gets or sets the default avatar of the webhook. /// public Optional Image { get; set; } /// - /// The channel for this webhook. + /// Gets or sets the channel for this webhook. /// /// - /// This field is not used when authenticated with . + /// This field is not used when authenticated with . /// public Optional Channel { get; set; } /// - /// The channel id for this webhook. + /// Gets or sets the channel ID for this webhook. /// /// - /// This field is not used when authenticated with . + /// This field is not used when authenticated with . /// public Optional ChannelId { get; set; } } diff --git a/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs b/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs index dd16d2943..56ed68cfe 100644 --- a/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs +++ b/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs @@ -4,19 +4,15 @@ using System.Threading.Tasks; namespace Discord { + /// An extension class for squashing . public static class AsyncEnumerableExtensions { - /// - /// Flattens the specified pages into one asynchronously - /// - /// - /// - /// + /// Flattens the specified pages into one asynchronously. public static async Task> FlattenAsync(this IAsyncEnumerable> source) { return await source.Flatten().ToArray().ConfigureAwait(false); } - + /// Flattens the specified pages into one . public static IAsyncEnumerable Flatten(this IAsyncEnumerable> source) { return source.SelectMany(enumerable => enumerable.ToAsyncEnumerable()); diff --git a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs index ff3c7caf7..c38fa8e00 100644 --- a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs +++ b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs @@ -1,24 +1,31 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Discord { + /// An extension class for the Discord client. public static class DiscordClientExtensions { + /// Gets the private channel with the provided ID. public static async Task GetPrivateChannelAsync(this IDiscordClient client, ulong id) => await client.GetChannelAsync(id).ConfigureAwait(false) as IPrivateChannel; + /// Gets the DM channel with the provided ID. public static async Task GetDMChannelAsync(this IDiscordClient client, ulong id) => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IDMChannel; + /// Gets all available DM channels for the client. public static async Task> GetDMChannelsAsync(this IDiscordClient client) => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IDMChannel).Where(x => x != null); + /// Gets the group channel with the provided ID. public static async Task GetGroupChannelAsync(this IDiscordClient client, ulong id) => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IGroupChannel; + /// Gets all available group channels for the client. public static async Task> GetGroupChannelsAsync(this IDiscordClient client) => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IGroupChannel).Where(x => x != null); + /// Gets the most optimal voice region for the client. public static async Task GetOptimalVoiceRegionAsync(this IDiscordClient discord) { var regions = await discord.GetVoiceRegionsAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs b/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs index 2eb4ed473..efa168be3 100644 --- a/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs +++ b/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs @@ -2,26 +2,35 @@ using System; namespace Discord { + /// An extension class for building an embed. public static class EmbedBuilderExtensions { + /// Adds embed color based on the provided raw value. public static EmbedBuilder WithColor(this EmbedBuilder builder, uint rawValue) => builder.WithColor(new Color(rawValue)); + /// Adds embed color based on the provided RGB value. public static EmbedBuilder WithColor(this EmbedBuilder builder, byte r, byte g, byte b) => builder.WithColor(new Color(r, g, b)); + /// Adds embed color based on the provided RGB value. public static EmbedBuilder WithColor(this EmbedBuilder builder, int r, int g, int b) => builder.WithColor(new Color(r, g, b)); + /// Adds embed color based on the provided RGB value. public static EmbedBuilder WithColor(this EmbedBuilder builder, float r, float g, float b) => builder.WithColor(new Color(r, g, b)); + /// Fills the embed author field with the provided user's full username and avatar URL. public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IUser user) => builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.GetAvatarUrl()); + /// Fills the embed author field with the provided user's nickname and avatar URL; username is used if nickname is not set. public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IGuildUser user) => builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.GetAvatarUrl()); + /// Converts a object to a . + /// The embed type is not . public static EmbedBuilder ToEmbedBuilder(this IEmbed embed) { if (embed.Type != EmbedType.Rich) diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index 951e8ca4b..7759c8ea3 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -1,13 +1,22 @@ +using System; using System.Threading.Tasks; using System.IO; namespace Discord { + /// An extension class for various Discord user objects. public static class UserExtensions { /// - /// Sends a message to the user via DM. + /// Sends a message via DM. /// + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// An awaitable Task containing the message sent to the channel. + /// public static async Task SendMessageAsync(this IUser user, string text = null, bool isTTS = false, @@ -18,8 +27,22 @@ namespace Discord } /// - /// Sends a file to the user via DM. + /// Sends a file to this message channel with an optional caption. /// + /// The of the file to be sent. + /// The name of the attachment. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// public static async Task SendFileAsync(this IUser user, Stream stream, string filename, @@ -33,8 +56,21 @@ namespace Discord } /// - /// Sends a file to the user via DM. + /// Sends a file via DM with an optional caption. /// + /// The file path of the file. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// public static async Task SendFileAsync(this IUser user, string filePath, string text = null, @@ -45,6 +81,15 @@ namespace Discord return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); } + /// + /// Bans the provided user from the guild and optionally prunes their recent messages. + /// + /// The user to ban. + /// + /// The number of days to remove messages from this user for - must be between [0, 7] + /// + /// The reason of the ban to be written in the audit log. + /// is not between 0 to 7. public static Task BanAsync(this IGuildUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) => user.Guild.AddBanAsync(user, pruneDays, reason, options); } diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index aa822f99e..0b16daeb1 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -1,9 +1,10 @@ -namespace Discord +namespace Discord { + /// A helper class for formatting characters. public static class Format { // Characters which need escaping - private static string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; + private static readonly string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; /// Returns a markdown-formatted string with bold formatting. public static string Bold(string text) => $"**{text}**"; diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index 083c0b512..f5bbd0a28 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -5,20 +5,76 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic Discord client. + /// public interface IDiscordClient : IDisposable { + /// + /// Gets the current state of connection. + /// ConnectionState ConnectionState { get; } + /// + /// Gets the currently logged-in user. + /// ISelfUser CurrentUser { get; } + /// + /// Gets the token type of the logged-in user. + /// TokenType TokenType { get; } Task StartAsync(); Task StopAsync(); + /// + /// Gets a Discord application information for the logged-in user. + /// + /// + /// This method reflects your application information you submitted when creating a Discord application via + /// the Developer Portal. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the application information. + /// Task GetApplicationInfoAsync(RequestOptions options = null); + /// + /// Gets a generic channel with the provided ID. + /// + /// The ID of the channel. + /// The that determines whether the object should be fetched from cache. Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a list of private channels. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// Task> GetPrivateChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Returns a collection of direct message channels. + /// + /// + /// This method returns a collection of currently opened direct message channels. + /// + /// This method will not return previously opened DM channels outside of the current session! If you + /// have just started the client, this may return an empty collection. + /// + /// + /// + /// The that determines whether the object should be fetched from cache. + /// + /// + /// An awaitable containing a collection of DM channels. + /// Task> GetDMChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a list of group channels. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// Task> GetGroupChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task> GetConnectionsAsync(RequestOptions options = null); diff --git a/src/Discord.Net.Core/Logging/LogManager.cs b/src/Discord.Net.Core/Logging/LogManager.cs index a69519fa2..a99c45b39 100644 --- a/src/Discord.Net.Core/Logging/LogManager.cs +++ b/src/Discord.Net.Core/Logging/LogManager.cs @@ -24,7 +24,10 @@ namespace Discord.Logging if (severity <= Level) await _messageEvent.InvokeAsync(new LogMessage(severity, source, null, ex)).ConfigureAwait(false); } - catch { } + catch + { + // ignored + } } public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) { @@ -33,7 +36,10 @@ namespace Discord.Logging if (severity <= Level) await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false); } - catch { } + catch + { + // ignored + } } public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null) diff --git a/src/Discord.Net.Core/Logging/LogMessage.cs b/src/Discord.Net.Core/Logging/LogMessage.cs index d1b3782be..d70bb01f5 100644 --- a/src/Discord.Net.Core/Logging/LogMessage.cs +++ b/src/Discord.Net.Core/Logging/LogMessage.cs @@ -1,15 +1,38 @@ -using System; +using System; using System.Text; namespace Discord { + /// + /// Represents a message used for logging purposes. + /// public struct LogMessage { + /// + /// Gets the severity of the log message. + /// public LogSeverity Severity { get; } + /// + /// Gets the source of the log message. + /// public string Source { get; } + /// + /// Gets the message of the log message. + /// public string Message { get; } + /// + /// Gets the exception of the log message. + /// public Exception Exception { get; } + /// + /// Initializes a new struct with the severity, source, message of the event, and + /// optionally, an exception. + /// + /// The severity of the event. + /// The source of the event. + /// The message of the event. + /// The exception of the event. public LogMessage(LogSeverity severity, string source, string message, Exception exception = null) { Severity = severity; @@ -17,8 +40,8 @@ namespace Discord Message = message; Exception = exception; } - - public override string ToString() => ToString(null); + + public override string ToString() => ToString(); public string ToString(StringBuilder builder = null, bool fullException = true, bool prependTimestamp = true, DateTimeKind timestampKind = DateTimeKind.Local, int? padSource = 11) { string sourceName = Source; diff --git a/src/Discord.Net.Core/Logging/LogSeverity.cs b/src/Discord.Net.Core/Logging/LogSeverity.cs index 785b0ef46..f9b518c17 100644 --- a/src/Discord.Net.Core/Logging/LogSeverity.cs +++ b/src/Discord.Net.Core/Logging/LogSeverity.cs @@ -1,12 +1,34 @@ -namespace Discord +namespace Discord { + /// + /// Specifies the severity of the log message. + /// public enum LogSeverity { + /// + /// Logs that contain the most severe level of error. This type of error indicate that immediate attention + /// may be required. + /// Critical = 0, + /// + /// Logs that highlight when the flow of execution is stopped due to a failure. + /// Error = 1, + /// + /// Logs that highlight an abnormal activity in the flow of execution. + /// Warning = 2, + /// + /// Logs that track the general flow of the application. + /// Info = 3, + /// + /// Logs that are used for interactive investigation during development. + /// Verbose = 4, + /// + /// Logs that contain the most detailed messages. + /// Debug = 5 } } diff --git a/src/Discord.Net.Core/LoginState.cs b/src/Discord.Net.Core/LoginState.cs index 42b6ecac9..49f86c9fa 100644 --- a/src/Discord.Net.Core/LoginState.cs +++ b/src/Discord.Net.Core/LoginState.cs @@ -1,10 +1,15 @@ -namespace Discord +namespace Discord { + /// Specifies the state of the client's login status. public enum LoginState : byte { + /// The client is currently logged out. LoggedOut, + /// The client is currently logging in. LoggingIn, + /// The client is currently logged in. LoggedIn, + /// The client is currently logging out. LoggingOut } } diff --git a/src/Discord.Net.Core/Net/HttpException.cs b/src/Discord.Net.Core/Net/HttpException.cs index d0ee65b23..aa440bf1c 100644 --- a/src/Discord.Net.Core/Net/HttpException.cs +++ b/src/Discord.Net.Core/Net/HttpException.cs @@ -3,13 +3,41 @@ using System.Net; namespace Discord.Net { + /// + /// Describes an exception that occurred during the processing of Discord HTTP requests. + /// public class HttpException : Exception { + /// + /// Gets the HTTP status code returned by Discord. + /// + /// + /// An + /// HTTP status code + /// from Discord. + /// public HttpStatusCode HttpCode { get; } + /// + /// Gets the JSON error code returned by Discord. + /// + /// + /// A + /// JSON error code + /// from Discord, or null if none. + /// public int? DiscordCode { get; } + /// + /// Gets the reason of the exception. + /// public string Reason { get; } + /// + /// Gets the request object used to send the request. + /// public IRequest Request { get; } + /// + /// Initializes a new instance of the class. + /// public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null) : base(CreateMessage(httpCode, discordCode, reason)) { diff --git a/src/Discord.Net.Core/Net/IRequest.cs b/src/Discord.Net.Core/Net/IRequest.cs index d3c708dd5..1f23e65cd 100644 --- a/src/Discord.Net.Core/Net/IRequest.cs +++ b/src/Discord.Net.Core/Net/IRequest.cs @@ -2,6 +2,9 @@ using System; namespace Discord.Net { + /// + /// Represents a generic request to be sent to Discord. + /// public interface IRequest { DateTimeOffset? TimeoutAt { get; } diff --git a/src/Discord.Net.Core/Net/RateLimitedException.cs b/src/Discord.Net.Core/Net/RateLimitedException.cs index 2d34d7bc2..6e084886e 100644 --- a/src/Discord.Net.Core/Net/RateLimitedException.cs +++ b/src/Discord.Net.Core/Net/RateLimitedException.cs @@ -2,10 +2,20 @@ using System; namespace Discord.Net { + /// + /// Describes an exception that indicates the user is being rate limited by Discord. + /// public class RateLimitedException : TimeoutException { + /// + /// Gets the request object used to send the request. + /// public IRequest Request { get; } + /// + /// Initializes a new instance of the class using the + /// sent. + /// public RateLimitedException(IRequest request) : base("You are being rate limited.") { diff --git a/src/Discord.Net.Core/Net/WebSocketClosedException.cs b/src/Discord.Net.Core/Net/WebSocketClosedException.cs index d647b6c8c..492bd82e0 100644 --- a/src/Discord.Net.Core/Net/WebSocketClosedException.cs +++ b/src/Discord.Net.Core/Net/WebSocketClosedException.cs @@ -1,11 +1,29 @@ -using System; +using System; namespace Discord.Net { + /// + /// Describes an exception that causes the WebSocket to close during a session. + /// public class WebSocketClosedException : Exception { + /// + /// Gets the close code sent by Discord. + /// + /// + /// A + /// close code + /// from Discord. + /// public int CloseCode { get; } + /// + /// Gets the reason of the interruption. + /// public string Reason { get; } + /// + /// Initializes a new instance of the using a Discord close code + /// and an optional reason. + /// public WebSocketClosedException(int closeCode, string reason = null) : base($"The server sent close {closeCode}{(reason != null ? $": \"{reason}\"" : "")}") { diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 5f3a8814b..785b518f3 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -1,22 +1,36 @@ -using System.Threading; +using System.Threading; namespace Discord { + /// + /// Represents options that should be used when sending a request. + /// public class RequestOptions { + /// + /// Creates a new class with its default settings. + /// public static RequestOptions Default => new RequestOptions(); - /// - /// The max time, in milliseconds, to wait for this request to complete. If null, a request will not time out. - /// If a rate limit has been triggered for this request's bucket and will not be unpaused in time, this request will fail immediately. + /// + /// Gets or set the max time, in milliseconds, to wait for this request to complete. If + /// null, a request will not time out. If a rate limit has been triggered for this + /// request's bucket and will not be unpaused in time, this request will fail immediately. /// public int? Timeout { get; set; } public CancellationToken CancelToken { get; set; } = CancellationToken.None; + /// + /// Gets or sets the retry behavior when the request fails. + /// public RetryMode? RetryMode { get; set; } public bool HeaderOnly { get; internal set; } /// - /// The reason for this action in the guild's audit log + /// Gets or sets the reason for this action in the guild's audit log. /// + /// + /// Gets or sets the reason that will be written to the guild's audit log if applicable. This may not apply + /// to all actions. + /// public string AuditLogReason { get; set; } internal bool IgnoreState { get; set; } @@ -31,11 +45,15 @@ namespace Discord return options.Clone(); } + /// + /// Initializes a new class with the default request timeout set in + /// . + /// public RequestOptions() { Timeout = DiscordConfig.DefaultRequestTimeout; } - + public RequestOptions Clone() => MemberwiseClone() as RequestOptions; } } diff --git a/src/Discord.Net.Core/TokenType.cs b/src/Discord.Net.Core/TokenType.cs index 62181420a..8ca3f031c 100644 --- a/src/Discord.Net.Core/TokenType.cs +++ b/src/Discord.Net.Core/TokenType.cs @@ -2,12 +2,22 @@ using System; namespace Discord { + /// Specifies the type of token to use with the client. public enum TokenType { [Obsolete("User logins are deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827", error: true)] User, + /// + /// An OAuth2 token type. + /// Bearer, + /// + /// A bot token type. + /// Bot, + /// + /// A webhook token type. + /// Webhook } } diff --git a/src/Discord.Net.Core/Utils/Cacheable.cs b/src/Discord.Net.Core/Utils/Cacheable.cs index f17aa8699..02da682ae 100644 --- a/src/Discord.Net.Core/Utils/Cacheable.cs +++ b/src/Discord.Net.Core/Utils/Cacheable.cs @@ -1,30 +1,31 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord { /// - /// Contains an entity that may be cached. + /// Represents a cached entity. /// - /// The type of entity that is cached - /// The type of this entity's ID + /// The type of entity that is cached. + /// The type of this entity's ID. public struct Cacheable where TEntity : IEntity where TId : IEquatable { /// - /// Is this entity cached? + /// Gets whether this entity is cached. /// public bool HasValue { get; } /// - /// The ID of this entity. + /// Gets the ID of this entity. /// public TId Id { get; } /// - /// The entity, if it could be pulled from cache. + /// Gets the entity if it could be pulled from cache. /// /// - /// This value is not guaranteed to be set; in cases where the entity cannot be pulled from cache, it is null. + /// This value is not guaranteed to be set; in cases where the entity cannot be pulled from cache, it is + /// null. /// public TEntity Value { get; } private Func> DownloadFunc { get; } @@ -38,22 +39,26 @@ namespace Discord } /// - /// Downloads this entity to cache. + /// Downloads this entity to cache. /// - /// An awaitable Task containing the downloaded entity. /// Thrown when used from a user account. /// Thrown when the message is deleted. + /// + /// An awaitable containing the downloaded entity. + /// public async Task DownloadAsync() { - return await DownloadFunc(); + return await DownloadFunc().ConfigureAwait(false); } /// - /// Returns the cached entity if it exists; otherwise downloads it. + /// Returns the cached entity if it exists; otherwise downloads it. /// - /// An awaitable Task containing a cached or downloaded entity. /// Thrown when used from a user account. /// Thrown when the message is deleted and is not in cache. - public async Task GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync(); + /// + /// An awaitable containing a cached or downloaded entity. + /// + public async Task GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync().ConfigureAwait(false); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/Comparers.cs b/src/Discord.Net.Core/Utils/Comparers.cs index d7641e897..40500ffe3 100644 --- a/src/Discord.Net.Core/Utils/Comparers.cs +++ b/src/Discord.Net.Core/Utils/Comparers.cs @@ -3,13 +3,31 @@ using System.Collections.Generic; namespace Discord { + /// + /// Represents a collection of for various Discord objects. + /// public static class DiscordComparers { // TODO: simplify with '??=' slated for C# 8.0 + /// + /// Gets an to be used to compare users. + /// public static IEqualityComparer UserComparer => _userComparer ?? (_userComparer = new EntityEqualityComparer()); + /// + /// Gets an to be used to compare guilds. + /// public static IEqualityComparer GuildComparer => _guildComparer ?? (_guildComparer = new EntityEqualityComparer()); + /// + /// Gets an to be used to compare channels. + /// public static IEqualityComparer ChannelComparer => _channelComparer ?? (_channelComparer = new EntityEqualityComparer()); + /// + /// Gets an to be used to compare roles. + /// public static IEqualityComparer RoleComparer => _roleComparer ?? (_roleComparer = new EntityEqualityComparer()); + /// + /// Gets an to be used to compare messages. + /// public static IEqualityComparer MessageComparer => _messageComparer ?? (_messageComparer = new EntityEqualityComparer()); private static IEqualityComparer _userComparer; diff --git a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs index 1fc11587e..bbdc59087 100644 --- a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs +++ b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -157,12 +157,16 @@ namespace Discord : this(collection, EqualityComparer.Default) { } public ConcurrentHashSet(IEqualityComparer comparer) : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) { } + /// is null public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) : this(comparer) { if (collection == null) throw new ArgumentNullException(nameof(collection)); InitializeFromCollection(collection); } + /// + /// or is null + /// public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer comparer) : this(concurrencyLevel, DefaultCapacity, false, comparer) { @@ -197,7 +201,7 @@ namespace Discord { foreach (var value in collection) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(nameof(value)); if (!TryAddInternal(value, _comparer.GetHashCode(value), false)) throw new ArgumentException(); @@ -206,10 +210,10 @@ namespace Discord if (_budget == 0) _budget = _tables._buckets.Length / _tables._locks.Length; } - + /// is null public bool ContainsKey(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(nameof(value)); return ContainsKeyInternal(value, _comparer.GetHashCode(value)); } private bool ContainsKeyInternal(T value, int hashcode) @@ -230,9 +234,10 @@ namespace Discord return false; } + /// is null public bool TryAdd(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(nameof(value)); return TryAddInternal(value, _comparer.GetHashCode(value), true); } private bool TryAddInternal(T value, int hashcode, bool acquireLock) @@ -279,9 +284,10 @@ namespace Discord } } + /// is null public bool TryRemove(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(nameof(value)); return TryRemoveInternal(value); } private bool TryRemoveInternal(T value) @@ -467,4 +473,4 @@ namespace Discord Monitor.Exit(_tables._locks[i]); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 6c69827b4..ae506e142 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -1,29 +1,55 @@ -using System; +using System; using System.Globalization; using System.Text; namespace Discord { + /// + /// Represents a helper class for mention-related parsing. + /// public static class MentionUtils { private const char SanitizeChar = '\x200b'; //If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake) internal static string MentionUser(string id, bool useNickname = true) => useNickname ? $"<@!{id}>" : $"<@{id}>"; + /// + /// Returns a mention string based on the user ID. + /// + /// + /// A user mention string (e.g. <@80351110224678912>). + /// public static string MentionUser(ulong id) => MentionUser(id.ToString(), true); internal static string MentionChannel(string id) => $"<#{id}>"; + /// + /// Returns a mention string based on the channel ID. + /// + /// + /// A channel mention string (e.g. <#103735883630395392>). + /// public static string MentionChannel(ulong id) => MentionChannel(id.ToString()); - internal static string MentionRole(string id) => $"<@&{id}>"; + internal static string MentionRole(string id) => $"<@&{id}>"; + /// + /// Returns a mention string based on the role ID. + /// + /// + /// A role mention string (e.g. <@&165511591545143296>). + /// public static string MentionRole(ulong id) => MentionRole(id.ToString()); - /// Parses a provided user mention string. + /// + /// Parses a provided user mention string. + /// + /// Invalid mention format. public static ulong ParseUser(string text) { if (TryParseUser(text, out ulong id)) return id; - throw new ArgumentException("Invalid mention format", nameof(text)); + throw new ArgumentException("Invalid mention format.", nameof(text)); } - /// Tries to parse a provided user mention string. + /// + /// Tries to parse a provided user mention string. + /// public static bool TryParseUser(string text, out ulong userId) { if (text.Length >= 3 && text[0] == '<' && text[1] == '@' && text[text.Length - 1] == '>') @@ -40,14 +66,19 @@ namespace Discord return false; } - /// Parses a provided channel mention string. + /// + /// Parses a provided channel mention string. + /// + /// Invalid mention format. public static ulong ParseChannel(string text) { if (TryParseChannel(text, out ulong id)) return id; - throw new ArgumentException("Invalid mention format", nameof(text)); + throw new ArgumentException("Invalid mention format.", nameof(text)); } - /// Tries to parse a provided channel mention string. + /// + /// Tries to parse a provided channel mention string. + /// public static bool TryParseChannel(string text, out ulong channelId) { if (text.Length >= 3 && text[0] == '<' && text[1] == '#' && text[text.Length - 1] == '>') @@ -61,14 +92,19 @@ namespace Discord return false; } - /// Parses a provided role mention string. + /// + /// Parses a provided role mention string. + /// + /// Invalid mention format. public static ulong ParseRole(string text) { if (TryParseRole(text, out ulong id)) return id; - throw new ArgumentException("Invalid mention format", nameof(text)); + throw new ArgumentException("Invalid mention format.", nameof(text)); } - /// Tries to parse a provided role mention string. + /// + /// Tries to parse a provided role mention string. + /// public static bool TryParseRole(string text, out ulong roleId) { if (text.Length >= 4 && text[0] == '<' && text[1] == '@' && text[2] == '&' && text[text.Length - 1] == '>') @@ -139,22 +175,22 @@ namespace Discord if (user != null) return $"@{guildUser?.Nickname ?? user?.Username}"; else - return $""; + return ""; case TagHandling.NameNoPrefix: if (user != null) return $"{guildUser?.Nickname ?? user?.Username}"; else - return $""; + return ""; case TagHandling.FullName: if (user != null) return $"@{user.Username}#{user.Discriminator}"; else - return $""; + return ""; case TagHandling.FullNameNoPrefix: if (user != null) return $"{user.Username}#{user.Discriminator}"; else - return $""; + return ""; case TagHandling.Sanitize: if (guildUser != null && guildUser.Nickname == null) return MentionUser($"{SanitizeChar}{tag.Key}", false); @@ -176,13 +212,13 @@ namespace Discord if (channel != null) return $"#{channel.Name}"; else - return $""; + return ""; case TagHandling.NameNoPrefix: case TagHandling.FullNameNoPrefix: if (channel != null) return $"{channel.Name}"; else - return $""; + return ""; case TagHandling.Sanitize: return MentionChannel($"{SanitizeChar}{tag.Key}"); } @@ -201,13 +237,13 @@ namespace Discord if (role != null) return $"@{role.Name}"; else - return $""; + return ""; case TagHandling.NameNoPrefix: case TagHandling.FullNameNoPrefix: if (role != null) return $"{role.Name}"; else - return $""; + return ""; case TagHandling.Sanitize: return MentionRole($"{SanitizeChar}{tag.Key}"); } diff --git a/src/Discord.Net.Core/Utils/Optional.cs b/src/Discord.Net.Core/Utils/Optional.cs index eb3cbdca2..348179699 100644 --- a/src/Discord.Net.Core/Utils/Optional.cs +++ b/src/Discord.Net.Core/Utils/Optional.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace Discord @@ -11,6 +11,7 @@ namespace Discord private readonly T _value; /// Gets the value for this parameter. + /// This property has no value set. public T Value { get diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 300f584e4..569917312 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord { @@ -86,7 +86,7 @@ namespace Discord private static ArgumentException CreateNotEqualException(string name, string msg, T value) { - if (msg == null) return new ArgumentException($"Value may not be equal to {value}", name); + if (msg == null) return new ArgumentException($"Value may not be equal to {value}.", name); else return new ArgumentException(msg, name); } @@ -109,7 +109,7 @@ namespace Discord private static ArgumentException CreateAtLeastException(string name, string msg, T value) { - if (msg == null) return new ArgumentException($"Value must be at least {value}", name); + if (msg == null) return new ArgumentException($"Value must be at least {value}.", name); else return new ArgumentException(msg, name); } @@ -132,7 +132,7 @@ namespace Discord private static ArgumentException CreateGreaterThanException(string name, string msg, T value) { - if (msg == null) return new ArgumentException($"Value must be greater than {value}", name); + if (msg == null) return new ArgumentException($"Value must be greater than {value}.", name); else return new ArgumentException(msg, name); } @@ -155,7 +155,7 @@ namespace Discord private static ArgumentException CreateAtMostException(string name, string msg, T value) { - if (msg == null) return new ArgumentException($"Value must be at most {value}", name); + if (msg == null) return new ArgumentException($"Value must be at most {value}.", name); else return new ArgumentException(msg, name); } @@ -178,11 +178,12 @@ namespace Discord private static ArgumentException CreateLessThanException(string name, string msg, T value) { - if (msg == null) return new ArgumentException($"Value must be less than {value}", name); + if (msg == null) return new ArgumentException($"Value must be less than {value}.", name); else return new ArgumentException(msg, name); } // Bulk Delete + /// Messages are younger than 2 weeks. public static void YoungerThanTwoWeeks(ulong[] collection, string name) { var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14))); @@ -193,12 +194,13 @@ namespace Discord throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); } } + /// The everyone role cannot be assigned to a user. public static void NotEveryoneRole(ulong[] roles, ulong guildId, string name) { for (var i = 0; i < roles.Length; i++) { if (roles[i] == guildId) - throw new ArgumentException($"The everyone role cannot be assigned to a user", name); + throw new ArgumentException("The everyone role cannot be assigned to a user.", name); } } } diff --git a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs index 586307523..a78017ffb 100644 --- a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs +++ b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; using System; diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index db8e2e691..44d19d018 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -24,11 +24,20 @@ namespace Discord.Rest internal API.DiscordRestApiClient ApiClient { get; } internal LogManager LogManager { get; } + /// + /// Gets the login state of the client. + /// public LoginState LoginState { get; private set; } + /// + /// Gets the logged-in user. + /// public ISelfUser CurrentUser { get; protected set; } + /// + /// Gets the type of the authentication token. + /// public TokenType TokenType => ApiClient.AuthTokenType; - /// Creates a new REST-only discord client. + /// Creates a new REST-only Discord client. internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) { ApiClient = client; @@ -48,8 +57,7 @@ namespace Discord.Rest }; ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); } - - /// + public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) { await _stateLock.WaitAsync().ConfigureAwait(false); @@ -87,8 +95,7 @@ namespace Discord.Rest } internal virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.Delay(0); - - /// + public async Task LogoutAsync() { await _stateLock.WaitAsync().ConfigureAwait(false); @@ -130,49 +137,69 @@ namespace Discord.Rest => ClientHelper.GetRecommendShardCountAsync(this, options); //IDiscordClient + /// ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; + /// ISelfUser IDiscordClient.CurrentUser => CurrentUser; + /// Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => throw new NotSupportedException(); + /// Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); + /// Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); + /// Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); + /// Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); + /// Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); + /// Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) => Task.FromResult(null); + /// Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); + /// Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); + /// + /// Creating a guild is not supported with the base client. Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) => throw new NotSupportedException(); + /// Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); + /// Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) => Task.FromResult(null); + /// Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); + /// Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) => Task.FromResult(null); + /// Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) => Task.FromResult(null); + /// Task IDiscordClient.StartAsync() => Task.Delay(0); + /// Task IDiscordClient.StopAsync() => Task.Delay(0); } diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index a1f8ece69..24cabe4d4 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -1,3 +1,4 @@ +using System; using Discord.API.Rest; using System.Collections.Generic; using System.Collections.Immutable; @@ -24,6 +25,7 @@ namespace Discord.Rest return RestChannel.Create(client, model); return null; } + /// Unexpected channel type. public static async Task> GetPrivateChannelsAsync(BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetMyPrivateChannelsAsync(options).ConfigureAwait(false); @@ -150,7 +152,7 @@ namespace Discord.Rest public static async Task GetWebhookAsync(BaseDiscordClient client, ulong id, RequestOptions options) { - var model = await client.ApiClient.GetWebhookAsync(id); + var model = await client.ApiClient.GetWebhookAsync(id).ConfigureAwait(false); if (model != null) return RestWebhook.Create(client, (IGuild)null, model); return null; diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 85e04f962..70a2acd15 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -58,6 +58,9 @@ namespace Discord.API SetBaseUrl(DiscordConfig.APIUrl); } + + /// Unknown OAuth token type. + /// A delegate callback throws an exception. internal void SetBaseUrl(string baseUrl) { RestClient = _restClientProvider(baseUrl); @@ -65,6 +68,7 @@ namespace Discord.API RestClient.SetHeader("user-agent", UserAgent); RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); } + /// Unknown OAuth token type. internal static string GetPrefixedToken(TokenType tokenType, string token) { switch (tokenType) @@ -76,7 +80,7 @@ namespace Discord.API case TokenType.Bearer: return $"Bearer {token}"; default: - throw new ArgumentException("Unknown OAuth token type", nameof(tokenType)); + throw new ArgumentException("Unknown OAuth token type.", nameof(tokenType)); } } internal virtual void Dispose(bool disposing) @@ -401,7 +405,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - await SendAsync("PUT", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options); + await SendAsync("PUT", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options).ConfigureAwait(false); } public async Task RemoveRoleAsync(ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) { @@ -412,7 +416,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options); + await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options).ConfigureAwait(false); } //Channel Messages @@ -463,6 +467,7 @@ namespace Discord.API endpoint = () => $"channels/{channelId}/messages?limit={limit}"; return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } + /// Message content is too long, length must be less or equal to . public async Task CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -471,12 +476,14 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); return await SendJsonAsync("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } + /// Message content is too long, length must be less or equal to . + /// This operation may only be called with a token. public async Task CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null) { if (AuthTokenType != TokenType.Webhook) @@ -488,11 +495,12 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); options = RequestOptions.CreateOrClone(options); return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } + /// Message content is too long, length must be less or equal to . public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -507,6 +515,9 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); return await SendMultipartAsync("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } + + /// Message content is too long, length must be less or equal to . + /// This operation may only be called with a token. public async Task UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, RequestOptions options = null) { if (AuthTokenType != TokenType.Webhook) @@ -559,6 +570,7 @@ namespace Discord.API break; } } + /// Message content is too long, length must be less or equal to . public async Task ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -1082,7 +1094,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options); + return await SendAsync("GET", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options).ConfigureAwait(false); } public async Task CreateGuildEmoteAsync(ulong guildId, Rest.CreateGuildEmoteParams args, RequestOptions options = null) @@ -1094,7 +1106,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("POST", () => $"guilds/{guildId}/emojis", args, ids, options: options); + return await SendJsonAsync("POST", () => $"guilds/{guildId}/emojis", args, ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildEmoteAsync(ulong guildId, ulong emoteId, ModifyGuildEmoteParams args, RequestOptions options = null) @@ -1105,7 +1117,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/emojis/{emoteId}", args, ids, options: options); + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/emojis/{emoteId}", args, ids, options: options).ConfigureAwait(false); } public async Task DeleteGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null) @@ -1115,7 +1127,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - await SendAsync("DELETE", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options); + await SendAsync("DELETE", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options).ConfigureAwait(false); } //Users @@ -1235,7 +1247,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("POST", () => $"channels/{channelId}/webhooks", args, ids, options: options); + return await SendJsonAsync("POST", () => $"channels/{channelId}/webhooks", args, ids, options: options).ConfigureAwait(false); } public async Task GetWebhookAsync(ulong webhookId, RequestOptions options = null) { @@ -1291,6 +1303,7 @@ namespace Discord.API } //Helpers + /// Client is not logged in. protected void CheckState() { if (LoginState != LoginState.LoggedIn) diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 3a596624d..3a19dd1a5 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -22,83 +22,71 @@ namespace Discord.Rest ApiClient.Dispose(); } + /// internal override async Task OnLoginAsync(TokenType tokenType, string token) { var user = await ApiClient.GetMyUserAsync(new RequestOptions { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false); ApiClient.CurrentUserId = user.Id; base.CurrentUser = RestSelfUser.Create(this, user); } + /// internal override Task OnLogoutAsync() { _applicationInfo = null; return Task.Delay(0); } - - /// + public async Task GetApplicationInfoAsync(RequestOptions options = null) { - return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options)); + return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false)); } - /// public Task GetChannelAsync(ulong id, RequestOptions options = null) => ClientHelper.GetChannelAsync(this, id, options); - /// public Task> GetPrivateChannelsAsync(RequestOptions options = null) => ClientHelper.GetPrivateChannelsAsync(this, options); public Task> GetDMChannelsAsync(RequestOptions options = null) => ClientHelper.GetDMChannelsAsync(this, options); public Task> GetGroupChannelsAsync(RequestOptions options = null) => ClientHelper.GetGroupChannelsAsync(this, options); - - /// + public Task> GetConnectionsAsync(RequestOptions options = null) => ClientHelper.GetConnectionsAsync(this, options); - /// public Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) => ClientHelper.GetInviteAsync(this, inviteId, withCount, options); - /// public Task GetGuildAsync(ulong id, RequestOptions options = null) => ClientHelper.GetGuildAsync(this, id, options); - /// public Task GetGuildEmbedAsync(ulong id, RequestOptions options = null) => ClientHelper.GetGuildEmbedAsync(this, id, options); - /// public IAsyncEnumerable> GetGuildSummariesAsync(RequestOptions options = null) => ClientHelper.GetGuildSummariesAsync(this, null, null, options); - /// public IAsyncEnumerable> GetGuildSummariesAsync(ulong fromGuildId, int limit, RequestOptions options = null) => ClientHelper.GetGuildSummariesAsync(this, fromGuildId, limit, options); - /// public Task> GetGuildsAsync(RequestOptions options = null) => ClientHelper.GetGuildsAsync(this, options); - /// public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options); - - /// + public Task GetUserAsync(ulong id, RequestOptions options = null) => ClientHelper.GetUserAsync(this, id, options); - /// public Task GetGuildUserAsync(ulong guildId, ulong id, RequestOptions options = null) => ClientHelper.GetGuildUserAsync(this, guildId, id, options); - - /// + public Task> GetVoiceRegionsAsync(RequestOptions options = null) => ClientHelper.GetVoiceRegionsAsync(this, options); - /// public Task GetVoiceRegionAsync(string id, RequestOptions options = null) => ClientHelper.GetVoiceRegionAsync(this, id, options); - /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ClientHelper.GetWebhookAsync(this, id, options); //IDiscordClient + /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => await GetApplicationInfoAsync(options).ConfigureAwait(false); + /// async Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -106,6 +94,7 @@ namespace Discord.Rest else return null; } + /// async Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -113,6 +102,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -120,6 +110,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -128,12 +119,14 @@ namespace Discord.Rest return ImmutableArray.Create(); } + /// async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync(options).ConfigureAwait(false); async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); + /// async Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -141,6 +134,7 @@ namespace Discord.Rest else return null; } + /// async Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -148,9 +142,11 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); + /// async Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -159,12 +155,15 @@ namespace Discord.Rest return null; } + /// async Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) => await GetVoiceRegionsAsync(options).ConfigureAwait(false); + /// async Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) => await GetVoiceRegionAsync(id, options).ConfigureAwait(false); + /// async Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) - => await GetWebhookAsync(id, options); + => await GetWebhookAsync(id, options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/DiscordRestConfig.cs b/src/Discord.Net.Rest/DiscordRestConfig.cs index 4a7aae287..68fa68e03 100644 --- a/src/Discord.Net.Rest/DiscordRestConfig.cs +++ b/src/Discord.Net.Rest/DiscordRestConfig.cs @@ -1,7 +1,10 @@ -using Discord.Net.Rest; +using Discord.Net.Rest; namespace Discord.Rest { + /// + /// Represents a configuration class for . + /// public class DiscordRestConfig : DiscordConfig { /// Gets or sets the provider used to generate new REST connections. diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs index 4b9d5875f..51a859376 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs @@ -1,10 +1,13 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to a ban. + /// public class BanAuditLogData : IAuditLogData { private BanAuditLogData(IUser user) @@ -18,6 +21,12 @@ namespace Discord.Rest return new BanAuditLogData(RestUser.Create(discord, userInfo)); } + /// + /// Gets the user that was banned. + /// + /// + /// A generic object representing the banned user. + /// public IUser Target { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs index ef4787295..451aac373 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs @@ -1,4 +1,3 @@ -using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Linq; @@ -7,6 +6,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to a channel creation. + /// public class ChannelCreateAuditLogData : IAuditLogData { private ChannelCreateAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) @@ -44,9 +46,33 @@ namespace Discord.Rest return new ChannelCreateAuditLogData(entry.TargetId.Value, name, type, overwrites.ToReadOnlyCollection()); } + /// + /// Gets the snowflake ID of the created channel. + /// + /// + /// An representing the snowflake identifier for the created channel. + /// public ulong ChannelId { get; } + /// + /// Gets the name of the created channel. + /// + /// + /// A string containing the name of the created channel. + /// public string ChannelName { get; } + /// + /// Gets the type of the created channel. + /// + /// + /// The type of channel that was created. + /// public ChannelType ChannelType { get; } + /// + /// Gets a collection of permission overwrites that was assigned to the created channel. + /// + /// + /// A collection of permission . + /// public IReadOnlyCollection Overwrites { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs index 4816ce770..7278b7b75 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs @@ -1,14 +1,14 @@ -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to a channel deletion. + /// public class ChannelDeleteAuditLogData : IAuditLogData { private ChannelDeleteAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) @@ -37,9 +37,33 @@ namespace Discord.Rest return new ChannelDeleteAuditLogData(id, name, type, overwrites.ToReadOnlyCollection()); } + /// + /// Gets the snowflake ID of the deleted channel. + /// + /// + /// An representing the snowflake identifier for the deleted channel. + /// public ulong ChannelId { get; } + /// + /// Gets the name of the deleted channel. + /// + /// + /// A string containing the name of the deleted channel. + /// public string ChannelName { get; } + /// + /// Gets the type of the deleted channel. + /// + /// + /// The type of channel that was deleted. + /// public ChannelType ChannelType { get; } + /// + /// Gets a collection of permission overwrites that was assigned to the deleted channel. + /// + /// + /// A collection of permission . + /// public IReadOnlyCollection Overwrites { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs index e2d6064a9..82b129656 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs @@ -1,5 +1,8 @@ namespace Discord.Rest { + /// + /// Represents information for a channel. + /// public struct ChannelInfo { internal ChannelInfo(string name, string topic, int? bitrate, int? limit) @@ -10,9 +13,35 @@ namespace Discord.Rest UserLimit = limit; } + /// + /// Gets the name of this channel. + /// + /// + /// A string containing the name of this channel. + /// public string Name { get; } + /// + /// Gets the topic of this channel. + /// + /// + /// A string containing the topic of this channel, if any. + /// public string Topic { get; } + /// + /// Gets the bitrate of this channel if applicable. + /// + /// + /// An representing the bitrate set for the voice channel; null if not + /// applicable. + /// public int? Bitrate { get; } + /// + /// Gets the number of users allowed to be in this channel if applicable. + /// + /// + /// An representing the number of users allowed to be in this voice channel; + /// null if not applicable. + /// public int? UserLimit { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs index 491cb5717..7cad9eff7 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -1,10 +1,13 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to a channel update. + /// public class ChannelUpdateAuditLogData : IAuditLogData { private ChannelUpdateAuditLogData(ulong id, ChannelInfo before, ChannelInfo after) @@ -38,6 +41,12 @@ namespace Discord.Rest return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after); } + /// + /// Gets the snowflake ID of the updated channel. + /// + /// + /// An representing the snowflake identifier for the updated channel. + /// public ulong ChannelId { get; } public ChannelInfo Before { get; } public ChannelInfo After { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs index 5d1ef8463..a114548cc 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs @@ -1,14 +1,13 @@ -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to an emoji creation. + /// public class EmoteCreateAuditLogData : IAuditLogData { private EmoteCreateAuditLogData(ulong id, string name) @@ -25,7 +24,19 @@ namespace Discord.Rest return new EmoteCreateAuditLogData(entry.TargetId.Value, emoteName); } + /// + /// Gets the snowflake ID of the created emoji. + /// + /// + /// An representing the snowflake identifier for the created emoji. + /// public ulong EmoteId { get; } + /// + /// Gets the name of the created emoji. + /// + /// + /// A string containing the name of the created emoji. + /// public string Name { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs index d0a11191f..e522b0ee2 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs @@ -1,10 +1,13 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to an emoji deletion. + /// public class EmoteDeleteAuditLogData : IAuditLogData { private EmoteDeleteAuditLogData(ulong id, string name) @@ -22,7 +25,19 @@ namespace Discord.Rest return new EmoteDeleteAuditLogData(entry.TargetId.Value, emoteName); } + /// + /// Gets the snowflake ID of the deleted emoji. + /// + /// + /// An representing the snowflake identifier for the deleted emoji. + /// public ulong EmoteId { get; } + /// + /// Gets the name of the deleted emoji. + /// + /// + /// A string containing the name of the deleted emoji. + /// public string Name { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs index 60020bcaa..e8cd76513 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs @@ -1,10 +1,13 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to an emoji update. + /// public class EmoteUpdateAuditLogData : IAuditLogData { private EmoteUpdateAuditLogData(ulong id, string oldName, string newName) @@ -24,8 +27,26 @@ namespace Discord.Rest return new EmoteUpdateAuditLogData(entry.TargetId.Value, oldName, newName); } + /// + /// Gets the snowflake ID of the updated emoji. + /// + /// + /// An representing the snowflake identifier of the updated emoji. + /// public ulong EmoteId { get; } + /// + /// Gets the new name of the updated emoji. + /// + /// + /// A string containing the new name of the updated emoji. + /// public string NewName { get; } + /// + /// Gets the old name of the updated emoji. + /// + /// + /// A string containing the old name of the updated emoji. + /// public string OldName { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs index 90865ef72..057930c82 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs @@ -1,5 +1,8 @@ namespace Discord.Rest { + /// + /// Represents information for a guild. + /// public struct GuildInfo { internal GuildInfo(int? afkTimeout, DefaultMessageNotifications? defaultNotifs, @@ -18,14 +21,66 @@ namespace Discord.Rest ContentFilterLevel = filter; } + /// + /// Gets the amount of time (in seconds) a user must be inactive in a voice channel for until they are + /// automatically moved to the AFK voice channel. + /// + /// + /// An representing the amount of time in seconds for a user to be marked as inactive + /// and moved into the AFK voice channel. + /// public int? AfkTimeout { get; } + /// + /// Gets the default message notifications for users who haven't explicitly set their notification settings. + /// public DefaultMessageNotifications? DefaultMessageNotifications { get; } + /// + /// Gets the ID of the AFK voice channel for this guild. + /// + /// + /// An representing the snowflake identifier of the AFK voice channel; null if + /// none is set. + /// public ulong? AfkChannelId { get; } + /// + /// Gets the name of this guild. + /// + /// + /// A string containing the name of this guild. + /// public string Name { get; } + /// + /// Gets the ID of the region hosting this guild's voice channels. + /// public string RegionId { get; } + /// + /// Gets the ID of this guild's icon. + /// + /// + /// A string containing the identifier for the splash image; null if none is set. + /// public string IconHash { get; } + /// + /// Gets the level of requirements a user must fulfill before being allowed to post messages in this guild. + /// + /// + /// The level of requirements. + /// public VerificationLevel? VerificationLevel { get; } + /// + /// Gets the owner of this guild. + /// + /// + /// A generic object representing the owner of this guild. + /// public IUser Owner { get; } + /// + /// Gets the level of Multi-Factor Authentication requirements a user must fulfill before being allowed to + /// perform administrative actions in this guild. + /// + /// + /// The level of MFA requirement. + /// public MfaLevel? MfaLevel { get; } public int? ContentFilterLevel { get; } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs index 08550ed7a..b4ffd3bf1 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs @@ -1,10 +1,13 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to a guild update. + /// public class GuildUpdateAuditLogData : IAuditLogData { private GuildUpdateAuditLogData(GuildInfo before, GuildInfo after) @@ -73,7 +76,19 @@ namespace Discord.Rest return new GuildUpdateAuditLogData(before, after); } + /// + /// Gets the guild information before the changes. + /// + /// + /// An information object containing the original guild information before the changes were made. + /// public GuildInfo Before { get; } + /// + /// Gets the guild information after the changes. + /// + /// + /// An information object containing the guild information after the changes were made. + /// public GuildInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs index 292715420..c072686f5 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs @@ -1,10 +1,13 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to an invite creation. + /// public class InviteCreateAuditLogData : IAuditLogData { private InviteCreateAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) @@ -44,12 +47,57 @@ namespace Discord.Rest return new InviteCreateAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); } + /// + /// Gets the time (in seconds) until the invite expires. + /// + /// + /// An representing the time in seconds until this invite expires. + /// public int MaxAge { get; } + /// + /// Gets the unique identifier for this invite. + /// + /// + /// A string containing the invite code (e.g. FTqNnyS). + /// public string Code { get; } + /// + /// Determines whether the invite is a temporary one (i.e. whether the invite will be removed from the guild + /// when the user logs off). + /// + /// + /// true if users accepting this invite will be removed from the guild when they log off; otherwise + /// false. + /// public bool Temporary { get; } + /// + /// Gets the user that created this invite. + /// + /// + /// A generic that created this invite. + /// public IUser Creator { get; } + /// + /// Gets the ID of the channel this invite is linked to. + /// + /// + /// An representing the channel snowflake identifier that the invite points to. + /// public ulong ChannelId { get; } + /// + /// Gets the number of times this invite has been used. + /// + /// + /// An representing the number of times this invite has been used. + /// public int Uses { get; } + /// + /// Gets the max number of uses this invite may have. + /// + /// + /// An representing the number of uses this invite may be accepted until it is removed + /// from the guild; null if none is set. + /// public int MaxUses { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs index 1dc6d518b..ef5a26c11 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs @@ -1,10 +1,13 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a piece of audit log data related to an invite removal. + /// public class InviteDeleteAuditLogData : IAuditLogData { private InviteDeleteAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) @@ -44,12 +47,57 @@ namespace Discord.Rest return new InviteDeleteAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); } + /// + /// Gets the time (in seconds) until the invite expires. + /// + /// + /// An representing the time in seconds until this invite expires. + /// public int MaxAge { get; } + /// + /// Gets the unique identifier for this invite. + /// + /// + /// A string containing the invite code (e.g. FTqNnyS). + /// public string Code { get; } + /// + /// Determines whether the invite is a temporary one (i.e. whether the invite will be removed from the guild + /// when the user logs off). + /// + /// + /// true if users accepting this invite will be removed from the guild when they log off; otherwise + /// false. + /// public bool Temporary { get; } + /// + /// Gets the user that created this invite. + /// + /// + /// A generic that created this invite. + /// public IUser Creator { get; } + /// + /// Gets the ID of the channel this invite is linked to. + /// + /// + /// An representing the channel snowflake identifier that the invite points to. + /// public ulong ChannelId { get; } + /// + /// Gets the number of times this invite has been used. + /// + /// + /// An representing the number of times this invite has been used. + /// public int Uses { get; } + /// + /// Gets the max number of uses this invite may have. + /// + /// + /// An representing the number of uses this invite may be accepted until it is removed + /// from the guild; null if none is set. + /// public int MaxUses { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs index c9840f6cc..54026dbf3 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs @@ -1,5 +1,8 @@ namespace Discord.Rest { + /// + /// Represents information for an invite. + /// public struct InviteInfo { internal InviteInfo(int? maxAge, string code, bool? temporary, ulong? channelId, int? maxUses) @@ -11,10 +14,45 @@ namespace Discord.Rest MaxUses = maxUses; } + /// + /// Gets the time (in seconds) until the invite expires. + /// + /// + /// An representing the time in seconds until this invite expires; null if this + /// invite never expires or not specified. + /// public int? MaxAge { get; } + /// + /// Gets the unique identifier for this invite. + /// + /// + /// A string containing the invite code (e.g. FTqNnyS). + /// public string Code { get; } + /// + /// Determines whether the invite is a temporary one (i.e. whether the invite will be removed from the guild + /// when the user logs off). + /// + /// + /// true if users accepting this invite will be removed from the guild when they log off, + /// false if not; null if not specified. + /// public bool? Temporary { get; } + /// + /// Gets the ID of the channel this invite is linked to. + /// + /// + /// An representing the channel snowflake identifier that the invite points to; + /// null if not specified. + /// public ulong? ChannelId { get; } + /// + /// Gets the max number of uses this invite may have. + /// + /// + /// An representing the number of uses this invite may be accepted until it is removed + /// from the guild; null if none is specified. + /// public int? MaxUses { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs index 3bcbce440..0d9a2a708 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs index 38f078848..7d3d3dba0 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs @@ -1,8 +1,7 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; -using ChangeModel = Discord.API.AuditLogChange; namespace Discord.Rest { diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs index 445c2e302..dc2f4a6f0 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; -using ChangeModel = Discord.API.AuditLogChange; -using OptionModel = Discord.API.AuditLogOptions; namespace Discord.Rest { diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs index 4133d5dff..2d536d868 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs index 54da42a8b..7db7aee36 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs index 9e30a5014..0cf616973 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -1,10 +1,13 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Represents a REST-based audit log entry. + /// public class RestAuditLogEntry : RestEntity, IAuditLogEntry { private RestAuditLogEntry(BaseDiscordClient discord, Model fullLog, EntryModel model, IUser user) diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 4047b7014..b8089824d 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; using UserModel = Discord.API.User; -using WebhookModel = Discord.API.Webhook; namespace Discord.Rest { @@ -77,14 +76,8 @@ namespace Discord.Rest int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) { var args = new CreateChannelInviteParams { IsTemporary = isTemporary, IsUnique = isUnique }; - if (maxAge.HasValue) - args.MaxAge = maxAge.Value; - else - args.MaxAge = 0; - if (maxUses.HasValue) - args.MaxUses = maxUses.Value; - else - args.MaxUses = 0; + args.MaxAge = maxAge.GetValueOrDefault(0); + args.MaxUses = maxUses.GetValueOrDefault(0); var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options).ConfigureAwait(false); return RestInviteMetadata.Create(client, null, channel, model); } @@ -160,6 +153,7 @@ namespace Discord.Rest return builder.ToImmutable(); } + /// Message content is too long, length must be less or equal to . public static async Task SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, string text, bool isTTS, Embed embed, RequestOptions options) { @@ -168,6 +162,30 @@ namespace Discord.Rest return RestUserMessage.Create(client, channel, client.CurrentUser, model); } + /// + /// is a zero-length string, contains only white space, or contains one or more + /// invalid characters as defined by . + /// + /// + /// is null. + /// + /// + /// 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. + /// + /// + /// The specified path is invalid, (for example, it is on an unmapped drive). + /// + /// + /// specified a directory.-or- The caller does not have the required permission. + /// + /// + /// The file specified in was not found. + /// + /// is in an invalid format. + /// An I/O error occurred while opening the file. + /// Message content is too long, length must be less or equal to . public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, string filePath, string text, bool isTTS, Embed embed, RequestOptions options) { @@ -176,6 +194,7 @@ namespace Discord.Rest return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options).ConfigureAwait(false); } + /// Message content is too long, length must be less or equal to . public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) { @@ -240,6 +259,7 @@ namespace Discord.Rest } //Users + /// Resolving permissions requires the parent guild to be downloaded. public static async Task GetUserAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) { @@ -252,6 +272,7 @@ namespace Discord.Rest return user; } + /// Resolving permissions requires the parent guild to be downloaded. public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, ulong? fromUserId, int? limit, RequestOptions options) { @@ -291,7 +312,7 @@ namespace Discord.Rest } public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, RequestOptions options) - => new TypingNotifier(client, channel, options); + => new TypingNotifier(channel, options); //Webhooks public static async Task CreateWebhookAsync(ITextChannel channel, BaseDiscordClient client, string name, Stream avatar, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelType.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelType.cs index e9f069a50..7759622c2 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelType.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelType.cs @@ -1,11 +1,17 @@ -namespace Discord +namespace Discord { + /// Defines the types of channels. public enum ChannelType { + /// The channel is a text channel. Text = 0, + /// The channel is a Direct Message channel. DM = 1, + /// The channel is a voice channel. Voice = 2, + /// The channel is a group channel. Group = 3, + /// The channel is a category channel. Category = 4 } } diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index e0095c7b1..ac2f27bbd 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -4,25 +4,108 @@ using System.Threading.Tasks; namespace Discord.Rest { + /// + /// Represents a REST-based channel that can send and receive messages. + /// public interface IRestMessageChannel : IMessageChannel { - /// Sends a message to this message channel. + /// + /// Sends a message to this message channel. + /// + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); - /// Sends a file to this text channel, with an optional caption. + /// + /// Sends a file to this message channel, with an optional caption. + /// + /// The file path of the file. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); - - /// Sends a file to this text channel, with an optional caption. + /// + /// Sends a file to this message channel, with an optional caption. + /// + /// The of the file to be sent. + /// The name of the attachment. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); - /// Gets a message from this message channel with the given id, or null if not found. + /// + /// Gets a message from this message channel with the given id, or null if not found. + /// + /// The ID of the message. + /// The options to be used when sending the request. + /// + /// The message gotten from either the cache or the download, or null if none is found. + /// Task GetMessageAsync(ulong id, RequestOptions options = null); - /// Gets the last N messages from this message channel. + /// + /// Gets the last N messages from this message channel. + /// + /// The numbers of message to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. Flattening the paginated response into a collection of messages with + /// is required if you wish to access the messages. + /// IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); - /// Gets a collection of messages in this channel. + /// + /// Gets a collection of messages in this channel. + /// + /// The ID of the starting message to get the messages from. + /// The direction of the messages to be gotten from. + /// The numbers of message to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. Flattening the paginated response into a collection of messages with + /// is required if you wish to access the messages. + /// IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); - /// Gets a collection of messages in this channel. + /// + /// Gets a collection of messages in this channel. + /// + /// The starting message to get the messages from. + /// The direction of the messages to be gotten from. + /// The numbers of message to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. Flattening the paginated response into a collection of messages with + /// is required if you wish to access the messages. + /// IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); - /// Gets a collection of pinned messages in this channel. + /// + /// Gets a collection of pinned messages in this channel. + /// + /// The options to be used when sending the request. + /// + /// An awaitable Task containing a collection of messages. + /// new Task> GetPinnedMessagesAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestPrivateChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestPrivateChannel.cs index a6939f81e..f387ac2d4 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestPrivateChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestPrivateChannel.cs @@ -1,9 +1,15 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Discord.Rest { + /// + /// Represents a REST-based channel that is private to select recipients. + /// public interface IRestPrivateChannel : IPrivateChannel { + /// + /// Users that can access this channel. + /// new IReadOnlyCollection Recipients { get; } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs index 321f1f1d2..9d69d6bdc 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; namespace Discord.Rest { + /// + /// Represents a REST-based category channel. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestCategoryChannel : RestGuildChannel, ICategoryChannel { @@ -25,14 +26,22 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Name} ({Id}, Category)"; // IGuildChannel + /// + /// This method is not supported with category channels. Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) => throw new NotSupportedException(); + /// + /// This method is not supported with category channels. Task> IGuildChannel.GetInvitesAsync(RequestOptions options) => throw new NotSupportedException(); //IChannel + /// + /// This method is not supported with category channels. IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => throw new NotSupportedException(); + /// + /// This method is not supported with category channels. Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => throw new NotSupportedException(); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 5860d8283..dd190199f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -6,14 +6,19 @@ using Model = Discord.API.Channel; namespace Discord.Rest { + /// + /// Represents a generic REST-based channel. + /// public class RestChannel : RestEntity, IChannel, IUpdateable { + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); internal RestChannel(BaseDiscordClient discord, ulong id) : base(discord, id) { } + /// Unexpected channel type. internal static RestChannel Create(BaseDiscordClient discord, Model model) { switch (model.Type) @@ -30,6 +35,7 @@ namespace Discord.Rest return new RestChannel(discord, model.Id); } } + /// Unexpected channel type. internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model) { switch (model.Type) @@ -44,13 +50,17 @@ namespace Discord.Rest } internal virtual void Update(Model model) { } + /// public virtual Task UpdateAsync(RequestOptions options = null) => Task.Delay(0); //IChannel + /// string IChannel.Name => null; + /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overridden } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 64efcf24b..eb6fe9105 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -9,11 +9,14 @@ using Model = Discord.API.Channel; namespace Discord.Rest { + /// + /// Represents a REST-based direct-message channel. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel, IUpdateable + public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel { - public RestUser CurrentUser { get; private set; } - public RestUser Recipient { get; private set; } + public RestUser CurrentUser { get; } + public RestUser Recipient { get; } public IReadOnlyCollection Users => ImmutableArray.Create(CurrentUser, Recipient); @@ -34,11 +37,13 @@ namespace Discord.Rest Recipient.Update(model.Recipients.Value[0]); } + /// public override async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); Update(model); } + /// public Task CloseAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); @@ -52,31 +57,67 @@ namespace Discord.Rest return null; } + /// public Task GetMessageAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetMessageAsync(this, Discord, id, options); + /// public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); + /// public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); + /// public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + /// public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + /// + /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + /// + /// + /// is a zero-length string, contains only white space, or contains one or more + /// invalid characters as defined by . + /// + /// + /// is null. + /// + /// + /// 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. + /// + /// + /// The specified path is invalid, (for example, it is on an unmapped drive). + /// + /// + /// specified a directory.-or- The caller does not have the required permission. + /// + /// + /// The file specified in was not found. + /// + /// is in an invalid format. + /// An I/O error occurred while opening the file. + /// Message content is too long, length must be less or equal to . public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - + /// + /// Message content is too long, length must be less or equal to . public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + /// public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + /// public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) @@ -85,16 +126,20 @@ namespace Discord.Rest public override string ToString() => $"@{Recipient}"; private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; - //IDMChannel + //IDMChannel + /// IUser IDMChannel.Recipient => Recipient; //IRestPrivateChannel + /// IReadOnlyCollection IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient); //IPrivateChannel + /// IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); //IMessageChannel + /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -102,6 +147,7 @@ namespace Discord.Rest else return null; } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -109,6 +155,7 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -116,6 +163,7 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -123,6 +171,7 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + /// async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); @@ -131,16 +180,21 @@ namespace Discord.Rest async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + /// async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + /// IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); //IChannel + /// string IChannel.Name => $"@{Recipient}"; + /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 367abfb4a..2f14dc94f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -11,11 +11,12 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel, IUpdateable + public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel { private string _iconId; private ImmutableDictionary _users; + /// public string Name { get; private set; } public IReadOnlyCollection Users => _users.ToReadOnlyCollection(); @@ -49,12 +50,13 @@ namespace Discord.Rest users[models[i].Id] = RestGroupUser.Create(Discord, models[i]); _users = users.ToImmutable(); } - + /// public override async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); Update(model); } + /// public Task LeaveAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); @@ -65,31 +67,67 @@ namespace Discord.Rest return null; } + /// public Task GetMessageAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetMessageAsync(this, Discord, id, options); + /// public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); + /// public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); + /// public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + /// public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + /// public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + /// public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// + /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + /// + /// + /// is a zero-length string, contains only white space, or contains one or more + /// invalid characters as defined by . + /// + /// + /// is null. + /// + /// + /// 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. + /// + /// + /// The specified path is invalid, (for example, it is on an unmapped drive). + /// + /// + /// specified a directory.-or- The caller does not have the required permission. + /// + /// + /// The file specified in was not found. + /// + /// is in an invalid format. + /// An I/O error occurred while opening the file. + /// Message content is too long, length must be less or equal to . public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - + /// + /// Message content is too long, length must be less or equal to . public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) @@ -147,7 +185,10 @@ namespace Discord.Rest => EnterTypingState(options); //IAudioChannel - Task IAudioChannel.ConnectAsync(Action configAction) { throw new NotSupportedException(); } + /// + /// Connecting to a group channel is not supported. + Task IAudioChannel.ConnectAsync(Action configAction) => + throw new NotSupportedException(); //IChannel Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 7355e3673..cc52a9864 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -7,15 +7,22 @@ using Model = Discord.API.Channel; namespace Discord.Rest { - public class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable + /// + /// Represents a private REST-based group channel. + /// + public class RestGuildChannel : RestChannel, IGuildChannel { private ImmutableArray _overwrites; + /// public IReadOnlyCollection PermissionOverwrites => _overwrites; internal IGuild Guild { get; } + /// public string Name { get; private set; } + /// public int Position { get; private set; } + /// public ulong GuildId => Guild.Id; internal RestGuildChannel(BaseDiscordClient discord, IGuild guild, ulong id) @@ -49,16 +56,19 @@ namespace Discord.Rest _overwrites = newOverwrites.ToImmutable(); } + /// public override async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetChannelAsync(GuildId, Id, options).ConfigureAwait(false); Update(model); } + /// public async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } + /// public Task DeleteAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); @@ -125,6 +135,7 @@ namespace Discord.Rest public override string ToString() => Name; //IGuildChannel + /// IGuild IGuildChannel.Guild { get @@ -135,32 +146,44 @@ namespace Discord.Rest } } + /// async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) => await GetInvitesAsync(options).ConfigureAwait(false); + /// async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) => await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); + /// OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) => GetPermissionOverwrite(role); + /// OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) => GetPermissionOverwrite(user); + /// async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); + /// async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); + /// async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); + /// async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); + /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overridden //Overridden in Text/Voice + /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden in Text/Voice //IChannel + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overridden in Text/Voice + /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden in Text/Voice } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index a08585cd8..5321cc36c 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -8,15 +8,21 @@ using Model = Discord.API.Channel; namespace Discord.Rest { + /// + /// Represents a REST-based channel in a guild that can send and receive messages. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel { + /// public string Topic { get; private set; } public ulong? CategoryId { get; private set; } + /// public string Mention => MentionUtils.MentionChannel(Id); private bool _nsfw; + /// public bool IsNsfw => _nsfw || ChannelHelper.IsNsfw(this); internal RestTextChannel(BaseDiscordClient discord, IGuild guild, ulong id) @@ -37,6 +43,7 @@ namespace Discord.Rest _nsfw = model.Nsfw.GetValueOrDefault(); } + /// public async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); @@ -47,42 +54,81 @@ namespace Discord.Rest => ChannelHelper.GetUserAsync(this, Guild, Discord, id, options); public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) => ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); - + + /// public Task GetMessageAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetMessageAsync(this, Discord, id, options); + /// public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); + /// public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); + /// public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + /// public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + /// + /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + /// + /// + /// is a zero-length string, contains only white space, or contains one or more + /// invalid characters as defined by . + /// + /// + /// is null. + /// + /// + /// 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. + /// + /// + /// The specified path is invalid, (for example, it is on an unmapped drive). + /// + /// + /// specified a directory.-or- The caller does not have the required permission. + /// + /// + /// The file specified in was not found. + /// + /// is in an invalid format. + /// An I/O error occurred while opening the file. + /// Message content is too long, length must be less or equal to . public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); + /// + /// Message content is too long, length must be less or equal to . public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + /// public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + /// public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + /// public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); - + public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); public Task GetWebhookAsync(ulong id, RequestOptions options = null) @@ -96,14 +142,18 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Name} ({Id}, Text)"; //ITextChannel + /// async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options) => await CreateWebhookAsync(name, avatar, options).ConfigureAwait(false); + /// async Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options) => await GetWebhookAsync(id, options).ConfigureAwait(false); + /// async Task> ITextChannel.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options).ConfigureAwait(false); //IMessageChannel + /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -111,6 +161,7 @@ namespace Discord.Rest else return null; } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -119,6 +170,7 @@ namespace Discord.Rest return AsyncEnumerable.Empty>(); } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -126,6 +178,7 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -133,20 +186,26 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + /// async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); + /// async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); + /// async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + /// async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + /// IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); //IGuildChannel + /// async Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -154,6 +213,7 @@ namespace Discord.Rest else return null; } + /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -163,6 +223,7 @@ namespace Discord.Rest } //IChannel + /// async Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -170,6 +231,7 @@ namespace Discord.Rest else return null; } + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index a2bead45f..07da22235 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -8,10 +8,15 @@ using Model = Discord.API.Channel; namespace Discord.Rest { + /// + /// Represents a REST-based voice channel in a guild. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestVoiceChannel : RestGuildChannel, IVoiceChannel, IRestAudioChannel { + /// public int Bitrate { get; private set; } + /// public int? UserLimit { get; private set; } public ulong? CategoryId { get; private set; } @@ -25,6 +30,7 @@ namespace Discord.Rest entity.Update(model); return entity; } + /// internal override void Update(Model model) { base.Update(model); @@ -33,6 +39,7 @@ namespace Discord.Rest UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; } + /// public async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); @@ -45,11 +52,15 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; //IAudioChannel - Task IAudioChannel.ConnectAsync(Action configAction) { throw new NotSupportedException(); } + /// + /// Connecting to a REST-based channel is not supported. + Task IAudioChannel.ConnectAsync(Action configAction) => throw new NotSupportedException(); //IGuildChannel + /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); + /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index e8b939e65..0643ecf6c 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -57,7 +57,7 @@ namespace Discord.Rest async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return null; } @@ -83,27 +83,26 @@ namespace Discord.Rest return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) - => await GetPinnedMessagesAsync(options); + => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, embed, options); + => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) - => await SendMessageAsync(text, isTTS, embed, options); + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); //IChannel - string IChannel.Name { get { throw new NotSupportedException(); } } - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - { + string IChannel.Name => throw new NotSupportedException(); - } - Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - { + + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => + throw new NotSupportedException(); + + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => throw new NotSupportedException(); - } } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index c76b41a91..195e6f3c0 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -14,10 +14,11 @@ namespace Discord.Rest internal static class GuildHelper { //General + /// is null. public static async Task ModifyAsync(IGuild guild, BaseDiscordClient client, Action func, RequestOptions options) { - if (func == null) throw new NullReferenceException(nameof(func)); + if (func == null) throw new ArgumentNullException(nameof(func)); var args = new GuildProperties(); func(args); @@ -31,7 +32,6 @@ namespace Discord.Rest Icon = args.Icon.IsSpecified ? args.Icon.Value?.ToModel() : Optional.Create(), Name = args.Name, Splash = args.Splash.IsSpecified ? args.Splash.Value?.ToModel() : Optional.Create(), - Username = args.Username, VerificationLevel = args.VerificationLevel }; @@ -62,10 +62,11 @@ namespace Discord.Rest return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); } + /// is null. public static async Task ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, Action func, RequestOptions options) { - if (func == null) throw new NullReferenceException(nameof(func)); + if (func == null) throw new ArgumentNullException(nameof(func)); var args = new GuildEmbedProperties(); func(args); @@ -144,6 +145,7 @@ namespace Discord.Rest var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestGuildChannel.Create(client, guild, x)).ToImmutableArray(); } + /// is null. public static async Task CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options, Action func = null) { @@ -161,6 +163,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestTextChannel.Create(client, guild, model); } + /// is null. public static async Task CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options, Action func = null) { @@ -178,6 +181,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestVoiceChannel.Create(client, guild, model); } + /// is null. public static async Task CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options) { @@ -212,6 +216,7 @@ namespace Discord.Rest } //Roles + /// is null. public static async Task CreateRoleAsync(IGuild guild, BaseDiscordClient client, string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) { @@ -258,7 +263,7 @@ namespace Discord.Rest }; if (info.Position != null) args.AfterUserId = info.Position.Value; - var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args, options); + var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args, options).ConfigureAwait(false); return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray(); }, nextPage: (info, lastPage) => @@ -330,7 +335,7 @@ namespace Discord.Rest //Emotes public static async Task GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) { - var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options); + var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options).ConfigureAwait(false); return emote.ToEntity(); } public static async Task CreateEmoteAsync(IGuild guild, BaseDiscordClient client, string name, Image image, Optional> roles, @@ -342,11 +347,12 @@ namespace Discord.Rest Image = image.ToModel() }; if (roles.IsSpecified) - apiargs.RoleIds = roles.Value?.Select(xr => xr.Id)?.ToArray(); + apiargs.RoleIds = roles.Value?.Select(xr => xr.Id).ToArray(); - var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options); + var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options).ConfigureAwait(false); return emote.ToEntity(); } + /// is null. public static async Task ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action func, RequestOptions options) { @@ -360,9 +366,9 @@ namespace Discord.Rest Name = props.Name }; if (props.Roles.IsSpecified) - apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id)?.ToArray(); + apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id).ToArray(); - var emote = await client.ApiClient.ModifyGuildEmoteAsync(guild.Id, id, apiargs, options); + var emote = await client.ApiClient.ModifyGuildEmoteAsync(guild.Id, id, apiargs, options).ConfigureAwait(false); return emote.ToEntity(); } public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs index 104bec903..ec8f60ae5 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs @@ -1,12 +1,22 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.Ban; namespace Discord.Rest { + /// + /// Represents a REST-based ban object. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestBan : IBan { + /// + /// Gets the banned user. + /// + /// + /// A generic object that was banned. + /// public RestUser User { get; } + /// public string Reason { get; } internal RestBan(RestUser user, string reason) @@ -19,10 +29,17 @@ namespace Discord.Rest return new RestBan(RestUser.Create(client, model.User), model.Reason); } + /// + /// Gets the name of the banned user. + /// + /// + /// A string containing the name of the user that was banned. + /// public override string ToString() => User.ToString(); private string DebuggerDisplay => $"{User}: {Reason}"; //IBan + /// IUser IBan.User => User; } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 357e22c85..e003e2130 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -10,6 +10,9 @@ using Model = Discord.API.Guild; namespace Discord.Rest { + /// + /// Represents a REST-based guild/server. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestGuild : RestEntity, IGuild, IUpdateable { @@ -17,32 +20,50 @@ namespace Discord.Rest private ImmutableArray _emotes; private ImmutableArray _features; + /// public string Name { get; private set; } + /// public int AFKTimeout { get; private set; } + /// public bool IsEmbeddable { get; private set; } + /// public VerificationLevel VerificationLevel { get; private set; } + /// public MfaLevel MfaLevel { get; private set; } + /// public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } + /// public ulong? AFKChannelId { get; private set; } + /// public ulong? EmbedChannelId { get; private set; } + /// public ulong? SystemChannelId { get; private set; } + /// public ulong OwnerId { get; private set; } + /// public string VoiceRegionId { get; private set; } + /// public string IconId { get; private set; } + /// public string SplashId { get; private set; } internal bool Available { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); [Obsolete("DefaultChannelId is deprecated, use GetDefaultChannelAsync")] public ulong DefaultChannelId => Id; + /// public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); + /// public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); public RestRole EveryoneRole => GetRole(Id); public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); + /// public IReadOnlyCollection Emotes => _emotes; + /// public IReadOnlyCollection Features => _features; internal RestGuild(BaseDiscordClient client, ulong id) @@ -103,37 +124,48 @@ namespace Discord.Rest } //General + /// public async Task UpdateAsync(RequestOptions options = null) => Update(await Discord.ApiClient.GetGuildAsync(Id, options).ConfigureAwait(false)); + /// public Task DeleteAsync(RequestOptions options = null) => GuildHelper.DeleteAsync(this, Discord, options); + /// + /// is null. public async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await GuildHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } + + /// + /// is null. public async Task ModifyEmbedAsync(Action func, RequestOptions options = null) { var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } + + /// + /// is null. public async Task ReorderChannelsAsync(IEnumerable args, RequestOptions options = null) { var arr = args.ToArray(); - await GuildHelper.ReorderChannelsAsync(this, Discord, arr, options); + await GuildHelper.ReorderChannelsAsync(this, Discord, arr, options).ConfigureAwait(false); } + /// public async Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null) { var models = await GuildHelper.ReorderRolesAsync(this, Discord, args, options).ConfigureAwait(false); foreach (var model in models) { var role = GetRole(model.Id); - if (role != null) - role.Update(model); + role?.Update(model); } } - + + /// public Task LeaveAsync(RequestOptions options = null) => GuildHelper.LeaveAsync(this, Discord, options); @@ -145,13 +177,17 @@ namespace Discord.Rest public Task GetBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.GetBanAsync(this, Discord, userId, options); + /// public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); + /// public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); + /// public Task RemoveBanAsync(IUser user, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); + /// public Task RemoveBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, userId, options); @@ -265,6 +301,7 @@ namespace Discord.Rest public Task GetOwnerAsync(RequestOptions options = null) => GuildHelper.GetUserAsync(this, Discord, OwnerId, options); + /// public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); @@ -278,25 +315,41 @@ namespace Discord.Rest public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); + /// + /// Returns the name of the guild. + /// + /// + /// The name of the guild. + /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; //Emotes + /// public Task GetEmoteAsync(ulong id, RequestOptions options = null) => GuildHelper.GetEmoteAsync(this, Discord, id, options); + /// public Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null) => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); + /// + /// is null. public Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null) => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); + /// public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); //IGuild + /// bool IGuild.Available => Available; + /// IAudioClient IGuild.AudioClient => null; + /// IRole IGuild.EveryoneRole => EveryoneRole; + /// IReadOnlyCollection IGuild.Roles => Roles; + /// async Task> IGuild.GetBansAsync(RequestOptions options) => await GetBansAsync(options).ConfigureAwait(false); /// @@ -306,6 +359,7 @@ namespace Discord.Rest async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) => await GetBanAsync(userId, options).ConfigureAwait(false); + /// async Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -313,6 +367,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -320,6 +375,7 @@ namespace Discord.Rest else return null; } + /// async Task> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -327,6 +383,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -334,6 +391,7 @@ namespace Discord.Rest else return null; } + /// async Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -341,6 +399,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -348,6 +407,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -355,6 +415,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -362,6 +423,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -369,6 +431,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -376,6 +439,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -383,26 +447,35 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.CreateTextChannelAsync(string name, Action func, RequestOptions options) => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false); + /// async Task IGuild.CreateVoiceChannelAsync(string name, Action func, RequestOptions options) => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); + /// async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); + /// async Task> IGuild.GetIntegrationsAsync(RequestOptions options) => await GetIntegrationsAsync(options).ConfigureAwait(false); + /// async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); + /// async Task> IGuild.GetInvitesAsync(RequestOptions options) => await GetInvitesAsync(options).ConfigureAwait(false); + /// IRole IGuild.GetRole(ulong id) => GetRole(id); + /// async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); + /// async Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -410,6 +483,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -417,6 +491,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -424,6 +499,7 @@ namespace Discord.Rest else return null; } + /// async Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -431,7 +507,10 @@ namespace Discord.Rest else return ImmutableArray.Create(); } - Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } + /// + /// Downloading users is not supported for a REST-based guild. + Task IGuild.DownloadUsersAsync() => + throw new NotSupportedException(); async Task> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) { @@ -441,9 +520,11 @@ namespace Discord.Rest return ImmutableArray.Create(); } + /// async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) - => await GetWebhookAsync(id, options); + => await GetWebhookAsync(id, options).ConfigureAwait(false); + /// async Task> IGuild.GetWebhooksAsync(RequestOptions options) - => await GetWebhooksAsync(options); + => await GetWebhooksAsync(options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs index f26a62d8d..00f3fae69 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.GuildEmbed; namespace Discord @@ -19,7 +19,7 @@ namespace Discord return new RestGuildEmbed(model.Enabled, model.ChannelId); } - public override string ToString() => ChannelId?.ToString(); + public override string ToString() => ChannelId?.ToString() ?? "Unknown"; private string DebuggerDisplay => $"{ChannelId} ({(IsEnabled ? "Enabled" : "Disabled")})"; } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs index eadda53f2..9759e64d2 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Integration; @@ -10,18 +10,28 @@ namespace Discord.Rest { private long _syncedAtTicks; + /// public string Name { get; private set; } + /// public string Type { get; private set; } + /// public bool IsEnabled { get; private set; } + /// public bool IsSyncing { get; private set; } + /// public ulong ExpireBehavior { get; private set; } + /// public ulong ExpireGracePeriod { get; private set; } + /// public ulong GuildId { get; private set; } + /// public ulong RoleId { get; private set; } public RestUser User { get; private set; } + /// public IntegrationAccount Account { get; private set; } internal IGuild Guild { get; private set; } + /// public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); internal RestGuildIntegration(BaseDiscordClient discord, IGuild guild, ulong id) @@ -78,6 +88,7 @@ namespace Discord.Rest public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; + /// IGuild IGuildIntegration.Guild { get @@ -87,6 +98,7 @@ namespace Discord.Rest throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); } } + /// IUser IGuildIntegration.User => User; } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs index 6bc9cea7a..b75d6288e 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.UserGuild; @@ -6,15 +6,20 @@ using Model = Discord.API.UserGuild; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestUserGuild : RestEntity, ISnowflakeEntity, IUserGuild + public class RestUserGuild : RestEntity, IUserGuild { private string _iconId; - + + /// public string Name { get; private set; } + /// public bool IsOwner { get; private set; } + /// public GuildPermissions Permissions { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// public string IconUrl => CDN.GetGuildIconUrl(Id, _iconId); internal RestUserGuild(BaseDiscordClient discord, ulong id) @@ -40,6 +45,7 @@ namespace Discord.Rest { await Discord.ApiClient.LeaveGuildAsync(Id, options).ConfigureAwait(false); } + /// public async Task DeleteAsync(RequestOptions options = null) { await Discord.ApiClient.DeleteGuildAsync(Id, options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 18698c626..4ada0ffe2 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -9,16 +9,24 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestInvite : RestEntity, IInvite, IUpdateable { + /// public string ChannelName { get; private set; } + /// public string GuildName { get; private set; } + /// public int? PresenceCount { get; private set; } + /// public int? MemberCount { get; private set; } + /// public ulong ChannelId { get; private set; } + /// public ulong GuildId { get; private set; } - internal IChannel Channel { get; private set; } - internal IGuild Guild { get; private set; } + internal IChannel Channel { get; } + internal IGuild Guild { get; } + /// public string Code => Id; + /// public string Url => $"{DiscordConfig.InviteUrl}{Code}"; internal RestInvite(BaseDiscordClient discord, IGuild guild, IChannel channel, string id) @@ -42,7 +50,8 @@ namespace Discord.Rest MemberCount = model.MemberCount.IsSpecified ? model.MemberCount.Value : null; PresenceCount = model.PresenceCount.IsSpecified ? model.PresenceCount.Value : null; } - + + /// public async Task UpdateAsync(RequestOptions options = null) { var args = new GetInviteParams(); @@ -51,12 +60,14 @@ namespace Discord.Rest var model = await Discord.ApiClient.GetInviteAsync(Code, args, options).ConfigureAwait(false); Update(model); } + /// public Task DeleteAsync(RequestOptions options = null) => InviteHelper.DeleteAsync(this, Discord, options); public override string ToString() => Url; private string DebuggerDisplay => $"{Url} ({GuildName} / {ChannelName})"; - + + /// IGuild IInvite.Guild { get @@ -68,6 +79,7 @@ namespace Discord.Rest throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); } } + /// IChannel IInvite.Channel { get diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index 42aeb40aa..d253d562e 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -1,19 +1,29 @@ -using System; +using System; using Model = Discord.API.InviteMetadata; namespace Discord.Rest { + /// Represents additional information regarding the REST-based invite object. public class RestInviteMetadata : RestInvite, IInviteMetadata { private long _createdAtTicks; + /// public bool IsRevoked { get; private set; } + /// public bool IsTemporary { get; private set; } + /// public int? MaxAge { get; private set; } + /// public int? MaxUses { get; private set; } + /// public int Uses { get; private set; } + /// + /// Gets the user that created this invite. + /// public RestUser Inviter { get; private set; } + /// public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); internal RestInviteMetadata(BaseDiscordClient discord, IGuild guild, IChannel channel, string id) @@ -38,6 +48,7 @@ namespace Discord.Rest _createdAtTicks = model.CreatedAt.UtcTicks; } + /// IUser IInviteMetadata.Inviter => Inviter; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs index e185234ac..0f5aaf438 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs @@ -1,17 +1,27 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.Attachment; namespace Discord { + /// + /// An attachment file seen in a . + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Attachment : IAttachment { + /// public ulong Id { get; } + /// public string Filename { get; } + /// public string Url { get; } + /// public string ProxyUrl { get; } + /// public int Size { get; } + /// public int? Height { get; } + /// public int? Width { get; } internal Attachment(ulong id, string filename, string url, string proxyUrl, int size, int? height, int? width) @@ -31,6 +41,12 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } + /// + /// Returns the filename of this attachment. + /// + /// + /// A string containing the filename of this attachment. + /// public override string ToString() => Filename; private string DebuggerDisplay => $"{Filename} ({Size} bytes)"; } diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 3dc3e74e9..ac1eacc3a 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -10,11 +10,13 @@ namespace Discord.Rest { internal static class MessageHelper { + /// Only the author of a message may modify the message. + /// Message content is too long, length must be less or equal to . public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, RequestOptions options) { if (msg.Author.Id != client.CurrentUser.Id) - throw new InvalidOperationException("Only the author of a message may change it."); + throw new InvalidOperationException("Only the author of a message may modify the message."); var args = new MessageProperties(); func(args); @@ -45,7 +47,7 @@ namespace Discord.Rest public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) { - await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options); + await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); } public static IAsyncEnumerable> GetReactionUsersAsync(IMessage msg, IEmote emote, diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 590886886..6d18beaad 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -11,23 +11,34 @@ namespace Discord.Rest { private long _timestampTicks; + /// public IMessageChannel Channel { get; } public IUser Author { get; } + /// public MessageSource Source { get; } + /// public string Content { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// public virtual bool IsTTS => false; + /// public virtual bool IsPinned => false; + /// public virtual DateTimeOffset? EditedTimestamp => null; public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); + /// public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); + /// public virtual IReadOnlyCollection MentionedRoleIds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + /// public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); + /// public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) @@ -53,20 +64,26 @@ namespace Discord.Rest Content = model.Content.Value; } + /// public async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetChannelMessageAsync(Channel.Id, Id, options).ConfigureAwait(false); Update(model); } + /// public Task DeleteAsync(RequestOptions options = null) => MessageHelper.DeleteAsync(this, Discord, options); public override string ToString() => Content; + /// MessageType IMessage.Type => MessageType.Default; IUser IMessage.Author => Author; + /// IReadOnlyCollection IMessage.Attachments => Attachments; + /// IReadOnlyCollection IMessage.Embeds => Embeds; + /// IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs index 6d3f72419..c38efe32d 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs @@ -1,11 +1,21 @@ -using Model = Discord.API.Reaction; +using Model = Discord.API.Reaction; namespace Discord.Rest { + /// + /// Represents a REST reaction object. + /// public class RestReaction : IReaction { + /// public IEmote Emote { get; } + /// + /// Gets the number of reactions added. + /// public int Count { get; } + /// + /// Gets whether the reactions is added by the user. + /// public bool Me { get; } internal RestReaction(IEmote emote, int count, bool me) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 7354cc4af..7e000fd5f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -17,23 +17,33 @@ namespace Discord.Rest private ImmutableArray _embeds; private ImmutableArray _tags; private ImmutableArray _reactions; - + + /// public override bool IsTTS => _isTTS; + /// public override bool IsPinned => _isPinned; + /// public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); + /// public override IReadOnlyCollection Attachments => _attachments; + /// public override IReadOnlyCollection Embeds => _embeds; + /// public override IReadOnlyCollection MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags); + /// public override IReadOnlyCollection MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); + /// public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); + /// public override IReadOnlyCollection Tags => _tags; + /// public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) : base(discord, id, channel, author, source) { } - internal static new RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) + internal new static RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) { var entity = new RestUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); entity.Update(model); @@ -124,30 +134,37 @@ namespace Discord.Rest } } + /// public async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await MessageHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } + /// public Task AddReactionAsync(IEmote emote, RequestOptions options = null) => MessageHelper.AddReactionAsync(this, emote, Discord, options); + /// public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); + /// public Task RemoveAllReactionsAsync(RequestOptions options = null) => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); + /// public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); - + /// public Task PinAsync(RequestOptions options = null) => MessageHelper.PinAsync(this, Discord, options); + /// public Task UnpinAsync(RequestOptions options = null) => MessageHelper.UnpinAsync(this, Discord, options); public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); + /// public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); diff --git a/src/Discord.Net.Rest/Entities/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index 827c33cf7..d033978d0 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -1,23 +1,33 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Application; namespace Discord.Rest { + /// + /// Represents a REST-based entity that contains information about a Discord application created via the developer portal. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestApplication : RestEntity, IApplication { protected string _iconId; - + + /// public string Name { get; private set; } + /// public string Description { get; private set; } + /// public string[] RPCOrigins { get; private set; } + /// public ulong Flags { get; private set; } + /// public IUser Owner { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// public string IconUrl => CDN.GetApplicationIconUrl(Id, _iconId); internal RestApplication(BaseDiscordClient discord, ulong id) @@ -43,6 +53,7 @@ namespace Discord.Rest Owner = RestUser.Create(Discord, model.Owner.Value); } + /// Unable to update this object from a different application token. public async Task UpdateAsync() { var response = await Discord.ApiClient.GetMyApplicationAsync().ConfigureAwait(false); @@ -51,6 +62,12 @@ namespace Discord.Rest Update(response); } + /// + /// Gets the name of the application. + /// + /// + /// Name of the application. + /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; } diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 486f41b9e..5ea7f4462 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Role; @@ -9,16 +9,25 @@ namespace Discord.Rest public class RestRole : RestEntity, IRole { internal IGuild Guild { get; } + /// public Color Color { get; private set; } + /// public bool IsHoisted { get; private set; } + /// public bool IsManaged { get; private set; } + /// public bool IsMentionable { get; private set; } + /// public string Name { get; private set; } + /// public GuildPermissions Permissions { get; private set; } + /// public int Position { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public bool IsEveryone => Id == Guild.Id; + /// public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) @@ -43,20 +52,24 @@ namespace Discord.Rest Permissions = new GuildPermissions(model.Permissions); } + /// public async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await RoleHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } + /// public Task DeleteAsync(RequestOptions options = null) => RoleHelper.DeleteAsync(this, Discord, options); + /// public int CompareTo(IRole role) => RoleUtils.Compare(this, role); public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; //IRole + /// IGuild IRole.Guild { get diff --git a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs index b8b83be3e..0c91493d2 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using Model = Discord.API.Connection; @@ -8,10 +8,15 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestConnection : IConnection { + /// public string Id { get; } + /// public string Type { get; } + /// public string Name { get; } + /// public bool IsRevoked { get; } + /// public IReadOnlyCollection IntegrationIds { get; } internal RestConnection(string id, string type, string name, bool isRevoked, IReadOnlyCollection integrationIds) @@ -28,6 +33,12 @@ namespace Discord return new RestConnection(model.Id, model.Type, model.Name, model.Revoked, model.Integrations.ToImmutableArray()); } + /// + /// Gets the name of the connection. + /// + /// + /// Name of the connection. + /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked ? ", Revoked" : "")})"; } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs index 951bd2e7c..0ad0d8175 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.User; namespace Discord.Rest @@ -16,14 +16,21 @@ namespace Discord.Rest entity.Update(model); return entity; } - + //IVoiceState + /// bool IVoiceState.IsDeafened => false; + /// bool IVoiceState.IsMuted => false; + /// bool IVoiceState.IsSelfDeafened => false; + /// bool IVoiceState.IsSelfMuted => false; + /// bool IVoiceState.IsSuppressed => false; + /// IVoiceChannel IVoiceState.VoiceChannel => null; + /// string IVoiceState.VoiceSessionId => null; } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index e571f8f73..68930e74c 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -9,17 +9,24 @@ using Model = Discord.API.GuildMember; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGuildUser : RestUser, IGuildUser, IUpdateable + public class RestGuildUser : RestUser, IGuildUser { private long? _joinedAtTicks; private ImmutableArray _roleIds; + /// public string Nickname { get; private set; } internal IGuild Guild { get; private set; } + /// public bool IsDeafened { get; private set; } + /// public bool IsMuted { get; private set; } + /// public ulong GuildId => Guild.Id; + + /// + /// Resolving permissions requires the parent guild to be downloaded. public GuildPermissions GuildPermissions { get @@ -29,8 +36,10 @@ namespace Discord.Rest return new GuildPermissions(Permissions.ResolveGuild(Guild, this)); } } + /// public IReadOnlyCollection RoleIds => _roleIds; + /// public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id) @@ -67,11 +76,13 @@ namespace Discord.Rest _roleIds = roles.ToImmutable(); } + /// public override async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetGuildMemberAsync(GuildId, Id, options).ConfigureAwait(false); Update(model); } + /// public async Task ModifyAsync(Action func, RequestOptions options = null) { var args = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); @@ -86,6 +97,7 @@ namespace Discord.Rest else if (args.RoleIds.IsSpecified) UpdateRoles(args.RoleIds.Value.ToArray()); } + /// public Task KickAsync(string reason = null, RequestOptions options = null) => UserHelper.KickAsync(this, Discord, reason, options); /// @@ -101,6 +113,8 @@ namespace Discord.Rest public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) => UserHelper.RemoveRolesAsync(this, Discord, roles, options); + /// + /// Resolving permissions requires the parent guild to be downloaded. public ChannelPermissions GetPermissions(IGuildChannel channel) { var guildPerms = GuildPermissions; @@ -108,6 +122,7 @@ namespace Discord.Rest } //IGuildUser + /// IGuild IGuildUser.Guild { get @@ -119,10 +134,15 @@ namespace Discord.Rest } //IVoiceState + /// bool IVoiceState.IsSelfDeafened => false; + /// bool IVoiceState.IsSelfMuted => false; + /// bool IVoiceState.IsSuppressed => false; + /// IVoiceChannel IVoiceState.VoiceChannel => null; + /// string IVoiceState.VoiceSessionId => null; } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs index ab5ec4a3b..9f84306ea 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; @@ -8,8 +8,11 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestSelfUser : RestUser, ISelfUser { + /// public string Email { get; private set; } + /// public bool IsVerified { get; private set; } + /// public bool IsMfaEnabled { get; private set; } internal RestSelfUser(BaseDiscordClient discord, ulong id) @@ -22,6 +25,7 @@ namespace Discord.Rest entity.Update(model); return entity; } + /// internal override void Update(Model model) { base.Update(model); @@ -34,6 +38,8 @@ namespace Discord.Rest IsMfaEnabled = model.MfaEnabled.Value; } + /// + /// Unable to update this object using a different token. public override async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetMyUserAsync(options).ConfigureAwait(false); @@ -42,6 +48,8 @@ namespace Discord.Rest Update(model); } + /// + /// Unable to modify this object using a different token. public async Task ModifyAsync(Action func, RequestOptions options = null) { if (Id != Discord.CurrentUser.Id) diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index c484986b1..47c3f8b98 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -8,16 +8,26 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestUser : RestEntity, IUser, IUpdateable { + /// public bool IsBot { get; private set; } + /// public string Username { get; private set; } + /// public ushort DiscriminatorValue { get; private set; } + /// public string AvatarId { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// public string Discriminator => DiscriminatorValue.ToString("D4"); + /// public string Mention => MentionUtils.MentionUser(Id); + /// public virtual IActivity Activity => null; + /// public virtual UserStatus Status => UserStatus.Offline; + /// public virtual bool IsWebhook => false; internal RestUser(BaseDiscordClient discord, ulong id) @@ -48,6 +58,7 @@ namespace Discord.Rest Username = model.Username.Value; } + /// public virtual async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetUserAsync(Id, options).ConfigureAwait(false); @@ -57,9 +68,11 @@ namespace Discord.Rest public Task GetOrCreateDMChannelAsync(RequestOptions options = null) => UserHelper.CreateDMChannelAsync(this, Discord, options); + /// public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + /// public string GetDefaultAvatarUrl() => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); @@ -67,7 +80,8 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; //IUser + /// async Task IUser.GetOrCreateDMChannelAsync(RequestOptions options) - => await GetOrCreateDMChannelAsync(options); + => await GetOrCreateDMChannelAsync(options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs index bb44f2777..bee4892fe 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -10,10 +10,13 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestWebhookUser : RestUser, IWebhookUser { + /// public ulong WebhookId { get; } internal IGuild Guild { get; } + /// public override bool IsWebhook => true; + /// public ulong GuildId => Guild.Id; internal RestWebhookUser(BaseDiscordClient discord, IGuild guild, ulong id, ulong webhookId) @@ -28,8 +31,9 @@ namespace Discord.Rest entity.Update(model); return entity; } - + //IGuildUser + /// IGuild IGuildUser.Guild { get @@ -39,45 +43,55 @@ namespace Discord.Rest throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); } } + /// IReadOnlyCollection IGuildUser.RoleIds => ImmutableArray.Create(); + /// DateTimeOffset? IGuildUser.JoinedAt => null; + /// string IGuildUser.Nickname => null; + /// GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; + /// ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); - Task IGuildUser.KickAsync(string reason, RequestOptions options) - { + /// + Task IGuildUser.KickAsync(string reason, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be kicked."); - } - Task IGuildUser.ModifyAsync(Action func, RequestOptions options) - { + + /// + Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be modified."); - } - Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) - { + /// + Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) - { + + /// + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) - { + + /// + Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) - { + + /// + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } //IVoiceState + /// bool IVoiceState.IsDeafened => false; + /// bool IVoiceState.IsMuted => false; + /// bool IVoiceState.IsSelfDeafened => false; + /// bool IVoiceState.IsSelfMuted => false; + /// bool IVoiceState.IsSuppressed => false; + /// IVoiceChannel IVoiceState.VoiceChannel => null; + /// string IVoiceState.VoiceSessionId => null; } } diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index dfb81ff2c..3e7493ab5 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -76,13 +76,13 @@ namespace Discord.Rest public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, RequestOptions options) { foreach (var role in roles) - await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, role.Id, options); + await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false); } public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, RequestOptions options) { foreach (var role in roles) - await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, role.Id, options); + await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs index 47cc50a9c..1fdc95a63 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Webhook; @@ -11,14 +11,21 @@ namespace Discord.Rest internal IGuild Guild { get; private set; } internal ITextChannel Channel { get; private set; } + /// public ulong ChannelId { get; } + /// public string Token { get; } + /// public string Name { get; private set; } + /// public string AvatarId { get; private set; } + /// public ulong? GuildId { get; private set; } + /// public IUser Creator { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); internal RestWebhook(BaseDiscordClient discord, IGuild guild, ulong id, string token, ulong channelId) @@ -59,12 +66,14 @@ namespace Discord.Rest Name = model.Name.Value; } + /// public async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetWebhookAsync(Id, options).ConfigureAwait(false); Update(model); } + /// public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); @@ -74,6 +83,7 @@ namespace Discord.Rest Update(model); } + /// public Task DeleteAsync(RequestOptions options = null) => WebhookHelper.DeleteAsync(this, Discord, options); @@ -81,10 +91,13 @@ namespace Discord.Rest private string DebuggerDisplay => $"Webhook: {Name} ({Id})"; //IWebhook + /// IGuild IWebhook.Guild => Guild ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); + /// ITextChannel IWebhook.Channel => Channel ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); + /// Task IWebhook.ModifyAsync(Action func, RequestOptions options) => ModifyAsync(func, options); } diff --git a/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs b/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs index a5a440d8b..9bbcfb8a3 100644 --- a/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Newtonsoft.Json; using Model = Discord.API.Image; @@ -13,6 +13,7 @@ namespace Discord.Net.Converters public override bool CanRead => true; public override bool CanWrite => true; + /// Cannot read from image. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new InvalidOperationException(); diff --git a/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs b/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs index 0ed566a84..de2e379d7 100644 --- a/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System; namespace Discord.Net.Converters @@ -11,6 +11,7 @@ namespace Discord.Net.Converters public override bool CanRead => true; public override bool CanWrite => true; + /// Unknown permission target. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { switch ((string)reader.Value) @@ -20,10 +21,11 @@ namespace Discord.Net.Converters case "role": return PermissionTarget.Role; default: - throw new JsonSerializationException("Unknown permission target"); + throw new JsonSerializationException("Unknown permission target."); } } + /// Invalid permission target. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { switch ((PermissionTarget)value) @@ -35,7 +37,7 @@ namespace Discord.Net.Converters writer.WriteValue("role"); break; default: - throw new JsonSerializationException("Invalid permission target"); + throw new JsonSerializationException("Invalid permission target."); } } } diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index ec789be59..4b4c1e045 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -82,6 +82,8 @@ namespace Discord.Net.Rest return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); } } + + /// Unsupported param type. public async Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null) { string uri = Path.Combine(_baseUrl, endpoint); @@ -111,7 +113,7 @@ namespace Discord.Net.Rest content.Add(new StreamContent(stream), p.Key, fileValue.Filename); continue; } - default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\""); + default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\"."); } } } diff --git a/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs b/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs index e0e776549..67b47096e 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs @@ -6,6 +6,7 @@ namespace Discord.Net.Rest { public static readonly RestClientProvider Instance = Create(); + /// The default RestClientProvider is not supported on this platform. public static RestClientProvider Create(bool useProxy = false) { return url => diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs index 943b76359..e3b34eb8f 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; #if DEBUG_LIMITS using System.Diagnostics; @@ -117,7 +117,7 @@ namespace Discord.Net.Queue if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) _buckets.TryRemove(bucket.Id, out RequestBucket ignored); } - await Task.Delay(60000, _cancelToken.Token); //Runs each minute + await Task.Delay(60000, _cancelToken.Token).ConfigureAwait(false); //Runs each minute } } catch (OperationCanceledException) { } diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index c4f5996c5..a4f87a3e9 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -126,7 +126,7 @@ namespace Discord.Net.Queue if ((request.Options.RetryMode & RetryMode.RetryTimeouts) == 0) throw; - await Task.Delay(500); + await Task.Delay(500).ConfigureAwait(false); continue; //Retry } /*catch (Exception) diff --git a/src/Discord.Net.Rest/Net/RateLimitInfo.cs b/src/Discord.Net.Rest/Net/RateLimitInfo.cs index a517f290c..d31cc5cdd 100644 --- a/src/Discord.Net.Rest/Net/RateLimitInfo.cs +++ b/src/Discord.Net.Rest/Net/RateLimitInfo.cs @@ -15,7 +15,7 @@ namespace Discord.Net internal RateLimitInfo(Dictionary headers) { IsGlobal = headers.TryGetValue("X-RateLimit-Global", out string temp) && - bool.TryParse(temp, out var isGlobal) ? isGlobal : false; + bool.TryParse(temp, out var isGlobal) && isGlobal; Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) && int.TryParse(temp, out var limit) ? limit : (int?)null; Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && diff --git a/src/Discord.Net.Rest/Utils/TypingNotifier.cs b/src/Discord.Net.Rest/Utils/TypingNotifier.cs index b4bd2f44b..745dbd36d 100644 --- a/src/Discord.Net.Rest/Utils/TypingNotifier.cs +++ b/src/Discord.Net.Rest/Utils/TypingNotifier.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; @@ -6,21 +6,19 @@ namespace Discord.Rest { internal class TypingNotifier : IDisposable { - private readonly BaseDiscordClient _client; private readonly CancellationTokenSource _cancelToken; private readonly IMessageChannel _channel; private readonly RequestOptions _options; - public TypingNotifier(BaseDiscordClient discord, IMessageChannel channel, RequestOptions options) + public TypingNotifier(IMessageChannel channel, RequestOptions options) { - _client = discord; _cancelToken = new CancellationTokenSource(); _channel = channel; _options = options; - var _ = Run(); + _ = RunAsync(); } - private async Task Run() + private async Task RunAsync() { try { @@ -31,7 +29,11 @@ namespace Discord.Rest { await _channel.TriggerTypingAsync(_options).ConfigureAwait(false); } - catch { } + catch + { + // ignored + } + await Task.Delay(9500, token).ConfigureAwait(false); } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 1f33b3cc5..c3960fa67 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -1,4 +1,4 @@ -using Discord.API.Voice; +using Discord.API.Voice; using Discord.Audio.Streams; using Discord.Logging; using Discord.Net.Converters; @@ -16,7 +16,7 @@ using System.Collections.Generic; namespace Discord.Audio { //TODO: Add audio reconnecting - internal partial class AudioClient : IAudioClient, IDisposable + internal partial class AudioClient : IAudioClient { internal struct StreamPair { @@ -65,7 +65,7 @@ namespace Discord.Audio ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); - ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); + ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync("Sent Discovery").ConfigureAwait(false); //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); ApiClient.ReceivedEvent += ProcessMessageAsync; ApiClient.ReceivedPacket += ProcessPacketAsync; @@ -291,7 +291,7 @@ namespace Discord.Audio { if (packet.Length != 70) { - await _audioLogger.DebugAsync($"Malformed Packet").ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Packet").ConfigureAwait(false); return; } string ip; @@ -303,7 +303,7 @@ namespace Discord.Audio } catch (Exception ex) { - await _audioLogger.DebugAsync($"Malformed Packet", ex).ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false); return; } @@ -343,7 +343,7 @@ namespace Discord.Audio { if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) { - await _audioLogger.DebugAsync($"Malformed Frame").ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false); return; } if (!_ssrcMap.TryGetValue(ssrc, out var userId)) @@ -362,7 +362,7 @@ namespace Discord.Audio } catch (Exception ex) { - await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Frame", ex).ConfigureAwait(false); return; } //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); @@ -371,7 +371,7 @@ namespace Discord.Audio } catch (Exception ex) { - await _audioLogger.WarningAsync($"Failed to process UDP packet", ex).ConfigureAwait(false); + await _audioLogger.WarningAsync("Failed to process UDP packet", ex).ConfigureAwait(false); return; } } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs index fb302f132..1365ddac2 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs @@ -116,7 +116,7 @@ namespace Discord.Audio.Streams timestamp += OpusEncoder.FrameSamplesPerChannel; } #if DEBUG - var _ = _logger?.DebugAsync($"Buffer underrun"); + var _ = _logger?.DebugAsync("Buffer underrun"); #endif } } @@ -140,7 +140,7 @@ namespace Discord.Audio.Streams if (!_bufferPool.TryDequeue(out byte[] buffer)) { #if DEBUG - var _ = _logger?.DebugAsync($"Buffer overflow"); //Should never happen because of the queueLock + var _ = _logger?.DebugAsync("Buffer overflow"); //Should never happen because of the queueLock #endif return; } @@ -149,7 +149,7 @@ namespace Discord.Audio.Streams if (!_isPreloaded && _queuedFrames.Count == _queueLength) { #if DEBUG - var _ = _logger?.DebugAsync($"Preloaded"); + var _ = _logger?.DebugAsync("Preloaded"); #endif _isPreloaded = true; } @@ -173,4 +173,4 @@ namespace Discord.Audio.Streams return Task.Delay(0); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs index 58c4f4c70..1861e3554 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; @@ -22,19 +22,22 @@ namespace Discord.Audio.Streams _decoder = new OpusDecoder(); } + /// Header received with no payload. public override void WriteHeader(ushort seq, uint timestamp, bool missed) { if (_hasHeader) - throw new InvalidOperationException("Header received with no payload"); + throw new InvalidOperationException("Header received with no payload."); _hasHeader = true; _nextMissed = missed; _next.WriteHeader(seq, timestamp, missed); } + + /// Received payload without an RTP header. public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { if (!_hasHeader) - throw new InvalidOperationException("Received payload without an RTP header"); + throw new InvalidOperationException("Received payload without an RTP header."); _hasHeader = false; if (!_nextMissed) diff --git a/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs index 2cedea114..120f67e0d 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; namespace Discord.Audio.Streams @@ -20,6 +21,8 @@ namespace Discord.Audio.Streams _nonce = new byte[24]; } + /// The token has had cancellation requested. + /// The associated has been disposed. public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); diff --git a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs index bacc9be47..2b5cc0a18 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs @@ -20,21 +20,25 @@ namespace Discord.Audio.Streams _client = (AudioClient)client; _nonce = new byte[24]; } - + + /// Header received with no payload. public override void WriteHeader(ushort seq, uint timestamp, bool missed) { if (_hasHeader) - throw new InvalidOperationException("Header received with no payload"); + throw new InvalidOperationException("Header received with no payload."); _nextSeq = seq; _nextTimestamp = timestamp; _hasHeader = true; } + /// Received payload without an RTP header. + /// The token has had cancellation requested. + /// The associated has been disposed. public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); if (!_hasHeader) - throw new InvalidOperationException("Received payload without an RTP header"); + throw new InvalidOperationException("Received payload without an RTP header."); _hasHeader = false; if (_client.SecretKey == null) diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index c236b1045..9a94cbf23 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -7,6 +7,17 @@ namespace Discord.WebSocket { //Channels /// Fired when a channel is created. + /// + /// + /// This event is fired when a generic channel has been created. The event handler must return a + /// . + /// + /// + /// The newly created channel is passed into the event handler parameter. The given channel type may + /// include, but not limited to, Private Channels (DM, Group), Guild Channels (Text, Voice, Category); + /// see the derived classes of for more details. + /// + /// public event Func ChannelCreated { add { _channelCreatedEvent.Add(value); } @@ -14,12 +25,35 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); /// Fired when a channel is destroyed. + /// + /// + /// This event is fired when a generic channel has been destroyed. The event handler must return a + /// . + /// + /// + /// The destroyed channel is passed into the event handler parameter. The given channel type may + /// include, but not limited to, Private Channels (DM, Group), Guild Channels (Text, Voice, Category); + /// see the derived classes of for more details. + /// + /// public event Func ChannelDestroyed { add { _channelDestroyedEvent.Add(value); } remove { _channelDestroyedEvent.Remove(value); } } internal readonly AsyncEvent> _channelDestroyedEvent = new AsyncEvent>(); /// Fired when a channel is updated. + /// + /// + /// This event is fired when a generic channel has been destroyed. The event handler must return a + /// . + /// + /// + /// The original (prior to update) channel is passed into the first , while + /// the updated channel is passed into the second. The given channel type may include, but not limited + /// to, Private Channels (DM, Group), Guild Channels (Text, Voice, Category); see the derived classes of + /// for more details. + /// + /// public event Func ChannelUpdated { add { _channelUpdatedEvent.Add(value); } remove { _channelUpdatedEvent.Remove(value); } @@ -28,12 +62,46 @@ namespace Discord.WebSocket //Messages /// Fired when a message is received. + /// + /// + /// This event is fired when a message is received. The event handler must return a + /// . + /// + /// + /// The message that is sent to the client is passed into the event handler parameter as + /// . This message may be a system message (i.e. + /// ) or a user message (i.e. . See + /// the derived clsases of for more details. + /// + /// public event Func MessageReceived { add { _messageReceivedEvent.Add(value); } remove { _messageReceivedEvent.Remove(value); } } internal readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); /// Fired when a message is deleted. + /// + /// + /// This event is fired when a message is deleted. The event handler must return a + /// and accept a and + /// as its parameters. + /// + /// + /// + /// It is not possible to retrieve the message via + /// ; the message cannot be retrieved by Discord + /// after the message has been deleted. + /// + /// If caching is enabled via , the + /// entity will contain the deleted message; otherwise, in event + /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the + /// . + /// + /// + /// The source channel of the removed message will be passed into the + /// parameter. + /// + /// public event Func, ISocketMessageChannel, Task> MessageDeleted { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index fb82fe14a..858fec7fe 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -8,86 +8,253 @@ namespace Discord.WebSocket { public abstract partial class BaseSocketClient : BaseDiscordClient, IDiscordClient { - protected readonly DiscordSocketConfig _baseconfig; + protected readonly DiscordSocketConfig BaseConfig; - /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. + /// + /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. + /// public abstract int Latency { get; protected set; } - public abstract UserStatus Status { get; protected set; } + /// + /// Gets the status for the logged-in user. + /// + public abstract UserStatus Status { get; protected set; } + /// + /// Gets the activity for the logged-in user. + /// public abstract IActivity Activity { get; protected set; } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; + /// + /// Gets the current logged-in user. + /// public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } + /// + /// Gets a collection of guilds that the logged-in user is currently in. + /// public abstract IReadOnlyCollection Guilds { get; } + /// + /// Gets a collection of private channels that are currently open for the logged-in user. + /// public abstract IReadOnlyCollection PrivateChannels { get; } + /// + /// Gets a collection of available voice regions for the logged-in user. + /// public abstract IReadOnlyCollection VoiceRegions { get; } internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) - : base(config, client) => _baseconfig = config; + : base(config, client) => BaseConfig = config; private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); - /// + /// + /// Gets a Discord application information for the logged-in user. + /// + /// + /// This method reflects your application information you submitted when creating a Discord application via + /// the Developer Portal. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the application information. + /// public abstract Task GetApplicationInfoAsync(RequestOptions options = null); - /// + /// + /// Gets a user. + /// + /// The user snowflake ID. + /// + /// This method gets the user present in the WebSocket cache with the given condition. + /// + /// Sometimes a user may return null due to Discord not sending offline users in large + /// guilds (i.e. guild with 100+ members) actively. To download users on startup, consider enabling + /// . + /// + /// + /// This method does not attempt to fetch users that the logged-in user does not have access to (i.e. + /// users who don't share mutual guild(s) with the current user). + /// + /// + /// + /// A WebSocket-based generic user; null when the user cannot be found. + /// public abstract SocketUser GetUser(ulong id); - /// + + /// + /// Gets a user. + /// + /// The name of the user. + /// The discriminator value of the user. + /// + /// This method gets the user present in the WebSocket cache with the given condition. + /// + /// Sometimes a user may return null due to Discord not sending offline users in large + /// guilds (i.e. guild with 100+ members) actively. To download users on startup, consider enabling + /// . + /// + /// + /// This method does not attempt to fetch users that the logged-in user does not have access to (i.e. + /// users who don't share mutual guild(s) with the current user). + /// + /// + /// + /// A WebSocket-based generic user; null when the user cannot be found. + /// public abstract SocketUser GetUser(string username, string discriminator); - /// + /// + /// Gets a channel. + /// + /// The channel snowflake ID. + /// + /// A generic WebSocket-based channel object (voice, text, category, etc.); null when the + /// channel cannot be found. + /// public abstract SocketChannel GetChannel(ulong id); - /// + /// + /// Gets a guild. + /// + /// The guild snowflake ID. + /// + /// A WebSocket-based guild; null when the guild cannot be found. + /// public abstract SocketGuild GetGuild(ulong id); - /// + /// + /// Gets a voice region. + /// + /// The unique identifier of the voice region. + /// + /// A REST-based voice region; null if none can be found. + /// public abstract RestVoiceRegion GetVoiceRegion(string id); /// public abstract Task StartAsync(); /// public abstract Task StopAsync(); + /// + /// Sets the current status of the logged-in user (e.g. Online, Do not Disturb). + /// + /// The new status to be set. + /// + /// An awaitable . + /// public abstract Task SetStatusAsync(UserStatus status); + /// + /// Sets the game of the logged-in user. + /// + /// The name of the game. + /// If streaming, the URL of the stream. Must be a valid Twitch URL. + /// The type of the game. + /// + /// An awaitable . + /// public abstract Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing); + /// + /// Sets the of the logged-in user. + /// + /// + /// This method sets the of the user. + /// + /// Discord will only accept setting of name and the type of activity. + /// + /// + /// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC + /// clients only. + /// + /// + /// The activty to be set. + /// + /// An awaitable . + /// public abstract Task SetActivityAsync(IActivity activity); - public abstract Task DownloadUsersAsync(IEnumerable guilds); + /// + /// Attempts to download users into the user cache for the selected guilds. + /// + /// The guilds to download the members from. + /// + /// An awaitable . + /// + public abstract Task DownloadUsersAsync(IEnumerable guilds); - /// + /// + /// Creates a guild for the logged-in user who is in less than 10 active guilds. + /// + /// + /// This method creates a new guild on behalf of the logged-in user. + /// + /// Due to Discord's limitation, this method will only work for users that are in less than 10 guilds. + /// + /// + /// The name of the new guild. + /// The voice region to create the guild with. + /// The icon of the guild. + /// The options to be used when sending the request. + /// + /// An awaitable containing the newly created guild. + /// public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default); - /// + /// + /// Gets the connections that the logged-in user has set up. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of connections. + /// public Task> GetConnectionsAsync(RequestOptions options = null) => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); - /// + /// + /// Gets an invite with the provided invite identifier. + /// + /// The invitation identifier. + /// The options to be used when sending the request. + /// + /// An awaitable containing the invite information. + /// public Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) => ClientHelper.GetInviteAsync(this, inviteId, withCount, options ?? RequestOptions.Default); // IDiscordClient + /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => await GetApplicationInfoAsync(options).ConfigureAwait(false); + /// Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetChannel(id)); + /// Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(PrivateChannels); + /// async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync(options).ConfigureAwait(false); + /// async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); + /// Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); + /// Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Guilds); + /// async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); + /// Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) => Task.FromResult(GetUser(username, discriminator)); + /// Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) => Task.FromResult(GetVoiceRegion(id)); + /// Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) => Task.FromResult>(VoiceRegions); } diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index f07976a0a..44b44e689 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -72,9 +72,9 @@ namespace Discord.WebSocket switch (channel) { case SocketDMChannel dmChannel: - _dmChannels.TryRemove(dmChannel.Recipient.Id, out var ignored); + _dmChannels.TryRemove(dmChannel.Recipient.Id, out var _); break; - case SocketGroupChannel groupChannel: + case SocketGroupChannel _: _groupChannels.TryRemove(id); break; } diff --git a/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs index a29c9bb70..f970c32fc 100644 --- a/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs +++ b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs @@ -1,9 +1,11 @@ -using Discord.WebSocket; +using Discord.WebSocket; namespace Discord.Commands { + /// The sharded variant of , which may contain the client, user, guild, channel, and message. public class ShardedCommandContext : SocketCommandContext, ICommandContext { + /// Gets the that the command is executed with. public new DiscordShardedClient Client { get; } public ShardedCommandContext(DiscordShardedClient client, SocketUserMessage msg) @@ -12,10 +14,12 @@ namespace Discord.Commands Client = client; } + /// Gets the shard ID of the command context. private static int GetShardId(DiscordShardedClient client, IGuild guild) => guild == null ? 0 : client.GetShardIdFor(guild); //ICommandContext + /// IDiscordClient ICommandContext.Client => Client; } } diff --git a/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs b/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs index c8b0747e7..f4d517909 100644 --- a/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs +++ b/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs @@ -1,17 +1,43 @@ -using Discord.WebSocket; +using Discord.WebSocket; namespace Discord.Commands { + /// + /// Represents a WebSocket-based context of a command. This may include the client, guild, channel, user, and message. + /// public class SocketCommandContext : ICommandContext { + /// + /// Gets the that the command is executed with. + /// public DiscordSocketClient Client { get; } + /// + /// Gets the that the command is executed in. + /// public SocketGuild Guild { get; } + /// + /// Gets the that the command is executed in. + /// public ISocketMessageChannel Channel { get; } + /// + /// Gets the who executed the command. + /// public SocketUser User { get; } + /// + /// Gets the that the command is interpreted from. + /// public SocketUserMessage Message { get; } + /// + /// Indicates whether the channel that the command is executed in is a private channel. + /// public bool IsPrivate => Channel is IPrivateChannel; + /// + /// Initializes a new class with the provided client and message. + /// + /// The underlying client. + /// The underlying message. public SocketCommandContext(DiscordSocketClient client, SocketUserMessage msg) { Client = client; @@ -22,10 +48,15 @@ namespace Discord.Commands } //ICommandContext + /// IDiscordClient ICommandContext.Client => Client; + /// IGuild ICommandContext.Guild => Guild; + /// IMessageChannel ICommandContext.Channel => Channel; + /// IUser ICommandContext.User => User; + /// IUserMessage ICommandContext.Message => Message; } } diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index dad53b3b7..039ea2fe5 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -19,24 +19,29 @@ namespace Discord.WebSocket private int _totalShards; private bool _automaticShards; - /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. + /// public override int Latency { get => GetLatency(); protected set { } } + /// public override UserStatus Status { get => _shards[0].Status; protected set { } } + /// public override IActivity Activity { get => _shards[0].Activity; protected set { } } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; - public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); - public override IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); + /// + public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); + /// + public override IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); public IReadOnlyCollection Shards => _shards; + /// public override IReadOnlyCollection VoiceRegions => _shards[0].VoiceRegions; - /// Creates a new REST/WebSocket discord client. + /// Creates a new REST/WebSocket Discord client. public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } - /// Creates a new REST/WebSocket discord client. + /// Creates a new REST/WebSocket Discord client. public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { } - /// Creates a new REST/WebSocket discord client. + /// Creates a new REST/WebSocket Discord client. public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { } - /// Creates a new REST/WebSocket discord client. + /// Creates a new REST/WebSocket Discord client. public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) { } private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client) : base(config, client) @@ -213,7 +218,9 @@ namespace Discord.WebSocket public override RestVoiceRegion GetVoiceRegion(string id) => _shards[0].GetVoiceRegion(id); - /// Downloads the users list for the provided guilds, if they don't have a complete list. + /// + /// Downloads the users list for the provided guilds if they don't have a complete list. + /// public override async Task DownloadUsersAsync(IEnumerable guilds) { for (int i = 0; i < _shards.Length; i++) @@ -233,11 +240,13 @@ namespace Discord.WebSocket return (int)Math.Round(total / (double)_shards.Length); } + /// public override async Task SetStatusAsync(UserStatus status) { for (int i = 0; i < _shards.Length; i++) await _shards[i].SetStatusAsync(status).ConfigureAwait(false); } + /// public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) { IActivity activity = null; @@ -247,6 +256,7 @@ namespace Discord.WebSocket activity = new Game(name, type); await SetActivityAsync(activity).ConfigureAwait(false); } + /// public override async Task SetActivityAsync(IActivity activity) { for (int i = 0; i < _shards.Length; i++) @@ -316,34 +326,46 @@ namespace Discord.WebSocket } //IDiscordClient + /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => await GetApplicationInfoAsync().ConfigureAwait(false); + /// Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetChannel(id)); + /// Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(PrivateChannels); + /// async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync().ConfigureAwait(false); + /// async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); + /// Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); + /// Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Guilds); + /// async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); + /// Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) => Task.FromResult(GetUser(username, discriminator)); + /// Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) => Task.FromResult>(VoiceRegions); + /// Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) => Task.FromResult(GetVoiceRegion(id)); } diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 8ae41cc59..20ff85be6 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -120,12 +120,14 @@ namespace Discord.API } finally { _stateLock.Release(); } } + /// The client must be logged in before connecting. + /// This client is not configured with WebSocket support. internal override async Task ConnectInternalAsync() { if (LoginState != LoginState.LoggedIn) - throw new InvalidOperationException("You must log in before connecting."); + throw new InvalidOperationException("The client must be logged in before connecting."); if (WebSocketClient == null) - throw new NotSupportedException("This client is not configured with websocket support."); + throw new NotSupportedException("This client is not configured with WebSocket support."); //Re-create streams to reset the zlib state _compressed?.Dispose(); @@ -176,10 +178,11 @@ namespace Discord.API } finally { _stateLock.Release(); } } + /// This client is not configured with WebSocket support. internal override async Task DisconnectInternalAsync() { if (WebSocketClient == null) - throw new NotSupportedException("This client is not configured with websocket support."); + throw new NotSupportedException("This client is not configured with WebSocket support."); if (ConnectionState == ConnectionState.Disconnected) return; ConnectionState = ConnectionState.Disconnecting; diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 01322a3cc..721181968 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -46,7 +46,9 @@ namespace Discord.WebSocket public ConnectionState ConnectionState => _connection.State; /// public override int Latency { get; protected set; } + /// public override UserStatus Status { get; protected set; } = UserStatus.Online; + /// public override IActivity Activity { get; protected set; } //From DiscordSocketConfig @@ -58,19 +60,22 @@ namespace Discord.WebSocket internal WebSocketProvider WebSocketProvider { get; private set; } internal bool AlwaysDownloadUsers { get; private set; } internal int? HandlerTimeout { get; private set; } - + internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; + /// public override IReadOnlyCollection Guilds => State.Guilds; + /// public override IReadOnlyCollection PrivateChannels => State.PrivateChannels; public IReadOnlyCollection DMChannels => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); public IReadOnlyCollection GroupChannels => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); + /// public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); - /// Creates a new REST/WebSocket discord client. + /// Creates a new REST/WebSocket Discord client. public DiscordSocketClient() : this(new DiscordSocketConfig()) { } - /// Creates a new REST/WebSocket discord client. + /// Creates a new REST/WebSocket Discord client. public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config), null, null) { } internal DiscordSocketClient(DiscordSocketConfig config, SemaphoreSlim groupLock, DiscordSocketClient parentClient) : this(config, CreateApiClient(config), groupLock, parentClient) { } private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client, SemaphoreSlim groupLock, DiscordSocketClient parentClient) @@ -128,6 +133,7 @@ namespace Discord.WebSocket } private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); + /// internal override void Dispose(bool disposing) { if (disposing) @@ -137,6 +143,7 @@ namespace Discord.WebSocket } } + /// internal override async Task OnLoginAsync(TokenType tokenType, string token) { if (_parentClient == null) @@ -147,6 +154,7 @@ namespace Discord.WebSocket else _voiceRegions = _parentClient._voiceRegions; } + /// internal override async Task OnLogoutAsync() { await StopAsync().ConfigureAwait(false); @@ -154,9 +162,11 @@ namespace Discord.WebSocket _voiceRegions = ImmutableDictionary.Create(); } - public override async Task StartAsync() + /// + public override async Task StartAsync() => await _connection.StartAsync().ConfigureAwait(false); - public override async Task StopAsync() + /// + public override async Task StopAsync() => await _connection.StopAsync().ConfigureAwait(false); private async Task OnConnectingAsync() @@ -277,7 +287,7 @@ namespace Discord.WebSocket return null; } - /// Downloads the users list for the provided guilds, if they don't have a complete list. + /// public override async Task DownloadUsersAsync(IEnumerable guilds) { if (ConnectionState == ConnectionState.Connected) @@ -316,6 +326,7 @@ namespace Discord.WebSocket } } + /// public override async Task SetStatusAsync(UserStatus status) { Status = status; @@ -325,6 +336,7 @@ namespace Discord.WebSocket _statusSince = null; await SendStatusAsync().ConfigureAwait(false); } + /// public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) { if (!string.IsNullOrEmpty(streamUrl)) @@ -335,6 +347,7 @@ namespace Discord.WebSocket Activity = null; await SendStatusAsync().ConfigureAwait(false); } + /// public override async Task SetActivityAsync(IActivity activity) { Activity = activity; @@ -351,8 +364,8 @@ namespace Discord.WebSocket var gameModel = new GameModel(); // Discord only accepts rich presence over RPC, don't even bother building a payload - if (Activity is RichGame game) - throw new NotSupportedException("Outgoing Rich Presences are not supported"); + if (Activity is RichGame) + throw new NotSupportedException("Outgoing Rich Presences are not supported via WebSocket."); if (Activity != null) { @@ -508,7 +521,7 @@ namespace Discord.WebSocket { type = "GUILD_AVAILABLE"; _lastGuildAvailableTime = Environment.TickCount; - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); var guild = State.GetGuild(data.Id); if (guild != null) @@ -533,7 +546,7 @@ namespace Discord.WebSocket } else { - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); var guild = AddGuild(data, State); if (guild != null) @@ -614,7 +627,7 @@ namespace Discord.WebSocket if (data.Unavailable == true) { type = "GUILD_UNAVAILABLE"; - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); var guild = State.GetGuild(data.Id); if (guild != null) @@ -630,7 +643,7 @@ namespace Discord.WebSocket } else { - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); var guild = RemoveGuild(data.Id); if (guild != null) @@ -1148,7 +1161,7 @@ namespace Discord.WebSocket after = SocketMessage.Create(this, State, author, channel, data); } - var cacheableBefore = new Cacheable(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id)); + var cacheableBefore = new Cacheable(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); } @@ -1175,7 +1188,7 @@ namespace Discord.WebSocket var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); bool isCached = msg != null; - var cacheable = new Cacheable(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id)); + var cacheable = new Cacheable(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); } @@ -1195,9 +1208,9 @@ namespace Discord.WebSocket { var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; bool isCached = cachedMsg != null; - var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); + var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); var reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); + var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); cachedMsg?.AddReaction(reaction); @@ -1219,9 +1232,9 @@ namespace Discord.WebSocket { var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; bool isCached = cachedMsg != null; - var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); + var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); var reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); + var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); cachedMsg?.RemoveReaction(reaction); @@ -1243,7 +1256,7 @@ namespace Discord.WebSocket { var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; bool isCached = cachedMsg != null; - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); + var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => (await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false)) as IUserMessage); cachedMsg?.ClearReactions(); @@ -1274,7 +1287,7 @@ namespace Discord.WebSocket { var msg = SocketChannelHelper.RemoveMessage(channel, this, id); bool isCached = msg != null; - var cacheable = new Cacheable(msg, id, isCached, async () => await channel.GetMessageAsync(id)); + var cacheable = new Cacheable(msg, id, isCached, async () => await channel.GetMessageAsync(id).ConfigureAwait(false)); await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); } } @@ -1624,6 +1637,7 @@ namespace Discord.WebSocket return guild; } + /// Unexpected channel type is created. internal ISocketPrivateChannel AddPrivateChannel(API.Channel model, ClientState state) { var channel = SocketChannel.CreatePrivate(this, state, model); @@ -1796,43 +1810,59 @@ namespace Discord.WebSocket internal int GetAudioId() => _nextAudioId++; //IDiscordClient + /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => await GetApplicationInfoAsync().ConfigureAwait(false); + /// Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetChannel(id)); + /// Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(PrivateChannels); + /// Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(DMChannels); + /// Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(GroupChannels); + /// async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync().ConfigureAwait(false); + /// async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); + /// Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); + /// Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Guilds); + /// async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); + /// Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) => Task.FromResult(GetUser(username, discriminator)); + /// Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) => Task.FromResult>(VoiceRegions); + /// Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) => Task.FromResult(GetVoiceRegion(id)); + /// async Task IDiscordClient.StartAsync() => await StartAsync().ConfigureAwait(false); + /// async Task IDiscordClient.StopAsync() => await StopAsync().ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs index 3f9c18863..d85230fec 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs @@ -1,41 +1,72 @@ -using Discord.Net.Udp; +using Discord.Net.Udp; using Discord.Net.WebSockets; using Discord.Rest; namespace Discord.WebSocket { + /// + /// Represents a configuration class for . + /// public class DiscordSocketConfig : DiscordRestConfig { + /// + /// Gets or sets the encoding gateway should use. + /// public const string GatewayEncoding = "json"; - /// Gets or sets the websocket host to connect to. If null, the client will use the /gateway endpoint. + /// + /// Gets or sets the WebSocket host to connect to. If null, the client will use the + /// /gateway endpoint. + /// public string GatewayHost { get; set; } = null; - /// Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. + /// + /// Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. + /// public int ConnectionTimeout { get; set; } = 30000; - /// Gets or sets the id for this shard. Must be less than TotalShards. + /// + /// Gets or sets the ID for this shard. Must be less than . + /// public int? ShardId { get; set; } = null; - /// Gets or sets the total number of shards for this application. + /// + /// Gets or sets the total number of shards for this application. + /// public int? TotalShards { get; set; } = null; - /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. + /// + /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero + /// disables the message cache entirely. + /// public int MessageCacheSize { get; set; } = 0; - /// - /// Gets or sets the max number of users a guild may have for offline users to be included in the READY packet. Max is 250. + /// + /// Gets or sets the max number of users a guild may have for offline users to be included in the READY + /// packet. Max is 250. /// public int LargeThreshold { get; set; } = 250; - /// Gets or sets the provider used to generate new websocket connections. + /// + /// Gets or sets the provider used to generate new WebSocket connections. + /// public WebSocketProvider WebSocketProvider { get; set; } - /// Gets or sets the provider used to generate new udp sockets. + /// + /// Gets or sets the provider used to generate new UDP sockets. + /// public UdpSocketProvider UdpSocketProvider { get; set; } - /// Gets or sets whether or not all users should be downloaded as guilds come available. + /// + /// Gets or sets whether or not all users should be downloaded as guilds come available. + /// public bool AlwaysDownloadUsers { get; set; } = false; - /// Gets or sets the timeout for event handlers, in milliseconds, after which a warning will be logged. Null disables this check. + /// + /// Gets or sets the timeout for event handlers, in milliseconds, after which a warning will be logged. Null + /// disables this check. + /// public int? HandlerTimeout { get; set; } = 3000; + /// + /// Initializes a default configuration. + /// public DiscordSocketConfig() { WebSocketProvider = DefaultWebSocketProvider.Instance; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketAudioChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketAudioChannel.cs index 7056a4df5..3cb978abf 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketAudioChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketAudioChannel.cs @@ -1,5 +1,8 @@ -namespace Discord.WebSocket +namespace Discord.WebSocket { + /// + /// Represents a generic WebSocket-based audio channel. + /// public interface ISocketAudioChannel : IAudioChannel { } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index 5fef7e4cd..73fc3c34c 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -5,27 +5,110 @@ using System.Threading.Tasks; namespace Discord.WebSocket { + /// + /// Represents a generic WebSocket-based channel that can send and receive messages. + /// public interface ISocketMessageChannel : IMessageChannel { - /// Gets all messages in this channel's cache. + /// + /// Gets all messages in this channel's cache. + /// + /// + /// A collection of WebSocket-based messages. + /// IReadOnlyCollection CachedMessages { get; } - /// Sends a message to this message channel. + /// + /// Sends a message to this message channel. + /// + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); - /// Sends a file to this text channel, with an optional caption. + /// + /// Sends a file to this message channel, with an optional caption. + /// + /// The file path of the file. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); - - /// Sends a file to this text channel, with an optional caption. + /// + /// Sends a file to this message channel, with an optional caption. + /// + /// The of the file to be sent. + /// The name of the attachment. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + /// + /// Gets the cached message if one exists. + /// + /// The ID of the message. + /// + /// Cached message object; null if it doesn't exist in the cache. + /// SocketMessage GetCachedMessage(ulong id); - /// Gets the last N messages from this message channel. + /// + /// Gets the last N messages from this message channel. + /// + /// The number of messages to get. + /// + /// A collection of WebSocket-based messages. + /// IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch); - /// Gets a collection of messages in this channel. + + /// + /// Gets a collection of messages in this channel. + /// + /// The message ID to start the fetching from. + /// The direction of which the message should be gotten from. + /// The number of messages to get. + /// + /// A collection of WebSocket-based messages. + /// IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); - /// Gets a collection of messages in this channel. + /// + /// Gets a collection of messages in this channel. + /// + /// The message to start the fetching from. + /// The direction of which the message should be gotten from. + /// The number of messages to get. + /// + /// A collection of WebSocket-based messages. + /// IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); - /// Gets a collection of pinned messages in this channel. + /// + /// Gets a collection of pinned messages in this channel. + /// + /// The options to be used when sending the request. + /// + /// A collection of messages. + /// new Task> GetPinnedMessagesAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketPrivateChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketPrivateChannel.cs index 4e91673dd..08da2237c 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketPrivateChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketPrivateChannel.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Discord.WebSocket { + /// + /// Represents a generic WebSocket-based channel that is private to select recipients. + /// public interface ISocketPrivateChannel : IPrivateChannel { new IReadOnlyCollection Recipients { get; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs index 74ca02dba..4c224e09a 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs @@ -3,17 +3,18 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Text; using System.Threading.Tasks; -using Discord.Audio; -using Discord.Rest; using Model = Discord.API.Channel; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based category channel. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel { + /// public override IReadOnlyCollection Users => Guild.Users.Where(x => Permissions.GetValue( Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), @@ -55,8 +56,12 @@ namespace Discord.WebSocket => ImmutableArray.Create>(Users).ToAsyncEnumerable(); Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// + /// This method is not supported with category channels. Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) => throw new NotSupportedException(); + /// + /// This method is not supported with category channels. Task> IGuildChannel.GetInvitesAsync(RequestOptions options) => throw new NotSupportedException(); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 502e61d15..e0fb93d6a 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -1,4 +1,3 @@ -using Discord.Rest; using System; using System.Collections.Generic; using System.Diagnostics; @@ -8,16 +7,27 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based channel. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class SocketChannel : SocketEntity, IChannel { + /// + /// Gets when the channel is created. + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// + /// Gets a collection of users from the WebSocket cache. + /// public IReadOnlyCollection Users => GetUsersInternal(); internal SocketChannel(DiscordSocketClient discord, ulong id) : base(discord, id) { } + + /// Unexpected channel type is created. internal static ISocketPrivateChannel CreatePrivate(DiscordSocketClient discord, ClientState state, Model model) { switch (model.Type) @@ -33,17 +43,32 @@ namespace Discord.WebSocket internal abstract void Update(ClientState state, Model model); //User + /// + /// Gets the user from the WebSocket cache. + /// + /// + /// This method does NOT attempt to fetch the user if they don't exist in the cache. To guarantee a return + /// from an existing user that doesn't exist in cache, use . + /// + /// The ID of the user. + /// + /// The user. + /// public SocketUser GetUser(ulong id) => GetUserInternal(id); internal abstract SocketUser GetUserInternal(ulong id); internal abstract IReadOnlyCollection GetUsersInternal(); + private string DebuggerDisplay => $"Unknown ({Id}, Channel)"; internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; //IChannel + /// string IChannel.Name => null; + /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overridden } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index ca53315aa..e6339b6d9 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -57,7 +57,7 @@ namespace Discord.WebSocket else return ImmutableArray.Create(); } - + /// Unexpected type. public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord, SocketMessage msg) { @@ -66,9 +66,10 @@ namespace Discord.WebSocket case SocketDMChannel dmChannel: dmChannel.AddMessage(msg); break; case SocketGroupChannel groupChannel: groupChannel.AddMessage(msg); break; case SocketTextChannel textChannel: textChannel.AddMessage(msg); break; - default: throw new NotSupportedException("Unexpected ISocketMessageChannel type"); + default: throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type."); } } + /// Unexpected type. public static SocketMessage RemoveMessage(ISocketMessageChannel channel, DiscordSocketClient discord, ulong id) { @@ -77,7 +78,7 @@ namespace Discord.WebSocket case SocketDMChannel dmChannel: return dmChannel.RemoveMessage(id); case SocketGroupChannel groupChannel: return groupChannel.RemoveMessage(id); case SocketTextChannel textChannel: return textChannel.RemoveMessage(id); - default: throw new NotSupportedException("Unexpected ISocketMessageChannel type"); + default: throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type."); } } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 11b7ce25a..af8854f21 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -10,13 +10,17 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based direct-message channel. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel { private readonly MessageCache _messages; - public SocketUser Recipient { get; private set; } + public SocketUser Recipient { get; } + /// public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); public new IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); @@ -26,7 +30,7 @@ namespace Discord.WebSocket Recipient = recipient; recipient.GlobalUser.AddRef(); if (Discord.MessageCacheSize > 0) - _messages = new MessageCache(Discord, this); + _messages = new MessageCache(Discord); } internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) { @@ -39,10 +43,12 @@ namespace Discord.WebSocket Recipient.Update(state, model.Recipients.Value[0]); } + /// public Task CloseAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); //Messages + /// public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); public async Task GetMessageAsync(ulong id, RequestOptions options = null) @@ -58,21 +64,29 @@ namespace Discord.WebSocket => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); + /// public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); + /// public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); + /// public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); + /// public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + /// + /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + /// public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - + /// + /// Message content is too long, length must be less or equal to . public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); @@ -81,8 +95,10 @@ namespace Discord.WebSocket public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); + /// public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); @@ -102,24 +118,33 @@ namespace Discord.WebSocket return null; } + /// + /// Returns the recipient user. + /// public override string ToString() => $"@{Recipient}"; private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; internal new SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; //SocketChannel + /// internal override IReadOnlyCollection GetUsersInternal() => Users; + /// internal override SocketUser GetUserInternal(ulong id) => GetUser(id); - //IDMChannel + //IDMChannel + /// IUser IDMChannel.Recipient => Recipient; //ISocketPrivateChannel + /// IReadOnlyCollection ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); //IPrivateChannel + /// IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); //IMessageChannel + /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -127,28 +152,37 @@ namespace Discord.WebSocket else return GetCachedMessage(id); } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); + /// async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + /// async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + /// IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); - //IChannel + //IChannel + /// string IChannel.Name => $"@{Recipient}"; + /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 81b5cfbf3..5eb2a6e12 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -14,6 +14,9 @@ using VoiceStateModel = Discord.API.VoiceState; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based private group channel. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel { @@ -21,10 +24,12 @@ namespace Discord.WebSocket private string _iconId; private ConcurrentDictionary _users; - private ConcurrentDictionary _voiceStates; + private readonly ConcurrentDictionary _voiceStates; + /// public string Name { get; private set; } + /// public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); public new IReadOnlyCollection Users => _users.ToReadOnlyCollection(); public IReadOnlyCollection Recipients @@ -34,7 +39,7 @@ namespace Discord.WebSocket : base(discord, id) { if (Discord.MessageCacheSize > 0) - _messages = new MessageCache(Discord, this); + _messages = new MessageCache(Discord); _voiceStates = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, 5); _users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, 5); } @@ -62,15 +67,18 @@ namespace Discord.WebSocket _users = users; } + /// public Task LeaveAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); + /// Voice is not yet supported for group channels. public Task ConnectAsync() { throw new NotSupportedException("Voice is not yet supported for group channels."); } //Messages + /// public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); public async Task GetMessageAsync(ulong id, RequestOptions options = null) @@ -86,21 +94,28 @@ namespace Discord.WebSocket => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); + /// public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); + /// public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); + /// public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); + /// public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + /// + /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + /// public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - + /// public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); @@ -109,8 +124,10 @@ namespace Discord.WebSocket public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); + /// public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); @@ -120,6 +137,17 @@ namespace Discord.WebSocket => _messages?.Remove(id); //Users + /// + /// Gets the group user from the WebSocket cache. + /// + /// + /// This method does NOT attempt to fetch the user if they don't exist in the cache. To guarantee a return + /// from an existing user that doesn't exist in cache, use . + /// + /// The ID of the user. + /// + /// The user in the group. + /// public new SocketGroupUser GetUser(ulong id) { if (_users.TryGetValue(id, out SocketGroupUser user)) @@ -169,21 +197,29 @@ namespace Discord.WebSocket return null; } + /// + /// Returns the name of the group. + /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id}, Group)"; internal new SocketGroupChannel Clone() => MemberwiseClone() as SocketGroupChannel; //SocketChannel + /// internal override IReadOnlyCollection GetUsersInternal() => Users; + /// internal override SocketUser GetUserInternal(ulong id) => GetUser(id); //ISocketPrivateChannel + /// IReadOnlyCollection ISocketPrivateChannel.Recipients => Recipients; //IPrivateChannel + /// IReadOnlyCollection IPrivateChannel.Recipients => Recipients; //IMessageChannel + /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -191,31 +227,43 @@ namespace Discord.WebSocket else return GetCachedMessage(id); } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); + /// async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); + /// async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - + /// async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + /// async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + /// IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); //IAudioChannel - Task IAudioChannel.ConnectAsync(Action configAction) { throw new NotSupportedException(); } + /// + /// Connecting to a group channel is not supported. + Task IAudioChannel.ConnectAsync(Action configAction) => + throw new NotSupportedException(); //IChannel + /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index bfcffa35f..075b2866c 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -9,16 +9,34 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based guild channel. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketGuildChannel : SocketChannel, IGuildChannel { private ImmutableArray _overwrites; + /// + /// Gets the guild associated with this channel. + /// + /// + /// A guild that this channel belongs to. + /// public SocketGuild Guild { get; } + /// public string Name { get; private set; } + /// public int Position { get; private set; } + /// public IReadOnlyCollection PermissionOverwrites => _overwrites; + /// + /// Gets a collection of users that are able to view the channel. + /// + /// + /// A collection of users that can access the channel (i.e. the users seen in the user list). + /// public new virtual IReadOnlyCollection Users => ImmutableArray.Create(); internal SocketGuildChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) @@ -53,8 +71,10 @@ namespace Discord.WebSocket _overwrites = newOverwrites.ToImmutable(); } + /// public Task ModifyAsync(Action func, RequestOptions options = null) => ChannelHelper.ModifyAsync(this, Discord, func, options); + /// public Task DeleteAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); @@ -120,7 +140,11 @@ namespace Discord.WebSocket public new virtual SocketGuildUser GetUser(ulong id) => null; + /// + /// Gets the name of the channel. + /// public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}, Guild)"; internal new SocketGuildChannel Clone() => MemberwiseClone() as SocketGuildChannel; //SocketChannel @@ -128,35 +152,49 @@ namespace Discord.WebSocket internal override SocketUser GetUserInternal(ulong id) => GetUser(id); //IGuildChannel + /// IGuild IGuildChannel.Guild => Guild; + /// ulong IGuildChannel.GuildId => Guild.Id; + /// async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) => await GetInvitesAsync(options).ConfigureAwait(false); + /// async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) => await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); + /// OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) => GetPermissionOverwrite(role); + /// OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) => GetPermissionOverwrite(user); + /// async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); + /// async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); + /// async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); + /// async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); + /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); //IChannel + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice + /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); //Overridden in Text/Voice } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 1d8041585..c34cee754 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -10,21 +10,29 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based channel in a guild that can send and receive messages. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketTextChannel : SocketGuildChannel, ITextChannel, ISocketMessageChannel { private readonly MessageCache _messages; + /// public string Topic { get; private set; } public ulong? CategoryId { get; private set; } public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; private bool _nsfw; + /// public bool IsNsfw => _nsfw || ChannelHelper.IsNsfw(this); + /// public string Mention => MentionUtils.MentionChannel(Id); + /// public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); + /// public override IReadOnlyCollection Users => Guild.Users.Where(x => Permissions.GetValue( Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), @@ -34,7 +42,7 @@ namespace Discord.WebSocket : base(discord, id, guild) { if (Discord.MessageCacheSize > 0) - _messages = new MessageCache(Discord, this); + _messages = new MessageCache(Discord); } internal new static SocketTextChannel Create(SocketGuild guild, ClientState state, Model model) { @@ -50,10 +58,12 @@ namespace Discord.WebSocket _nsfw = model.Nsfw.GetValueOrDefault(); } + /// public Task ModifyAsync(Action func, RequestOptions options = null) => ChannelHelper.ModifyAsync(this, Discord, func, options); //Messages + /// public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); public async Task GetMessageAsync(ulong id, RequestOptions options = null) @@ -69,26 +79,34 @@ namespace Discord.WebSocket => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); + /// public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); + /// public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); + /// public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); + /// public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + /// public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + /// public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - + /// public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + /// public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + /// public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); @@ -97,6 +115,7 @@ namespace Discord.WebSocket public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) @@ -108,6 +127,7 @@ namespace Discord.WebSocket => _messages?.Remove(id); //Users + /// public override SocketGuildUser GetUser(ulong id) { var user = Guild.GetUser(id); @@ -122,10 +142,34 @@ namespace Discord.WebSocket } //Webhooks + /// + /// Creates a webhook in this text channel. + /// + /// The name of the webhook. + /// The avatar of the webhook. + /// The options to be used when sending the request. + /// + /// The created webhook. + /// public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); + /// + /// Gets the webhook in this text channel with the provided ID. + /// + /// The ID of the webhook. + /// The options to be used when sending the request. + /// + /// A webhook associated with the , or null if not found. + /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetWebhookAsync(this, Discord, id, options); + /// + /// Gets the webhooks for this text channel. + /// + /// The options to be used when sending the request. + /// + /// A collection of webhooks. + /// public Task> GetWebhooksAsync(RequestOptions options = null) => ChannelHelper.GetWebhooksAsync(this, Discord, options); @@ -133,20 +177,26 @@ namespace Discord.WebSocket internal new SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel; //ITextChannel + /// async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options) - => await CreateWebhookAsync(name, avatar, options); + => await CreateWebhookAsync(name, avatar, options).ConfigureAwait(false); + /// async Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options) - => await GetWebhookAsync(id, options); + => await GetWebhookAsync(id, options).ConfigureAwait(false); + /// async Task> ITextChannel.GetWebhooksAsync(RequestOptions options) - => await GetWebhooksAsync(options); + => await GetWebhooksAsync(options).ConfigureAwait(false); //IGuildChannel + /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //IMessageChannel + /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -154,22 +204,29 @@ namespace Discord.WebSocket else return GetCachedMessage(id); } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); + /// async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); + /// async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - + /// async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + /// async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + /// IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 349621fac..ee932e0cd 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -10,15 +10,21 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based voice channel in a guild. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel { + /// public int Bitrate { get; private set; } + /// public int? UserLimit { get; private set; } public ulong? CategoryId { get; private set; } public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; + /// public override IReadOnlyCollection Users => Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray(); @@ -40,14 +46,17 @@ namespace Discord.WebSocket UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; } + /// public Task ModifyAsync(Action func, RequestOptions options = null) => ChannelHelper.ModifyAsync(this, Discord, func, options); + /// public async Task ConnectAsync(Action configAction = null) { return await Guild.ConnectAudioAsync(Id, false, false, configAction).ConfigureAwait(false); } + /// public override SocketGuildUser GetUser(ulong id) { var user = Guild.GetUser(id); @@ -60,8 +69,10 @@ namespace Discord.WebSocket internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; //IGuildChannel + /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 14263f0af..81106ec48 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -21,6 +21,10 @@ using VoiceStateModel = Discord.API.VoiceState; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based guild object. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketGuild : SocketEntity, IGuild { private readonly SemaphoreSlim _audioLock; @@ -34,38 +38,75 @@ namespace Discord.WebSocket private ImmutableArray _features; private AudioClient _audioClient; + /// public string Name { get; private set; } + /// public int AFKTimeout { get; private set; } + /// public bool IsEmbeddable { get; private set; } + /// public VerificationLevel VerificationLevel { get; private set; } + /// public MfaLevel MfaLevel { get; private set; } + /// public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } + /// + /// Gets the number of members. + /// + /// + /// The number of members is returned by Discord and is the most accurate. You may see discrepancy between + /// the collection and this. + /// public int MemberCount { get; internal set; } + /// Gets the number of members downloaded to the local guild cache. public int DownloadedMemberCount { get; private set; } internal bool IsAvailable { get; private set; } + /// Indicates whether the client is connected to this guild. public bool IsConnected { get; internal set; } internal ulong? AFKChannelId { get; private set; } internal ulong? EmbedChannelId { get; private set; } internal ulong? SystemChannelId { get; private set; } + /// public ulong OwnerId { get; private set; } + /// Gets the user that owns this guild. public SocketGuildUser Owner => GetUser(OwnerId); + /// public string VoiceRegionId { get; private set; } + /// public string IconId { get; private set; } + /// public string SplashId { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); + /// public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); + /// Indicates whether the client has all the members downloaded to the local guild cache. public bool HasAllMembers => MemberCount == DownloadedMemberCount;// _downloaderPromise.Task.IsCompleted; + /// Indicates whether the guild cache is synced to this guild. public bool IsSynced => _syncPromise.Task.IsCompleted; public Task SyncPromise => _syncPromise.Task; public Task DownloaderPromise => _downloaderPromise.Task; + /// + /// Gets the associated with this guild. + /// public IAudioClient AudioClient => _audioClient; + /// + /// Gets the first viewable text channel. + /// + /// + /// This property does not guarantee the user can send message to it. + /// public SocketTextChannel DefaultChannel => TextChannels .Where(c => CurrentUser.GetPermissions(c).ViewChannel) .OrderBy(c => c.Position) .FirstOrDefault(); + /// + /// Gets the AFK voice channel, or null if none is set. + /// public SocketVoiceChannel AFKChannel { get @@ -74,6 +115,9 @@ namespace Discord.WebSocket return id.HasValue ? GetVoiceChannel(id.Value) : null; } } + /// + /// Gets the embed channel set in the widget settings of this guild, or null if none is set. + /// public SocketGuildChannel EmbedChannel { get @@ -82,6 +126,9 @@ namespace Discord.WebSocket return id.HasValue ? GetChannel(id.Value) : null; } } + /// + /// Gets the channel where randomized welcome messages are sent, or null if none is set. + /// public SocketTextChannel SystemChannel { get @@ -90,14 +137,35 @@ namespace Discord.WebSocket return id.HasValue ? GetTextChannel(id.Value) : null; } } + /// + /// Gets a collection of text channels present in this guild. + /// public IReadOnlyCollection TextChannels => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); + /// + /// Gets a collection of voice channels present in this guild. + /// public IReadOnlyCollection VoiceChannels => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); + /// + /// Gets a collection of category channels present in this guild. + /// public IReadOnlyCollection CategoryChannels => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); + /// + /// Gets the current logged-in user. + /// public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; + /// + /// Gets the @everyone role in this guild. + /// public SocketRole EveryoneRole => GetRole(Id); + /// + /// Gets a collection of channels present in this guild. + /// + /// + /// Collection of channels. + /// public IReadOnlyCollection Channels { get @@ -107,9 +175,38 @@ namespace Discord.WebSocket return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); } } + /// + /// Gets a collection of emotes created in this guild. + /// + /// + /// Collection of emotes. + /// public IReadOnlyCollection Emotes => _emotes; + /// + /// Gets a collection of features enabled in this guild. + /// + /// + /// Collection of features in string. + /// public IReadOnlyCollection Features => _features; + /// + /// Gets a collection of users in this guild. + /// + /// + /// This property may not always return all the members for large guilds (i.e. guilds containing 100+ users). + /// You may need to enable to fetch the full user list + /// upon startup, or use to manually download the users. + /// + /// + /// Collection of users. + /// public IReadOnlyCollection Users => _members.ToReadOnlyCollection(); + /// + /// Gets a collection of roles in this guild. + /// + /// + /// Collection of roles. + /// public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); internal SocketGuild(DiscordSocketClient client, ulong id) @@ -270,22 +367,38 @@ namespace Discord.WebSocket } //General + /// public Task DeleteAsync(RequestOptions options = null) => GuildHelper.DeleteAsync(this, Discord, options); + /// + /// is null. public Task ModifyAsync(Action func, RequestOptions options = null) => GuildHelper.ModifyAsync(this, Discord, func, options); + + /// + /// is null. public Task ModifyEmbedAsync(Action func, RequestOptions options = null) => GuildHelper.ModifyEmbedAsync(this, Discord, func, options); + /// public Task ReorderChannelsAsync(IEnumerable args, RequestOptions options = null) => GuildHelper.ReorderChannelsAsync(this, Discord, args, options); + /// public Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null) => GuildHelper.ReorderRolesAsync(this, Discord, args, options); + /// public Task LeaveAsync(RequestOptions options = null) => GuildHelper.LeaveAsync(this, Discord, options); //Bans + /// + /// Returns a collection of the banned users in this guild. + /// + /// The options to be used when sending the request. + /// + /// A collection of bans. + /// public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); public Task GetBanAsync(IUser user, RequestOptions options = null) @@ -293,17 +406,28 @@ namespace Discord.WebSocket public Task GetBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.GetBanAsync(this, Discord, userId, options); + /// public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); + /// public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); + /// public Task RemoveBanAsync(IUser user, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); + /// public Task RemoveBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, userId, options); //Channels + /// + /// Returns a guild channel with the provided ID. + /// + /// The channel ID. + /// + /// The guild channel associated with the ID. + /// public SocketGuildChannel GetChannel(ulong id) { var channel = Discord.State.GetChannel(id) as SocketGuildChannel; @@ -311,14 +435,56 @@ namespace Discord.WebSocket return channel; return null; } + /// + /// Returns a text channel with the provided ID. + /// + /// The channel ID. + /// + /// The text channel associated with the ID. + /// public SocketTextChannel GetTextChannel(ulong id) => GetChannel(id) as SocketTextChannel; + /// + /// Returns a voice channel with the provided ID. + /// + /// The channel ID. + /// + /// The voice channel associated with the ID. + /// public SocketVoiceChannel GetVoiceChannel(ulong id) => GetChannel(id) as SocketVoiceChannel; + + /// + /// Creates a text channel with the provided name. + /// + /// The name of the new channel. + /// The options to be used when sending the request. + /// is null. + /// + /// The created text channel. + /// public Task CreateTextChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateTextChannelAsync(this, Discord, name, options, func); + /// + /// Creates a voice channel with the provided name. + /// + /// The name of the new channel. + /// The options to be used when sending the request. + /// is null. + /// + /// The created voice channel. + /// public Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); + /// + /// Creates a category channel with the provided name. + /// + /// The name of the new channel. + /// The options to be used when sending the request. + /// is null. + /// + /// The created category channel. + /// public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); @@ -343,16 +509,45 @@ namespace Discord.WebSocket => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); //Invites + /// + /// Returns a collection of invites associated with this channel. + /// + /// The options to be used when sending the request. + /// + /// A collection of invites. + /// public Task> GetInvitesAsync(RequestOptions options = null) => GuildHelper.GetInvitesAsync(this, Discord, options); //Roles + /// + /// Returns a role with the provided role ID. + /// + /// The ID of the role. + /// + /// The role associated with the ID. + /// public SocketRole GetRole(ulong id) { if (_roles.TryGetValue(id, out SocketRole value)) return value; return null; } + + /// + /// Creates a role. + /// + /// The name of the new role. + /// + /// The permissions that the new role possesses. Set to null to use the default permissions. + /// + /// The color of the role. Set to null to use the default color. + /// Used to determine if users of this role are separated in the user list. + /// The options to be used when sending the request. + /// is null. + /// + /// The created role. + /// public Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false, RequestOptions options = null) => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options); @@ -370,12 +565,20 @@ namespace Discord.WebSocket } //Users + /// + /// Gets the user with the provided ID. + /// + /// The ID of the user. + /// + /// The user associated with the ID. + /// public SocketGuildUser GetUser(ulong id) { if (_members.TryGetValue(id, out SocketGuildUser member)) return member; return null; } + /// public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); @@ -429,6 +632,9 @@ namespace Discord.WebSocket return null; } + /// + /// Downloads the users of this guild to the WebSocket cache. + /// public async Task DownloadUsersAsync() { await Discord.DownloadUsersAsync(new[] { this }).ConfigureAwait(false); @@ -443,18 +649,38 @@ namespace Discord.WebSocket => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); //Webhooks + /// + /// Returns the webhook with the provided ID. + /// + /// The ID of the webhook. + /// The options to be used when sending the request. + /// + /// An awaitable Task containing the webhook associated with the ID. + /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => GuildHelper.GetWebhookAsync(this, Discord, id, options); + /// + /// Gets a collection of webhooks that exist in the guild. + /// + /// The options to be used when sending the request. + /// + /// An awaitable Task containing a collection of webhooks. + /// public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); //Emotes + /// public Task GetEmoteAsync(ulong id, RequestOptions options = null) => GuildHelper.GetEmoteAsync(this, Discord, id, options); + /// public Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null) => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); + /// + /// is null. public Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null) => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); + /// public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); @@ -562,7 +788,7 @@ namespace Discord.WebSocket try { var timeoutTask = Task.Delay(15000); - if (await Task.WhenAny(promise.Task, timeoutTask) == timeoutTask) + if (await Task.WhenAny(promise.Task, timeoutTask).ConfigureAwait(false) == timeoutTask) throw new TimeoutException(); return await promise.Task.ConfigureAwait(false); } @@ -633,20 +859,32 @@ namespace Discord.WebSocket } } + /// + /// Gets the name of the guild. + /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; internal SocketGuild Clone() => MemberwiseClone() as SocketGuild; //IGuild + /// ulong? IGuild.AFKChannelId => AFKChannelId; + /// IAudioClient IGuild.AudioClient => null; + /// bool IGuild.Available => true; + /// ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; + /// ulong? IGuild.EmbedChannelId => EmbedChannelId; + /// ulong? IGuild.SystemChannelId => SystemChannelId; + /// IRole IGuild.EveryoneRole => EveryoneRole; + /// IReadOnlyCollection IGuild.Roles => Roles; + /// async Task> IGuild.GetBansAsync(RequestOptions options) => await GetBansAsync(options).ConfigureAwait(false); /// @@ -656,54 +894,77 @@ namespace Discord.WebSocket async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) => await GetBanAsync(userId, options).ConfigureAwait(false); + /// Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Channels); + /// Task IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetChannel(id)); + /// Task> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(TextChannels); + /// Task IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetTextChannel(id)); + /// Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(VoiceChannels); + /// Task> IGuild.GetCategoriesAsync(CacheMode mode , RequestOptions options) => Task.FromResult>(CategoryChannels); + /// Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetVoiceChannel(id)); + /// Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(AFKChannel); + /// Task IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(DefaultChannel); + /// Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(EmbedChannel); + /// Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(SystemChannel); + /// async Task IGuild.CreateTextChannelAsync(string name, Action func, RequestOptions options) => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false); + /// async Task IGuild.CreateVoiceChannelAsync(string name, Action func, RequestOptions options) => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); + /// async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); + /// async Task> IGuild.GetIntegrationsAsync(RequestOptions options) => await GetIntegrationsAsync(options).ConfigureAwait(false); + /// async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); + /// async Task> IGuild.GetInvitesAsync(RequestOptions options) => await GetInvitesAsync(options).ConfigureAwait(false); + /// IRole IGuild.GetRole(ulong id) => GetRole(id); + /// async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); + /// Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Users); + /// Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + /// Task IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) => Task.FromResult(CurrentUser); + /// Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Owner); @@ -715,9 +976,11 @@ namespace Discord.WebSocket return ImmutableArray.Create(); } + /// async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) - => await GetWebhookAsync(id, options); + => await GetWebhookAsync(id, options).ConfigureAwait(false); + /// async Task> IGuild.GetWebhooksAsync(RequestOptions options) - => await GetWebhooksAsync(options); + => await GetWebhooksAsync(options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index c2cad4d86..8cac95cd3 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -14,7 +14,7 @@ namespace Discord.WebSocket public IReadOnlyCollection Messages => _messages.ToReadOnlyCollection(); - public MessageCache(DiscordSocketClient discord, IChannel channel) + public MessageCache(DiscordSocketClient discord) { _size = discord.MessageCacheSize; _messages = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(_size * 1.05)); @@ -28,7 +28,7 @@ namespace Discord.WebSocket _orderedMessages.Enqueue(message.Id); while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out ulong msgId)) - _messages.TryRemove(msgId, out SocketMessage msg); + _messages.TryRemove(msgId, out SocketMessage _); } } @@ -44,6 +44,8 @@ namespace Discord.WebSocket return result; return null; } + + /// is less than 0. public IReadOnlyCollection GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 5442c888a..0767f2ad7 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -8,27 +8,80 @@ using Model = Discord.API.Message; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based message. + /// public abstract class SocketMessage : SocketEntity, IMessage { private long _timestampTicks; - + + /// + /// Gets the author of this message. + /// + /// + /// A WebSocket-based user object. + /// public SocketUser Author { get; } + /// + /// Gets the source channel of the message. + /// + /// + /// A WebSocket-based message channel. + /// public ISocketMessageChannel Channel { get; } + /// public MessageSource Source { get; } + /// public string Content { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// public virtual bool IsTTS => false; + /// public virtual bool IsPinned => false; + /// public virtual DateTimeOffset? EditedTimestamp => null; + /// + /// Returns all attachments included in this message. + /// + /// + /// Collection of attachments. + /// public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); + /// + /// Returns all embeds included in this message. + /// + /// + /// Collection of embed objects. + /// public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); + /// + /// Returns the channels mentioned in this message. + /// + /// + /// Collection of WebSocket-based guild channels. + /// public virtual IReadOnlyCollection MentionedChannels => ImmutableArray.Create(); + /// + /// Returns the roles mentioned in this message. + /// + /// + /// Collection of WebSocket-based roles. + /// public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); + /// + /// Returns the users mentioned in this message. + /// + /// + /// Collection of WebSocket-based users. + /// public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + /// public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); + /// public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) @@ -54,20 +107,35 @@ namespace Discord.WebSocket Content = model.Content.Value; } + /// public Task DeleteAsync(RequestOptions options = null) => MessageHelper.DeleteAsync(this, Discord, options); + /// + /// Gets the content of the message. + /// + /// + /// Content of the message. + /// public override string ToString() => Content; internal SocketMessage Clone() => MemberwiseClone() as SocketMessage; //IMessage + /// IUser IMessage.Author => Author; + /// IMessageChannel IMessage.Channel => Channel; + /// MessageType IMessage.Type => MessageType.Default; + /// IReadOnlyCollection IMessage.Attachments => Attachments; + /// IReadOnlyCollection IMessage.Embeds => Embeds; + /// IReadOnlyCollection IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); + /// IReadOnlyCollection IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); + /// IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs index e8fa17a35..8df6d51b4 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs @@ -1,14 +1,48 @@ -using Model = Discord.API.Gateway.Reaction; +using Model = Discord.API.Gateway.Reaction; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based reaction object. + /// public class SocketReaction : IReaction { + /// + /// Gets the ID of the user who added the reaction. + /// + /// + /// A user snowflake ID. + /// public ulong UserId { get; } + /// + /// Gets the user who added the reaction if possible. + /// + /// + /// A user object where possible. This value is not always returned. + /// public Optional User { get; } + /// + /// Gets the ID of the message that has been reacted to. + /// + /// + /// A message snowflake ID. + /// public ulong MessageId { get; } + /// + /// Gets the message that has been reacted to if possible. + /// + /// + /// A WebSocket-based message where possible. This value is not always returned. + /// public Optional Message { get; } + /// + /// Gets the channel where the reaction takes place in. + /// + /// + /// A WebSocket-based message channel. + /// public ISocketMessageChannel Channel { get; } + /// public IEmote Emote { get; } internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional message, ulong userId, Optional user, IEmote emoji) @@ -30,6 +64,7 @@ namespace Discord.WebSocket return new SocketReaction(channel, model.MessageId, message, model.UserId, user, emote); } + /// public override bool Equals(object other) { if (other == null) return false; @@ -41,6 +76,7 @@ namespace Discord.WebSocket return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote); } + /// public override int GetHashCode() { unchecked diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs index e6c67159f..d0ce5025b 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs @@ -1,11 +1,15 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.Message; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based message sent by the system. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketSystemMessage : SocketMessage, ISystemMessage { + /// public MessageType Type { get; private set; } internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index f8c15a986..7730e5363 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -9,6 +9,9 @@ using Model = Discord.API.Message; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based message sent by a user. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketUserMessage : SocketMessage, IUserMessage { @@ -17,24 +20,34 @@ namespace Discord.WebSocket private ImmutableArray _attachments; private ImmutableArray _embeds; private ImmutableArray _tags; - private List _reactions = new List(); - + private readonly List _reactions = new List(); + + /// public override bool IsTTS => _isTTS; + /// public override bool IsPinned => _isPinned; + /// public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); + /// public override IReadOnlyCollection Attachments => _attachments; + /// public override IReadOnlyCollection Embeds => _embeds; + /// public override IReadOnlyCollection Tags => _tags; + /// public override IReadOnlyCollection MentionedChannels => MessageHelper.FilterTagsByValue(TagType.ChannelMention, _tags); + /// public override IReadOnlyCollection MentionedRoles => MessageHelper.FilterTagsByValue(TagType.RoleMention, _tags); + /// public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); + /// public IReadOnlyDictionary Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) : base(discord, id, channel, author, source) { } - internal static new SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) + internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) { var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); entity.Update(state, model); @@ -121,30 +134,40 @@ namespace Discord.WebSocket _reactions.Clear(); } + /// + /// Only the author of a message may modify the message. + /// Message content is too long, length must be less or equal to . public Task ModifyAsync(Action func, RequestOptions options = null) => MessageHelper.ModifyAsync(this, Discord, func, options); + /// public Task AddReactionAsync(IEmote emote, RequestOptions options = null) => MessageHelper.AddReactionAsync(this, emote, Discord, options); + /// public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); + /// public Task RemoveAllReactionsAsync(RequestOptions options = null) => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); + /// public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); + /// public Task PinAsync(RequestOptions options = null) => MessageHelper.PinAsync(this, Discord, options); + /// public Task UnpinAsync(RequestOptions options = null) => MessageHelper.UnpinAsync(this, Discord, options); public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); + /// public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); - + private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage; } diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index c366258cc..14af11e07 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System; using System.Collections.Generic; using System.Diagnostics; @@ -13,16 +13,25 @@ namespace Discord.WebSocket { public SocketGuild Guild { get; } + /// public Color Color { get; private set; } + /// public bool IsHoisted { get; private set; } + /// public bool IsManaged { get; private set; } + /// public bool IsMentionable { get; private set; } + /// public string Name { get; private set; } + /// public GuildPermissions Permissions { get; private set; } + /// public int Position { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public bool IsEveryone => Id == Guild.Id; + /// public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); public IEnumerable Members => Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id)); @@ -49,8 +58,10 @@ namespace Discord.WebSocket Permissions = new GuildPermissions(model.Permissions); } + /// public Task ModifyAsync(Action func, RequestOptions options = null) => RoleHelper.ModifyAsync(this, Discord, func, options); + /// public Task DeleteAsync(RequestOptions options = null) => RoleHelper.DeleteAsync(this, Discord, options); @@ -58,9 +69,11 @@ namespace Discord.WebSocket private string DebuggerDisplay => $"{Name} ({Id})"; internal SocketRole Clone() => MemberwiseClone() as SocketRole; + /// public int CompareTo(IRole role) => RoleUtils.Compare(this, role); //IRole + /// IGuild IRole.Guild => Guild; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 3117eb14c..48de7552a 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Linq; using Model = Discord.API.User; using PresenceModel = Discord.API.Presence; @@ -54,7 +54,8 @@ namespace Discord.WebSocket Presence = SocketPresence.Create(model); DMChannel = state.DMChannels.FirstOrDefault(x => x.Recipient.Id == Id); } - + + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)"; internal new SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs index 8d1b360e3..601677e2e 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.User; namespace Discord.WebSocket @@ -9,12 +9,17 @@ namespace Discord.WebSocket public SocketGroupChannel Channel { get; } internal override SocketGlobalUser GlobalUser { get; } + /// public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } + /// public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } + /// public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } + /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } + /// public override bool IsWebhook => false; internal SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser globalUser) @@ -30,15 +35,23 @@ namespace Discord.WebSocket return entity; } + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Group)"; internal new SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser; //IVoiceState + /// bool IVoiceState.IsDeafened => false; + /// bool IVoiceState.IsMuted => false; + /// bool IVoiceState.IsSelfDeafened => false; + /// bool IVoiceState.IsSelfMuted => false; + /// bool IVoiceState.IsSuppressed => false; + /// IVoiceChannel IVoiceState.VoiceChannel => null; + /// string IVoiceState.VoiceSessionId => null; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 66af20bb6..8721e722b 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -1,4 +1,4 @@ -using Discord.Audio; +using Discord.Audio; using Discord.Rest; using System; using System.Collections.Generic; @@ -12,6 +12,9 @@ using PresenceModel = Discord.API.Presence; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based guild user. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketGuildUser : SocketUser, IGuildUser { @@ -19,33 +22,60 @@ namespace Discord.WebSocket private ImmutableArray _roleIds; internal override SocketGlobalUser GlobalUser { get; } + /// + /// Gets the guild the user is in. + /// public SocketGuild Guild { get; } + /// public string Nickname { get; private set; } + /// public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } + /// public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } + /// public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } + /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } + /// public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); internal override SocketPresence Presence { get; set; } + /// public override bool IsWebhook => false; + /// public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; + /// public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; + /// public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; + /// public bool IsDeafened => VoiceState?.IsDeafened ?? false; + /// public bool IsMuted => VoiceState?.IsMuted ?? false; + /// public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); + /// + /// Returns a collection of roles that the user possesses. + /// public IReadOnlyCollection Roles => _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null).ToReadOnlyCollection(() => _roleIds.Length); + /// + /// Returns the voice channel the user is in, or null if none. + /// public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; + /// public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); public AudioInStream AudioStream => Guild.GetAudioStream(Id); - /// The position of the user within the role hierarchy. - /// The returned value equal to the position of the highest role the user has, - /// or int.MaxValue if user is the server owner. + /// + /// Returns the position of the user within the role hierarchy. + /// + /// + /// The returned value equal to the position of the highest role the user has, or + /// if user is the server owner. + /// public int Hierarchy { get @@ -119,9 +149,11 @@ namespace Discord.WebSocket roles.Add(roleIds[i]); _roleIds = roles.ToImmutable(); } - + + /// public Task ModifyAsync(Action func, RequestOptions options = null) => UserHelper.ModifyAsync(this, Discord, func, options); + /// public Task KickAsync(string reason = null, RequestOptions options = null) => UserHelper.KickAsync(this, Discord, reason, options); /// @@ -137,17 +169,23 @@ namespace Discord.WebSocket public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) => UserHelper.RemoveRolesAsync(this, Discord, roles, options); + /// public ChannelPermissions GetPermissions(IGuildChannel channel) => new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, GuildPermissions.RawValue)); + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Guild)"; internal new SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; //IGuildUser + /// IGuild IGuildUser.Guild => Guild; + /// ulong IGuildUser.GuildId => Guild.Id; + /// IReadOnlyCollection IGuildUser.RoleIds => _roleIds; - + //IVoiceState + /// IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs index 7d7ba16ce..8942bdf32 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -1,13 +1,14 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.Presence; namespace Discord.WebSocket { - //TODO: C#7 Candidate for record type [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct SocketPresence : IPresence { + /// public UserStatus Status { get; } + /// public IActivity Activity { get; } internal SocketPresence(UserStatus status, IActivity activity) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index b7c02c2db..af7710629 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System; using System.Diagnostics; using System.Threading.Tasks; @@ -9,17 +9,26 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketSelfUser : SocketUser, ISelfUser { + /// public string Email { get; private set; } + /// public bool IsVerified { get; private set; } + /// public bool IsMfaEnabled { get; private set; } internal override SocketGlobalUser GlobalUser { get; } + /// public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } + /// public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } + /// public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } + /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } + /// internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } + /// public override bool IsWebhook => false; internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) @@ -53,10 +62,12 @@ namespace Discord.WebSocket } return hasGlobalChanges; } - + + /// public Task ModifyAsync(Action func, RequestOptions options = null) => UserHelper.ModifyAsync(this, Discord, func, options); + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Self)"; internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs index c7f6cb846..b3eb08f6d 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using Model = Discord.API.User; @@ -15,7 +15,8 @@ namespace Discord.WebSocket public override bool IsWebhook => false; internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } - internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } + internal override SocketGlobalUser GlobalUser => + throw new NotSupportedException(); internal SocketUnknownUser(DiscordSocketClient discord, ulong id) : base(discord, id) @@ -28,6 +29,7 @@ namespace Discord.WebSocket return entity; } + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Unknown)"; internal new SocketUnknownUser Clone() => MemberwiseClone() as SocketUnknownUser; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 00899d47e..9a101cddb 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -1,24 +1,39 @@ using Discord.Rest; using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based user. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class SocketUser : SocketEntity, IUser { + /// public abstract bool IsBot { get; internal set; } + /// public abstract string Username { get; internal set; } + /// public abstract ushort DiscriminatorValue { get; internal set; } + /// public abstract string AvatarId { get; internal set; } + /// public abstract bool IsWebhook { get; } internal abstract SocketGlobalUser GlobalUser { get; } internal abstract SocketPresence Presence { get; set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// public string Discriminator => DiscriminatorValue.ToString("D4"); + /// public string Mention => MentionUtils.MentionUser(Id); + /// public IActivity Activity => Presence.Activity; + /// public UserStatus Status => Presence.Status; internal SocketUser(DiscordSocketClient discord, ulong id) @@ -55,15 +70,24 @@ namespace Discord.WebSocket return hasChanges; } + /// public async Task GetOrCreateDMChannelAsync(RequestOptions options = null) - => GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options) as IDMChannel; + => GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options).ConfigureAwait(false) as IDMChannel; + /// public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + /// public string GetDefaultAvatarUrl() => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); + /// + /// Gets the full name of the user (e.g. Example#0001). + /// + /// + /// The full name of the user. + /// public override string ToString() => $"{Username}#{Discriminator}"; private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; internal SocketUser Clone() => MemberwiseClone() as SocketUser; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs index 480103326..d5f0433ad 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs @@ -1,10 +1,9 @@ -using System; +using System; using System.Diagnostics; using Model = Discord.API.VoiceState; namespace Discord.WebSocket { - //TODO: C#7 Candidate for record type [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct SocketVoiceState : IVoiceState { @@ -22,14 +21,23 @@ namespace Discord.WebSocket } private readonly Flags _voiceStates; - + + /// + /// Gets the voice channel that the user is currently in; or null if none. + /// public SocketVoiceChannel VoiceChannel { get; } + /// public string VoiceSessionId { get; } + /// public bool IsMuted => (_voiceStates & Flags.Muted) != 0; + /// public bool IsDeafened => (_voiceStates & Flags.Deafened) != 0; + /// public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0; + /// public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0; + /// public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed) @@ -55,6 +63,12 @@ namespace Discord.WebSocket return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress); } + /// + /// Gets the name of the voice channel. + /// + /// + /// The name of the voice channel. + /// public override string ToString() => VoiceChannel?.Name ?? "Unknown"; private string DebuggerDisplay => $"{VoiceChannel?.Name ?? "Unknown"} ({_voiceStates})"; internal SocketVoiceState Clone() => this; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index dd80648d2..b66f14e7d 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -10,18 +10,26 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketWebhookUser : SocketUser, IWebhookUser { + /// Gets the guild of this webhook. public SocketGuild Guild { get; } + /// public ulong WebhookId { get; } + /// public override string Username { get; internal set; } + /// public override ushort DiscriminatorValue { get; internal set; } + /// public override string AvatarId { get; internal set; } + /// public override bool IsBot { get; internal set; } + /// public override bool IsWebhook => true; internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } - internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } + internal override SocketGlobalUser GlobalUser => + throw new NotSupportedException(); internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId) : base(guild.Discord, id) @@ -36,51 +44,70 @@ namespace Discord.WebSocket return entity; } + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Webhook)"; internal new SocketWebhookUser Clone() => MemberwiseClone() as SocketWebhookUser; //IGuildUser + /// IGuild IGuildUser.Guild => Guild; + /// ulong IGuildUser.GuildId => Guild.Id; + /// IReadOnlyCollection IGuildUser.RoleIds => ImmutableArray.Create(); + /// DateTimeOffset? IGuildUser.JoinedAt => null; + /// string IGuildUser.Nickname => null; + /// GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; + /// ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); - Task IGuildUser.KickAsync(string reason, RequestOptions options) - { + /// + /// Webhook users cannot be kicked. + Task IGuildUser.KickAsync(string reason, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be kicked."); - } - Task IGuildUser.ModifyAsync(Action func, RequestOptions options) - { + + /// + /// Webhook users cannot be modified. + Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be modified."); - } - Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) - { + /// + /// Roles are not supported on webhook users. + Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) - { + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) - { + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) - { + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } //IVoiceState + /// bool IVoiceState.IsDeafened => false; + /// bool IVoiceState.IsMuted => false; + /// bool IVoiceState.IsSelfDeafened => false; + /// bool IVoiceState.IsSelfMuted => false; + /// bool IVoiceState.IsSuppressed => false; + /// IVoiceChannel IVoiceState.VoiceChannel => null; + /// string IVoiceState.VoiceSessionId => null; } } diff --git a/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs b/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs index 57abf1d03..c5f13b1a9 100644 --- a/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs +++ b/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs @@ -2,12 +2,33 @@ using System.Diagnostics; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based voice server. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketVoiceServer { - public Cacheable Guild { get; private set; } - public string Endpoint { get; private set; } - public string Token { get; private set; } + /// + /// Gets the guild associated with the voice server. + /// + /// + /// A cached entity of the guild. + /// + public Cacheable Guild { get; } + /// + /// Gets the endpoint URL of the voice server host. + /// + /// + /// An URL representing the voice server host. + /// + public string Endpoint { get; } + /// + /// Gets the voice connection token. + /// + /// + /// A voice connection token. + /// + public string Token { get; } internal SocketVoiceServer(Cacheable guild, string endpoint, string token) { diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs index 2d66d5900..bc580c410 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs @@ -7,6 +7,7 @@ namespace Discord.Net.WebSockets { public static readonly WebSocketProvider Instance = Create(); + /// The default WebSocketProvider is not supported on this platform. public static WebSocketProvider Create(IWebProxy proxy = null) { return () => diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 67a5462be..16841e936 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -7,6 +7,7 @@ using Discord.Rest; namespace Discord.Webhook { + /// A client responsible for connecting as a Webhook. public class DiscordWebhookClient : IDisposable { public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } @@ -19,14 +20,14 @@ namespace Discord.Webhook internal API.DiscordRestApiClient ApiClient { get; } internal LogManager LogManager { get; } - /// Creates a new Webhook discord client. + /// Creates a new Webhook Discord client. public DiscordWebhookClient(IWebhook webhook) : this(webhook.Id, webhook.Token, new DiscordRestConfig()) { } - /// Creates a new Webhook discord client. + /// Creates a new Webhook Discord client. public DiscordWebhookClient(ulong webhookId, string webhookToken) : this(webhookId, webhookToken, new DiscordRestConfig()) { } - /// Creates a new Webhook discord client. + /// Creates a new Webhook Discord client. public DiscordWebhookClient(ulong webhookId, string webhookToken, DiscordRestConfig config) : this(config) { @@ -34,7 +35,7 @@ namespace Discord.Webhook ApiClient.LoginAsync(TokenType.Webhook, webhookToken).GetAwaiter().GetResult(); Webhook = WebhookClientHelper.GetWebhookAsync(this, webhookId).GetAwaiter().GetResult(); } - /// Creates a new Webhook discord client. + /// Creates a new Webhook Discord client. public DiscordWebhookClient(IWebhook webhook, DiscordRestConfig config) : this(config) { @@ -61,18 +62,19 @@ namespace Discord.Webhook } private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); - - /// Sends a message using to the channel for this webhook. Returns the ID of the created message. + /// Sends a message using to the channel for this webhook. + /// Returns the ID of the created message. public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, options); - /// Send a message to the channel for this webhook with an attachment. Returns the ID of the created message. + /// Sends a message to the channel for this webhook with an attachment. + /// Returns the ID of the created message. public Task SendFileAsync(string filePath, string text, bool isTTS = false, IEnumerable embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) => WebhookClientHelper.SendFileAsync(this, filePath, text, isTTS, embeds, username, avatarUrl, options); - - /// Send a message to the channel for this webhook with an attachment. Returns the ID of the created message. + /// Sends a message to the channel for this webhook with an attachment. + /// Returns the ID of the created message. public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, IEnumerable embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) => WebhookClientHelper.SendFileAsync(this, stream, filename, text, isTTS, embeds, username, avatarUrl, options); diff --git a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs index cd35d731c..60cb89ee2 100644 --- a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs +++ b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs @@ -49,7 +49,7 @@ namespace Discord.Webhook public async Task ModifyAsync(Action func, RequestOptions options = null) { - var model = await WebhookClientHelper.ModifyAsync(_client, func, options); + var model = await WebhookClientHelper.ModifyAsync(_client, func, options).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index d3cac9703..a02cb3e2f 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -12,11 +12,12 @@ namespace Discord.Webhook { internal static class WebhookClientHelper { + /// Could not find a webhook with the supplied credentials. public static async Task GetWebhookAsync(DiscordWebhookClient client, ulong webhookId) { - var model = await client.ApiClient.GetWebhookAsync(webhookId); + var model = await client.ApiClient.GetWebhookAsync(webhookId).ConfigureAwait(false); if (model == null) - throw new InvalidOperationException("Could not find a webhook for the supplied credentials."); + throw new InvalidOperationException("Could not find a webhook with the supplied credentials."); return RestInternalWebhook.Create(client, model); } public static async Task SendMessageAsync(DiscordWebhookClient client,