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/CONTRIBUTING.md b/CONTRIBUTING.md index 8248291e8..752b40931 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,4 +41,22 @@ We attempt to conform to the .NET Foundation's [Coding Style](https://github.com where possible. As a general rule, follow the coding style already set in the file you -are editing, or look at a similar file if you are adding a new one. \ No newline at end of file +are editing, or look at a similar file if you are adding a new one. + +### Documentation Style for Members + +When creating a new public member, the member must be annotated with sufficient documentation. This should include the +following, but not limited to: + +* `` summarizing the purpose of the method. +* `` or `` explaining the parameter. +* `` explaining the type of the returned member and what it is. +* `` if the method directly throws an exception. + +The length of the documentation should also follow the ruler as suggested by our +[Visual Studio Code workspace](Discord.Net.code-workspace). + +#### Recommended Reads + +* [Official Microsoft Documentation](https://docs.microsoft.com) +* [Sandcastle User Manual](https://ewsoftware.github.io/XMLCommentsGuide/html/4268757F-CE8D-4E6D-8502-4F7F2E22DDA3.htm) \ No newline at end of file diff --git a/Discord.Net.code-workspace b/Discord.Net.code-workspace new file mode 100644 index 000000000..709eb0e95 --- /dev/null +++ b/Discord.Net.code-workspace @@ -0,0 +1,23 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "editor.rulers": [ + 120 + ], + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "docs/": true, + "**/obj": true, + "**/bin": true, + "samples/": true, + } + } +} \ No newline at end of file diff --git a/Discord.Net.sln.DotSettings b/Discord.Net.sln.DotSettings new file mode 100644 index 000000000..ca75a7f1b --- /dev/null +++ b/Discord.Net.sln.DotSettings @@ -0,0 +1,16 @@ + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 296b6d1cb..8641d377e 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,27 +1,21 @@ # Contributing to Docs -I don't really have any strict conditions for writing documentation, -but just keep these few guidelines in mind: +## General Guidelines + +We do not have any strict conditions for writing documentation, +but keep these 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 its page in the + API documentation +* Documentation should be written in an FAQ/Wiki-style format * 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 - -Documentation should be written in a FAQ/Wiki style format. - -Recommended reads: - -* http://docs.microsoft.com -* http://flask.pocoo.org/docs/0.12/ - -Style consistencies: +## Style Consistencies * Use a ruler set at 70 characters * Links should use long syntax @@ -29,18 +23,13 @@ Style consistencies: Example of long link syntax: -``` +```md 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. +## Recommended Reads -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 +* http://docs.microsoft.com +* http://flask.pocoo.org/docs/0.12/ \ No newline at end of file diff --git a/docs/Discord.Net.Docs.code-workspace b/docs/Discord.Net.Docs.code-workspace new file mode 100644 index 000000000..d9f442869 --- /dev/null +++ b/docs/Discord.Net.Docs.code-workspace @@ -0,0 +1,21 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "editor.rulers": [ + 70 + ], + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "obj/": true, + "_site/": true, + } + } +} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index a672330d4..4a06dccab 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,16 +1,15 @@ # 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. Build the docs using `docfx docs/docfx.json`. Add the `--serve` + parameter to preview the site locally. Some elements of the page + may appear incorrectly when hosted offline. + +Please note that if you intend to target a specific version, ensure +that you have the correct version checked out. [docfx-main]: https://dotnet.github.io/docfx/ -[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html +[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html \ No newline at end of file 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..4c1257b23 --- /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/intro/empty-module.cs)] +[!code[Command handler](../../guides/commands/samples/intro/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/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll b/docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll new file mode 100644 index 000000000..3e3a6fb7c Binary files /dev/null and b/docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll differ diff --git a/docs/_template/description-generator/plugins/LICENSE b/docs/_template/description-generator/plugins/LICENSE new file mode 100644 index 000000000..eb92c0a03 --- /dev/null +++ b/docs/_template/description-generator/plugins/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Still Hsu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/_template/last-modified/plugins/LICENSE b/docs/_template/last-modified/plugins/LICENSE new file mode 100644 index 000000000..eb92c0a03 --- /dev/null +++ b/docs/_template/last-modified/plugins/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Still Hsu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll b/docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll new file mode 100644 index 000000000..d91cf0873 Binary files /dev/null and b/docs/_template/last-modified/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..aafcdf05b --- /dev/null +++ b/docs/_template/light-dark-theme/partials/affix.tmpl.partial @@ -0,0 +1,33 @@ +{{!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..94670f432 --- /dev/null +++ b/docs/_template/light-dark-theme/partials/head.tmpl.partial @@ -0,0 +1,27 @@ +{{!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..1b048feb9 --- /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..0a021b377 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/dark.css @@ -0,0 +1,304 @@ +/* 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 */ + +.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 */ +.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; +} + +/* + Following code regarding collapse container are fetched + or modified from the Materialize project. + + The MIT License (MIT) + Copyright (c) 2014-2018 Materialize + https://github.com/Dogfalo/materialize +*/ + +/* all collapse container */ +.collapse-container.last-modified { + -webkit-box-shadow: 0 2px 2px 0 rgba(50, 50, 50, 0.64), 0 3px 1px -2px rgba(50, 50, 50, 0.62), 0 1px 5px 0 rgba(50, 50, 50, 0.7); + box-shadow: 0 2px 2px 0 rgba(50, 50, 50, 0.64), 0 3px 1px -2px rgba(50, 50, 50, 0.62), 0 1px 5px 0 rgba(50, 50, 50, 0.7); + border-top: 1px solid rgba(96, 96, 96, 0.7); + border-right: 1px solid rgba(96, 96, 96, 0.7); + border-left: 1px solid rgba(96, 96, 96, 0.7); +} + +/* header */ +.collapse-container.last-modified>:nth-child(odd) { + background-color: #3f3f3f; + border-bottom: 1px solid rgba(96, 96, 96, 0.7); +} + +/* body */ +.collapse-container.last-modified>:nth-child(even) { + border-bottom: 1px solid rgba(96, 96, 96, 0.7); + background-color: inherit; +} + +span.arrow-d{ + border-top: 5px solid white +} + +span.arrow-r{ + border-left: 5px solid white +} \ No newline at end of file diff --git a/docs/_template/light-dark-theme/styles/docfx.vendor.minify.css b/docs/_template/light-dark-theme/styles/docfx.vendor.minify.css new file mode 100644 index 000000000..f9eb380d6 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/docfx.vendor.minify.css @@ -0,0 +1,1022 @@ +@font-face { + font-family: 'Glyphicons Halflings'; + font-display: fallback; + src: url(../fonts/glyphicons-halflings-regular.eot); + src: url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'), url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'), url(../fonts/glyphicons-halflings-regular.woff) format('woff'), url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'), url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg') +} + +body { + margin: 0; +} + +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +article, +footer, +header, +nav { + display: block; +} + +a { + background-color: transparent; +} + +a:active, +a:hover { + outline: 0; +} + +strong { + font-weight: 700; +} + +h1 { + margin: .67em 0; +} + +svg:not(:root) { + overflow: hidden; +} + +pre { + overflow: auto; +} + +code, +pre { + font-size: 1em; +} + +button, +input, +select { + margin: 0; + font: inherit; + color: inherit; +} + +.glyphicon { + font-style: normal; +} + +button { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +button { + -webkit-appearance: button; + cursor: pointer; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +table { + border-spacing: 0; + border-collapse: collapse; +} + +td, +th { + padding: 0; +} + +@media print { + + pre, + tr { + page-break-inside: avoid; + } + + *, + :after, + :before { + color: #000 !important; + text-shadow: none !important; + background: 0 0 !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " ("attr(href) ")"; + } + + a[href^="#"]:after { + content: ""; + } + + pre { + border: 1px solid #999; + } + + thead { + display: table-header-group; + } + + h3, + p { + orphans: 3; + widows: 3; + } + + h3 { + page-break-after: avoid; + } + + .navbar { + display: none; + } + + .table { + border-collapse: collapse !important; + } + + .table td, + .table th { + background-color: #fff !important; + } + + .table-bordered td, + .table-bordered th { + border: 1px solid #ddd !important; + } +} + +.collapsing, +.dropdown, +.dropup { + position: relative +} + +.collapsing { + height: 0; + overflow: hidden; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility +} + +.btn, +.btn:active, +.form-control, +.navbar-toggle { + background-image: none; +} + +body { + background-color: #fff; +} + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-weight: 400; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.glyphicon-refresh:before { + content: "\e031"; +} + +.glyphicon-filter:before { + content: "\e138"; +} + +*, +:after, +:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 10px; + -webkit-tap-highlight-color: transparent; +} + +body { + font-size: 14px; + line-height: 1.42857143; + color: #333; +} + +button, +input, +select { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +a { + color: #337ab7; + text-decoration: none; +} + +a:focus, +a:hover { + color: #23527c; + text-decoration: underline; +} + +a:focus { + outline: -webkit-focus-ring-color auto 5px; + outline-offset: -2px; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +h1, +h3, +h4, +h5, +h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +h1, +h3 { + margin-top: 20px; + margin-bottom: 10px; +} + +h4, +h5, +h6 { + margin-top: 10px; + margin-bottom: 10px; +} + +h1 { + font-size: 36px; +} + +h3 { + font-size: 24px; +} + +h4 { + font-size: 18px; +} + +h5 { + font-size: 14px; +} + +h6 { + font-size: 12px; +} + +p { + margin: 0 0 10px; +} + +pre { + line-height: 1.42857143; +} + +.small { + font-size: 85%; +} + +pre code, +table { + background-color: transparent; +} + +ul { + margin-top: 0; +} + +ul ul { + margin-bottom: 0; +} + +ul { + margin-bottom: 10px; +} + +@media (min-width:768px) { + .container { + width: 750px; + } +} + +code { + padding: 2px 4px; + font-size: 90%; +} + +th { + text-align: left; +} + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + word-break: break-all; + word-wrap: break-word; + color: #333; + border: 1px solid #ccc; + border-radius: 4px; +} + +.container { + margin-right: auto; + margin-left: auto; +} + +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + border-radius: 0; +} + +.container { + padding-right: 15px; + padding-left: 15px; +} + +@media (min-width:992px) { + .container { + width: 970px; + } +} + +@media (min-width:1200px) { + .container { + width: 1170px; + } +} + +.row { + margin-right: -15px; + margin-left: -15px; +} + +.col-md-10, +.col-md-2 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +@media (min-width:992px) { + + .col-md-10, + .col-md-2 { + float: left; + } + + .col-md-10 { + width: 83.33333333%; + } + + .col-md-2 { + width: 16.66666667%; + } +} + +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} + +.table>tbody>tr>td, +.table>thead>tr>th { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} + +.table>thead>tr>th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} + +.table>thead:first-child>tr:first-child>th { + border-top: 0; +} + +.table-condensed>tbody>tr>td, +.table-condensed>thead>tr>th { + padding: 5px; +} + +.table-bordered, +.table-bordered>tbody>tr>td, +.table-bordered>thead>tr>th { + border: 1px solid #ddd; +} + +.table-bordered>thead>tr>th { + border-bottom-width: 2px; +} + +.table-striped>tbody>tr:nth-of-type(odd) { + background-color: #f9f9f9; +} + +.table-responsive { + min-height: .01%; + overflow-x: auto; +} + +@media screen and (max-width:767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + + .table-responsive>.table { + margin-bottom: 0; + } + + .table-responsive>.table>tbody>tr>td, + .table-responsive>.table>thead>tr>th { + white-space: nowrap; + } + + .table-responsive>.table-bordered { + border: 0; + } + + .table-responsive>.table-bordered>tbody>tr>td:first-child, + .table-responsive>.table-bordered>thead>tr>th:first-child { + border-left: 0; + } + + .table-responsive>.table-bordered>tbody>tr>td:last-child, + .table-responsive>.table-bordered>thead>tr>th:last-child { + border-right: 0; + } + + .table-responsive>.table-bordered>tbody>tr:last-child>td { + border-bottom: 0; + } +} + +.form-control { + font-size: 14px; + line-height: 1.42857143; + color: #555; + display: block; +} + +.form-control { + width: 100%; + height: 34px; + padding: 6px 12px; + background-color: #fff; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} + +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); +} + +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} + +.form-control:-ms-input-placeholder { + color: #999; +} + +.form-control::-webkit-input-placeholder { + color: #999; +} + +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} + +.form-group { + margin-bottom: 15px; +} + +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: 400; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border: 1px solid transparent; + border-radius: 4px; +} + +.btn:active:focus, +.btn:focus { + outline: -webkit-focus-ring-color auto 5px; + outline-offset: -2px; +} + +.btn:focus, +.btn:hover { + color: #333; + text-decoration: none; +} + +.btn:active { + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} + +.collapse { + display: none +} + +.collapse.in { + display: block +} + +.nav>li, +.nav>li>a { + display: block; + position: relative; +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav>li>a { + padding: 10px 15px; +} + +.nav>li>a:focus, +.nav>li>a:hover { + text-decoration: none; + background-color: #eee; +} + +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} + +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} + +@media (min-width:768px) { + .navbar { + border-radius: 4px; + } + + .navbar-header { + float: left; + } + + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } +} + +.container>.navbar-collapse, +.container>.navbar-header { + margin-right: -15px; + margin-left: -15px; +} + +.navbar-brand { + float: left; + height: 50px; + padding: 15px; + font-size: 18px; + line-height: 20px; +} + +.navbar-brand:focus, +.navbar-brand:hover { + text-decoration: none; +} + +@media (min-width:768px) { + + .container>.navbar-collapse, + .container>.navbar-header { + margin-right: 0; + margin-left: 0; + } + + .navbar>.container .navbar-brand { + margin-left: -15px; + } +} + +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + border: 1px solid transparent; + border-radius: 4px; +} + +.navbar-toggle:focus { + outline: 0; +} + +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} + +.navbar-toggle .icon-bar+.icon-bar { + margin-top: 4px; +} + +.navbar-nav { + margin: 7.5px -15px; +} + +.navbar-nav>li>a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} + +@media (min-width:768px) { + .navbar-toggle { + display: none; + } + + .navbar-nav { + float: left; + margin: 0; + } + + .navbar-nav>li { + float: left; + } + + .navbar-nav>li>a { + padding-top: 15px; + padding-bottom: 15px; + } +} + +.navbar-form { + padding: 10px 15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + margin: 8px -15px; +} + +@media (min-width:768px) { + .navbar-form .form-group { + display: inline-block; + } + + .navbar-form .form-group { + margin-bottom: 0; + vertical-align: middle; + } + + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} + +.breadcrumb>li { + display: inline-block; +} + +@media (max-width:767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} + +@media (min-width:768px) { + .navbar-right { + float: right !important; + margin-right: -15px; + } +} + +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} + +.navbar-inverse { + background-color: #222; + border-color: #080808; +} + +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} + +.navbar-inverse .navbar-brand:focus, +.navbar-inverse .navbar-brand:hover { + color: #fff; + background-color: transparent; +} + +.navbar-inverse .navbar-nav>li>a { + color: #9d9d9d; +} + +.navbar-inverse .navbar-nav>li>a:focus, +.navbar-inverse .navbar-nav>li>a:hover { + color: #fff; + background-color: transparent; +} + +.navbar-inverse .navbar-nav>.active>a, +.navbar-inverse .navbar-nav>.active>a:focus, +.navbar-inverse .navbar-nav>.active>a:hover { + color: #fff; + background-color: #080808; +} + +.navbar-inverse .navbar-toggle { + border-color: #333; +} + +.navbar-inverse .navbar-toggle:focus, +.navbar-inverse .navbar-toggle:hover { + background-color: #333; +} + +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} + +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} + +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} + +.breadcrumb>li+li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} + +.alert { + margin-bottom: 20px; +} + +.alert { + padding: 15px; + border: 1px solid transparent; + border-radius: 4px; +} + +.alert>p { + margin-bottom: 0; +} + +.container:after, +.container:before, +.nav:after, +.nav:before, +.navbar-collapse:after, +.navbar-collapse:before, +.navbar-header:after, +.navbar-header:before, +.navbar:after, +.navbar:before, +.row:after, +.row:before { + display: table; + content: " "; +} + +.container:after, +.nav:after, +.navbar-collapse:after, +.navbar-header:after, +.navbar:after, +.row:after { + clear: both; +} + +.pull-right { + float: right !important; +} + +.affix { + position: fixed; +} + +@media (max-width:767px) { + .hidden-xs { + display: none !important; + } +} + +@media (min-width:768px) and (max-width:991px) { + .hidden-sm { + display: none !important; + } +} + +@media print { + .hidden-print { + display: none !important; + } +} + +.hide { + display: none !important; +} + +.show { + display: block !important; +} + +.pagination { + display: inline-block; +} + +.pagination { + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} + +.pagination>li { + display: inline; +} + +.pagination>li>a { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} + +.pagination>li:first-child>a { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.pagination>li:last-child>a { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.pagination>li>a:focus, +.pagination>li>a:hover { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; +} + +.pagination>.active>a, +.pagination>.active>a:focus, +.pagination>.active>a:hover { + z-index: 3; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; +} + +.pagination>.disabled>a, +.pagination>.disabled>a:focus, +.pagination>.disabled>a:hover { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} diff --git a/docs/_template/light-dark-theme/styles/gray.css b/docs/_template/light-dark-theme/styles/gray.css new file mode 100644 index 000000000..1d914368a --- /dev/null +++ b/docs/_template/light-dark-theme/styles/gray.css @@ -0,0 +1,311 @@ +/* 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: #23272A; + color: #dddddd; +} + +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; +} + +/* top navbar */ +.navbar-inverse[role="navigation"] { + background-color: #2C2F33; +} + +/* sub navbar (below top) */ +.subnav { + background: #282B2F +} + + +.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 */ + +.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: #2C2F33; +} + +/* alert */ +.alert-info { + color: #f3fdff; + background: #40788A; + border-color: #2F7A95; +} + +.alert-warning { + color: #fffaf2; + background: #936C36; + border-color: #AE8443; +} + +.alert-danger { + color: #fff4f4; + background: #834040; + border-color: #8C2F2F +} + +/* 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: #5B646B; + 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; +} + +/* + Following code regarding collapse container are fetched + or modified from the Materialize project. + + The MIT License (MIT) + Copyright (c) 2014-2018 Materialize + https://github.com/Dogfalo/materialize +*/ + +/* all collapse container */ +.collapse-container.last-modified { + -webkit-box-shadow: 0 2px 2px 0 rgba(50, 50, 50, 0.64), 0 3px 1px -2px rgba(50, 50, 50, 0.62), 0 1px 5px 0 rgba(50, 50, 50, 0.7); + box-shadow: 0 2px 2px 0 rgba(50, 50, 50, 0.64), 0 3px 1px -2px rgba(50, 50, 50, 0.62), 0 1px 5px 0 rgba(50, 50, 50, 0.7); + border-top: 1px solid rgba(96, 96, 96, 0.7); + border-right: 1px solid rgba(96, 96, 96, 0.7); + border-left: 1px solid rgba(96, 96, 96, 0.7); +} + +/* header */ +.collapse-container.last-modified>:nth-child(odd) { + background-color: #3f3f3f; + border-bottom: 1px solid rgba(96, 96, 96, 0.7); +} + +/* body */ +.collapse-container.last-modified>:nth-child(even) { + border-bottom: 1px solid rgba(96, 96, 96, 0.7); + background-color: inherit; +} + +span.arrow-d{ + border-top: 5px solid white +} + +span.arrow-r{ + border-left: 5px solid white +} \ No newline at end of file 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..a5360711f --- /dev/null +++ b/docs/_template/light-dark-theme/styles/light.css @@ -0,0 +1,113 @@ +/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */ + +@import url('tomorrow.css'); +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; +} + +/* alert */ +.alert-info { + color: #165e82; + background-color: #c1e0ef; + border-color: #8cbfd8; +} + +.alert-warning { + color: #825e16; + background-color: #efe0c1; + border-color: #d8bf8c; +} + +.alert-danger { + color: #821616; + background-color: #efc1c1; + border-color: #d88c8c; +} + +/* code */ + +code { + color: #9c3a3f; + 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; +} + +/* + Following code regarding collapse container are fetched + or modified from the Materialize project. + + The MIT License (MIT) + Copyright (c) 2014-2018 Materialize + https://github.com/Dogfalo/materialize +*/ + +/* all collapse container */ +.collapse-container.last-modified { + -webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2); + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2); + border-top: 1px solid #ddd; + border-right: 1px solid #ddd; + border-left: 1px solid #ddd; +} + +/* header */ +.collapse-container.last-modified>:nth-child(odd) { + background-color: #fff; + border-bottom: 1px solid #ddd; +} + +/* body */ +.collapse-container.last-modified>:nth-child(even) { + border-bottom: 1px solid #ddd; +} + +span.arrow-d{ + border-top: 5px solid black +} + +span.arrow-r{ + border-left: 5px solid black +} \ No newline at end of file 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..4e0dffd7a --- /dev/null +++ b/docs/_template/light-dark-theme/styles/master.css @@ -0,0 +1,156 @@ +@import url('https://fonts.googleapis.com/css?family=Titillium+Web|Noto+Sans'); + +html, +body { + font-family: 'Titillium Web', 'Segoe UI', Tahoma, Helvetica, sans-serif; + font-display: optional; + height: 100%; + font-size: 15px; + scroll-behavior: smooth; +} + +p, +li, +.toc { + text-rendering: optimizeLegibility; + line-height: 160%; +} + +img { + box-shadow: 0px 0px 3px 0px rgb(66, 66, 66); + max-width: 95% !important; + margin-top: 15px; + margin-bottom: 15px; +} + +article.content p{ + -webkit-transition: all .75s ease-in-out; + transition: all .75s ease-in-out; +} + +article.content h1, +article.content h2, +article.content h3, +article.content h4, +article.content h5, +article.content h6{ + -webkit-transition: all .25s ease-in-out; + transition: all .25s ease-in-out; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: 'Noto Sans', Verdana, Geneva, Tahoma, sans-serif; + 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; +} + +/* + Following code are fetched or modified from + the Materialize project. + + The MIT License (MIT) + Copyright (c) 2014-2018 Materialize + https://github.com/Dogfalo/materialize +*/ + +/* all collapse container */ + +.collapse-container.last-modified { + margin: 0.5rem 0 1rem 0; +} + +/* header */ + +.collapse-container.last-modified>:nth-child(odd):focus { + outline: 0; +} + +.collapse-container.last-modified>:nth-child(odd) { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + line-height: 1.5; + padding: 0.75rem; + background-image: none; + border: 0px; +} + +/* body */ + +.collapse-container.last-modified>:nth-child(even) { + display: none; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 1rem; + border: 0px; +} + +/* nav bar */ + +.nav { + margin: 0; +} + +.nav li { + -webkit-transition: background-color .3s, color .3s; + transition: background-color .3s, color .3s; +} + +.nav a { + -webkit-transition: background-color .3s, color .3s; + transition: background-color .3s, color .3s; + cursor: pointer; +} + +/* arrow */ + +span.arrow-d{ + top: 6px; position: relative; +} + +span.arrow-r{ + top: 6px; position: relative; +} \ No newline at end of file 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..2fd056fa0 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/plugin-featherlight.js @@ -0,0 +1,37 @@ +// MIT License + +// Copyright (c) 2017 Roel Fauconnier + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +$(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/tomorrow.css b/docs/_template/light-dark-theme/styles/tomorrow.css new file mode 100644 index 000000000..026a62fe3 --- /dev/null +++ b/docs/_template/light-dark-theme/styles/tomorrow.css @@ -0,0 +1,72 @@ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #8e908c; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #c82829; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #f5871f; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #eab700; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #718c00; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #4271ae; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #8959a8; +} + +.hljs { + display: block; + overflow-x: auto; + background: white; + color: #4d4d4c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} 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..2a50869d6 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -1,74 +1,54 @@ { - "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/**", + "langwordMapping.yml" + ] + }], "dest": "_site", "template": [ - "default" + "default", + "_template/light-dark-theme", + "_template/last-modified", + "_template/description-generator" ], + "postProcessors": ["ExtractSearchIndex", "LastModifiedPostProcessor", "DescriptionPostProcessor"], + "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 + "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..16f98c3b9 --- /dev/null +++ b/docs/faq/basics/basic-operations.md @@ -0,0 +1,123 @@ +--- +uid: FAQ.Basics.BasicOp +title: Questions about Basic Operations +--- + +# Basic Operations Questions + +In the following section, you will find commonly asked questions and +answers regarding basic usage of the library, as well as +language-specific tips when using this library. + +## 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.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. + +# [Adding a reaction to another message](#tab/emoji-others) + +[!code-csharp[Emoji](samples/emoji-others.cs)] + +# [Adding a reaction to a sent message](#tab/emoji-self) + +[!code-csharp[Emoji](samples/emoji-self.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..5c529a3f5 --- /dev/null +++ b/docs/faq/basics/client-basics.md @@ -0,0 +1,66 @@ +--- +uid: FAQ.Basics.ClientBasics +title: Basic Questions about Client +--- + +# Client Basics Questions + +In the following section, you will find commonly asked questions and +answers about common issues that you may face when utilizing the +various clients offered by the library. + +## 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..f38d2cea7 --- /dev/null +++ b/docs/faq/basics/getting-started.md @@ -0,0 +1,79 @@ +--- +uid: FAQ.Basics.GetStarted +title: Beginner Questions / How to Get Started +--- + +# Basic Concepts / Getting Started + +In this following section, you will find commonly asked questions and +answers about how to get started with Discord.Net, as well as basic +introduction to the Discord API ecosystem. + +## How do I add my bot to my server/guild? + +You can do so by using the [permission calculator] provided +by [FiniteReality]. +This tool allows you to set permissions that the bot will be assigned +with, and invite the bot into your guild. With this method, bots will +also be assigned a unique role that a regular user cannot use; this +is what we call a `Managed` role. Because you cannot assign this +role to any other users, it is much safer than creating a single +role which, intentionally or not, can be applied to other users +to escalate their privilege. + +[FiniteReality]: https://github.com/FiniteReality/permissions-calculator +[permission calculator]: https://finitereality.github.io/permissions-calculator + +## 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) + +Anyone can see the ID; 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-others.cs b/docs/faq/basics/samples/emoji-others.cs new file mode 100644 index 000000000..dd3e6317f --- /dev/null +++ b/docs/faq/basics/samples/emoji-others.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/basics/samples/emoji-self.cs b/docs/faq/basics/samples/emoji-self.cs new file mode 100644 index 000000000..cd4cff171 --- /dev/null +++ b/docs/faq/basics/samples/emoji-self.cs @@ -0,0 +1,17 @@ +// capture the message you're sending in a variable +var msg = await channel.SendMessageAsync("This will have reactions added."); + +// 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 msg.AddReactionAsync(emoji); +await msg.AddReactionAsync(emote); \ No newline at end of file diff --git a/docs/faq/commands/dependency-injection.md b/docs/faq/commands/dependency-injection.md new file mode 100644 index 000000000..0a5de3e32 --- /dev/null +++ b/docs/faq/commands/dependency-injection.md @@ -0,0 +1,54 @@ +--- +uid: FAQ.Commands.DI +title: Questions about Dependency Injection with Commands +--- + +# Dependency-injection-related Questions + +In the following section, you will find common questions and answers +to utilizing dependency injection with @Discord.Commands, as well as +common troubleshooting steps regarding DI. + +## 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 + +## Why is my `CommandService` complaining about a missing dependency? + +If you encounter an error similar to `Failed to create MyModule, +dependency MyExternalDependency was not found.`, you may have +forgotten to add the external dependency to the dependency container. + +Starting from Discord.Net 2.0, all dependencies required by each +module must be present when the module is loaded into the +[CommandService]. This means when loading the module, you must pass a +valid [IServiceProvider] with the dependency loaded before the module +can be successfully registered. + +For example, if your module, `MyModule`, requests a `DatabaseService` +in its constructor, the `DatabaseService` must be present in the +[IServiceProvider] when registering `MyModule`. + +[!code-csharp[Missing Dependencies](samples/missing-dep.cs)] + +[IServiceProvider]: xref:System.IServiceProvider +[CommandService]: xref:Discord.Commands.CommandService diff --git a/docs/faq/commands/general.md b/docs/faq/commands/general.md new file mode 100644 index 000000000..fb6fe39b6 --- /dev/null +++ b/docs/faq/commands/general.md @@ -0,0 +1,142 @@ +--- +uid: FAQ.Commands.General +title: General Questions about Commands +--- + +# Command-related Questions + +In the following section, you will find commonly asked questions and +answered regarding general command usage when using @Discord.Commands. + +## How can I restrict some of my commands so only specific users can execute them? + +Based on how you want to implement the restrictions, you can use the +built-in [RequireUserPermission] precondition, which allows you to +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 + +## 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..ce4454bc2 --- /dev/null +++ b/docs/faq/commands/samples/DI.cs @@ -0,0 +1,28 @@ +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 the module's constructor + + // private readonly MyService _myService; + // public MyModule (MyService myService) => _myService = myService; + + [Command("string")] + public Task GetOrSetStringAsync(string input) + { + if (string.IsNullOrEmpty(_myService.MyCoolString)) _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/missing-dep.cs b/docs/faq/commands/samples/missing-dep.cs new file mode 100644 index 000000000..d3fb9085b --- /dev/null +++ b/docs/faq/commands/samples/missing-dep.cs @@ -0,0 +1,29 @@ +public class MyModule : ModuleBase +{ + private readonly DatabaseService _dbService; + public MyModule(DatabaseService dbService) + => _dbService = dbService; +} +public class CommandHandler +{ + private readonly CommandService _commands; + private readonly IServiceProvider _services; + public CommandHandler(DiscordSocketClient client) + { + _services = new ServiceCollection() + .AddService() + .AddService(client) + // We are missing DatabaseService! + .BuildServiceProvider(); + } + public async Task RegisterCommandsAsync() + { + // ... + // The method fails here because DatabaseService is a required + // dependency and cannot be resolved by the dependency + // injection service at runtime since the service is not + // registered in this instance of _services. + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + // ... + } +} \ 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..11d9cc295 --- /dev/null +++ b/docs/faq/commands/samples/runmode-cmdconfig.cs @@ -0,0 +1,10 @@ +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..95bdbaa03 --- /dev/null +++ b/docs/faq/misc/glossary.md @@ -0,0 +1,82 @@ +--- +uid: FAQ.Glossary +title: Common Terminologies / Glossary +--- + +# Glossary + +This is an additional chapter for quick references to various common +types that you may see within Discord.Net. To see more information +regarding each type of object, click on the object to navigate +to our API documentation page where you might find more explanation +about it. + +## 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. +* A **Nested Channel** ([INestedChannel]) (2.0+) is a channel that can +exist under a category. + +[INestedChannel]: xref:Discord.INestedChannel +[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..5931579d3 --- /dev/null +++ b/docs/faq/misc/legacy.md @@ -0,0 +1,29 @@ +--- +uid: FAQ.Legacy +title: Questions about Legacy Versions +--- + +# Legacy Questions + +This section refers to legacy library-related questions that do not +apply to the latest or recent version of the Discord.Net library. + +## 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..393e948f6 --- /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: Commands + items: + - name: General + topicUid: FAQ.Commands.General + - name: Dependency Injection + topicUid: FAQ.Commands.DI +- name: Glossary + topicUid: FAQ.Glossary +- name: Legacy or Upgrade + topicUid: FAQ.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..5dc5b02d2 --- /dev/null +++ b/docs/guides/commands/dependency-injection.md @@ -0,0 +1,47 @@ +--- +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 a @Microsoft.Extensions.DependencyInjection.ServiceCollection. +2. Add the dependencies to the service collection that you wish + to use in the modules. +3. Build the service collection into a service provider. +4. Pass the service collection into @Discord.Commands.CommandService.AddModulesAsync* / @Discord.Commands.CommandService.AddModuleAsync* , @Discord.Commands.CommandService.ExecuteAsync* . + +### Example - Setting up Injection + +[!code-csharp[IServiceProvider Setup](samples/dependency-injection/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[Injection Modules](samples/dependency-injection/dependency_module.cs)] +[!code-csharp[Disallow Dependency Injection](samples/dependency-injection/dependency_module_noinject.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..c815175bd --- /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/intro/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 by the +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/intro/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/intro/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/intro/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..28f63659b --- /dev/null +++ b/docs/guides/commands/post-execution.md @@ -0,0 +1,122 @@ +--- +uid: Guides.Commands.PostExecution +title: Post-command Execution Handling +--- + +# Post-execution Handling for Commands + +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/intro/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/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.General). + +## 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/post-execution/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/post-execution/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/post-execution/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/post-execution/customresult_usage.cs)] + +And now we can check for it in our [CommandExecuted] handler: + +[!code[Usage](samples/post-execution/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/post-execution/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..44b10c513 --- /dev/null +++ b/docs/guides/commands/preconditions.md @@ -0,0 +1,83 @@ +--- +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 + +## Using Preconditions + +To use a precondition, simply apply any valid precondition candidate to +a command method signature as an attribute. + +### Example - Using a Precondition + +[!code-csharp[Precondition usage](samples/preconditions/precondition_usage.cs)] + +## ORing Preconditions + +When writing commands, you may want to allow some of them to be +executed when only some of the precondition checks are passed. + +This is where the [Group] property of a precondition attribute comes in +handy. By assigning two or more preconditions to a group, the command +system will allow the command to be executed when one of the +precondition passes. + +### Example - ORing Preconditions + +[!code-csharp[OR Precondition](samples/preconditions/group_precondition.cs)] + +[Group]: xref:Discord.Commands.PreconditionAttribute.Group + +## 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/preconditions/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_handler.cs b/docs/guides/commands/samples/command_handler.cs deleted file mode 100644 index da2453aa8..000000000 --- a/docs/guides/commands/samples/command_handler.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Reflection; -using Discord; -using Discord.WebSocket; -using Discord.Commands; -using Microsoft.Extensions.DependencyInjection; - -public class Program -{ - private CommandService _commands; - private DiscordSocketClient _client; - private IServiceProvider _services; - - private static void Main(string[] args) => new Program().StartAsync().GetAwaiter().GetResult(); - - public async Task StartAsync() - { - _client = new DiscordSocketClient(); - _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()); - } - - private async Task HandleCommandAsync(SocketMessage messageParam) - { - // Don't process the command if it was a System Message - var message = messageParam as SocketUserMessage; - if (message == null) return; - // Create a number to track where the prefix ends and the command begins - int argPos = 0; - // Determine if the message is a command, based on if it starts with '!' or a mention prefix - if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return; - // Create a Command Context - var context = new SocketCommandContext(_client, message); - // Execute the command. (result does not indicate a return value, - // rather an object stating if the command executed successfully) - var result = await _commands.ExecuteAsync(context, argPos, _services); - if (!result.IsSuccess) - await context.Channel.SendMessageAsync(result.ErrorReason); - } -} \ No newline at end of file diff --git a/docs/guides/commands/samples/dependency-injection/dependency_map_setup.cs b/docs/guides/commands/samples/dependency-injection/dependency_map_setup.cs new file mode 100644 index 000000000..118c032ed --- /dev/null +++ b/docs/guides/commands/samples/dependency-injection/dependency_map_setup.cs @@ -0,0 +1,62 @@ +public class Initialize +{ + private readonly CommandService _commands; + private readonly DiscordSocketClient _client; + + public Initialize(CommandService commands = null, DiscordSocketClient client = null) + { + _commands = commands ?? new CommandService(); + _client = client ?? new DiscordSocketClient(); + } + + public IServiceProvider BuildServiceProvider() => new ServiceCollection() + .AddSingleton(_client) + .AddSingleton(_commands) + // You can pass in an instance of the desired type + .AddSingleton(new NotificationService()) + // ...or by using the generic method. + // + // The benefit of using the generic method is that + // ASP.NET DI will attempt to inject the required + // dependencies that are specified under the constructor + // for us. + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); +} +public class CommandHandler +{ + private readonly DiscordSocketClient _client; + private readonly CommandService _commands; + private readonly IServiceProvider _services; + + public CommandHandler(IServiceProvider services, CommandService commands, DiscordSocketClient client) + { + _commands = commands; + _services = services; + _client = client; + } + + public async Task InitializeAsync() + { + // Pass the service provider to the second parameter of + // AddModulesAsync to inject dependencies to all modules + // that may require them. + await _commands.AddModulesAsync( + assembly: Assembly.GetEntryAssembly(), + services: _services); + _client.MessageReceived += HandleCommandAsync; + } + + public async Task HandleCommandAsync(SocketMessage msg) + { + // ... + // Pass the service provider to the ExecuteAsync method for + // precondition checks. + await _commands.ExecuteAsync( + context: context, + argPos: argPos, + services: _services); + // ... + } +} diff --git a/docs/guides/commands/samples/dependency-injection/dependency_module.cs b/docs/guides/commands/samples/dependency-injection/dependency_module.cs new file mode 100644 index 000000000..f0625e503 --- /dev/null +++ b/docs/guides/commands/samples/dependency-injection/dependency_module.cs @@ -0,0 +1,37 @@ +// After setting up dependency injection, modules will need to request +// the dependencies to let the library know to pass +// them along during execution. + +// Dependency can be injected in two ways with Discord.Net. +// You may inject any required dependencies via... +// the module constructor +// -or- +// public settable properties + +// Injection via constructor +public class DatabaseModule : ModuleBase +{ + private readonly DatabaseService _database; + public DatabaseModule(DatabaseService database) + { + _database = database; + } + + [Command("read")] + public async Task ReadFromDbAsync() + { + await ReplyAsync(_database.GetData()); + } +} + +// Injection via public settable properties +public class DatabaseModule : ModuleBase +{ + public DatabaseService DbService { get; set; } + + [Command("read")] + public async Task ReadFromDbAsync() + { + await ReplyAsync(_database.GetData()); + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/dependency-injection/dependency_module_noinject.cs b/docs/guides/commands/samples/dependency-injection/dependency_module_noinject.cs new file mode 100644 index 000000000..48cd52308 --- /dev/null +++ b/docs/guides/commands/samples/dependency-injection/dependency_module_noinject.cs @@ -0,0 +1,29 @@ +// Sometimes injecting dependencies automatically with the provided +// methods in the prior example may not be desired. + +// You may explicitly tell Discord.Net to **not** inject the properties +// by either... +// restricting the access modifier +// -or- +// applying DontInjectAttribute to the property + +// Restricting the access modifier of the property +public class ImageModule : ModuleBase +{ + public ImageService ImageService { get; } + public ImageModule() + { + ImageService = new ImageService(); + } +} + +// Applying DontInjectAttribute +public class ImageModule : ModuleBase +{ + [DontInject] + public ImageService ImageService { get; set; } + public ImageModule() + { + ImageService = new ImageService(); + } +} diff --git a/docs/guides/commands/samples/dependency_map_setup.cs b/docs/guides/commands/samples/dependency_map_setup.cs deleted file mode 100644 index a36925904..000000000 --- a/docs/guides/commands/samples/dependency_map_setup.cs +++ /dev/null @@ -1,18 +0,0 @@ -private IServiceProvider _services; -private CommandService _commands; - -public async Task InstallAsync(DiscordSocketClient client) -{ - // Here, we will inject the ServiceProvider with - // all of the services our client will use. - _services = new ServiceCollection() - .AddSingleton(client) - .AddSingleton(_commands) - // You can pass in an instance of the desired type - .AddSingleton(new NotificationService()) - // ...or by using the generic method. - .AddSingleton() - .BuildServiceProvider(); - // ... - await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); -} \ No newline at end of file diff --git a/docs/guides/commands/samples/dependency_module.cs b/docs/guides/commands/samples/dependency_module.cs deleted file mode 100644 index 561b0f6ac..000000000 --- a/docs/guides/commands/samples/dependency_module.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.WebSocket; - -public class ModuleA : ModuleBase -{ - private readonly DatabaseService _database; - - // Dependencies can be injected via the constructor - public ModuleA(DatabaseService database) - { - _database = database; - } - - public async Task ReadFromDb() - { - var x = _database.getX(); - await ReplyAsync(x); - } -} - -public class ModuleB -{ - - // Public settable properties will be injected - public AnnounceService { get; set; } - - // Public properties without setters will not - public CommandService Commands { get; } - - // Public properties annotated with [DontInject] will not - [DontInject] - public NotificationService { get; set; } - - public ModuleB(CommandService commands) - { - Commands = commands; - } - -} diff --git a/docs/guides/commands/samples/empty-module.cs b/docs/guides/commands/samples/empty-module.cs deleted file mode 100644 index 6483c7cd2..000000000 --- a/docs/guides/commands/samples/empty-module.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Discord.Commands; - -public class InfoModule : ModuleBase -{ - -} \ No newline at end of file diff --git a/docs/guides/commands/samples/intro/command_handler.cs b/docs/guides/commands/samples/intro/command_handler.cs new file mode 100644 index 000000000..e4531fa41 --- /dev/null +++ b/docs/guides/commands/samples/intro/command_handler.cs @@ -0,0 +1,63 @@ +public class CommandHandler +{ + private readonly DiscordSocketClient _client; + private readonly CommandService _commands; + + public CommandHandler(DiscordSocketClient client, CommandService commands) + { + _commands = commands; + _client = client; + } + + public async Task InstallCommandsAsync() + { + // Hook the MessageReceived event into our command handler + _client.MessageReceived += HandleCommandAsync; + + // Here we discover all of the command modules in the entry + // assembly and load them. Starting from Discord.NET 2.0, a + // service provider is required to be passed into the + // module registration method to inject the + // required dependencies. + // + // If you do not use Dependency Injection, pass null. + // See Dependency Injection guide for more information. + await _commands.AddModulesAsync(assembly: Assembly.GetEntryAssembly(), + services: null); + } + + private async Task HandleCommandAsync(SocketMessage messageParam) + { + // Don't process the command if it was a system message + var message = messageParam as SocketUserMessage; + if (message == null) return; + + // Create a number to track where the prefix ends and the command begins + int argPos = 0; + + // Determine if the message is a command based on the prefix + if (!(message.HasCharPrefix('!', ref argPos) || + message.HasMentionPrefix(_client.CurrentUser, ref argPos))) + return; + + // Create a WebSocket-based command context based on the message + var context = new SocketCommandContext(_client, message); + + // Execute the command with the command context we just + // created, along with the service provider for precondition checks. + + // Keep in mind that result does not indicate a return value + // rather an object stating if the command executed successfully. + var result = await _command.ExecuteAsync( + context: context, + argPos: argPos, + services: null); + + // Optionally, we may inform the user if the command fails + // to be executed; however, this may not always be desired, + // as it may clog up the request queue should a user spam a + // command. + // if (!result.IsSuccess) + // await context.Channel.SendMessageAsync(result.ErrorReason); + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/intro/empty-module.cs b/docs/guides/commands/samples/intro/empty-module.cs new file mode 100644 index 000000000..db62032c2 --- /dev/null +++ b/docs/guides/commands/samples/intro/empty-module.cs @@ -0,0 +1,8 @@ +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 +{ + +} \ No newline at end of file diff --git a/docs/guides/commands/samples/groups.cs b/docs/guides/commands/samples/intro/groups.cs similarity index 52% rename from docs/guides/commands/samples/groups.cs rename to docs/guides/commands/samples/intro/groups.cs index 5f96c34e8..e117a52da 100644 --- a/docs/guides/commands/samples/groups.cs +++ b/docs/guides/commands/samples/intro/groups.cs @@ -4,15 +4,22 @@ public class AdminModule : ModuleBase [Group("clean")] public class CleanModule : ModuleBase { - // ~admin clean 15 + // ~admin clean [Command] - public async Task Default(int count = 10) => Messages(count); + public async Task DefaultCleanAsync() + { + // ... + } // ~admin clean messages 15 [Command("messages")] - public async Task Messages(int count = 10) { } + public async Task CleanAsync(int count) + { + // ... + } } // ~admin ban foxbot#0282 [Command("ban")] - public async Task Ban(IGuildUser user) { } + public Task BanAsync(IGuildUser user) => + Context.Guild.AddBanAsync(user); } \ No newline at end of file diff --git a/docs/guides/commands/samples/module.cs b/docs/guides/commands/samples/intro/module.cs similarity index 58% rename from docs/guides/commands/samples/module.cs rename to docs/guides/commands/samples/intro/module.cs index 1e3555501..e5fe70534 100644 --- a/docs/guides/commands/samples/module.cs +++ b/docs/guides/commands/samples/intro/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/command_exception_log.cs b/docs/guides/commands/samples/post-execution/command_exception_log.cs new file mode 100644 index 000000000..2956a0203 --- /dev/null +++ b/docs/guides/commands/samples/post-execution/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/post-execution/command_executed_adv_demo.cs b/docs/guides/commands/samples/post-execution/command_executed_adv_demo.cs new file mode 100644 index 000000000..1ce40c51d --- /dev/null +++ b/docs/guides/commands/samples/post-execution/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/post-execution/command_executed_demo.cs b/docs/guides/commands/samples/post-execution/command_executed_demo.cs new file mode 100644 index 000000000..8d8fb911b --- /dev/null +++ b/docs/guides/commands/samples/post-execution/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/post-execution/customresult_base.cs b/docs/guides/commands/samples/post-execution/customresult_base.cs new file mode 100644 index 000000000..895a370c7 --- /dev/null +++ b/docs/guides/commands/samples/post-execution/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/post-execution/customresult_extended.cs b/docs/guides/commands/samples/post-execution/customresult_extended.cs new file mode 100644 index 000000000..6754c96aa --- /dev/null +++ b/docs/guides/commands/samples/post-execution/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/post-execution/customresult_usage.cs b/docs/guides/commands/samples/post-execution/customresult_usage.cs new file mode 100644 index 000000000..44266cfb4 --- /dev/null +++ b/docs/guides/commands/samples/post-execution/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, I don't want that!"); + return MyCustomResult.FromSuccess($"Give me the {food}!"). + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/post-execution/post-execution_basic.cs b/docs/guides/commands/samples/post-execution/post-execution_basic.cs new file mode 100644 index 000000000..19c7bed59 --- /dev/null +++ b/docs/guides/commands/samples/post-execution/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/preconditions/group_precondition.cs b/docs/guides/commands/samples/preconditions/group_precondition.cs new file mode 100644 index 000000000..bae102b9a --- /dev/null +++ b/docs/guides/commands/samples/preconditions/group_precondition.cs @@ -0,0 +1,9 @@ +// The following example only requires the user to either have the +// Administrator permission in this guild or own the bot application. +[RequireUserPermission(GuildPermission.Administrator, Group = "Permission")] +[RequireOwner(Group = "Permission")] +public class AdminModule : ModuleBase +{ + [Command("ban")] + public Task BanAsync(IUser user) => Context.Guild.AddBanAsync(user); +} \ No newline at end of file diff --git a/docs/guides/commands/samples/preconditions/precondition_usage.cs b/docs/guides/commands/samples/preconditions/precondition_usage.cs new file mode 100644 index 000000000..eacee932a --- /dev/null +++ b/docs/guides/commands/samples/preconditions/precondition_usage.cs @@ -0,0 +1,3 @@ +[RequireOwner] +[Command("echo")] +public Task EchoAsync(string input) => ReplyAsync(input); \ No newline at end of file diff --git a/docs/guides/commands/samples/require_owner.cs b/docs/guides/commands/samples/preconditions/require_owner.cs similarity index 66% rename from docs/guides/commands/samples/require_owner.cs rename to docs/guides/commands/samples/preconditions/require_owner.cs index 3611afab8..f17b7d9a7 100644 --- a/docs/guides/commands/samples/require_owner.cs +++ b/docs/guides/commands/samples/preconditions/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,10 +11,13 @@ 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 client via Depedency Injection + var client = services.GetRequiredService(); // Get the ID of the bot's owner - var ownerId = (await services.GetService().GetApplicationInfoAsync()).Owner.Id; + var appInfo = await client.GetApplicationInfoAsync().ConfigureAwait(false); + var ownerId = appInfo.Owner.Id; // If this command was executed by that user, return a success if (context.User.Id == ownerId) return PreconditionResult.FromSuccess(); diff --git a/docs/guides/commands/samples/typereaders/typereader-register.cs b/docs/guides/commands/samples/typereaders/typereader-register.cs new file mode 100644 index 000000000..292caea6f --- /dev/null +++ b/docs/guides/commands/samples/typereaders/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/typereaders/typereader.cs similarity index 74% rename from docs/guides/commands/samples/typereader.cs rename to docs/guides/commands/samples/typereaders/typereader.cs index d2864a4c7..a2a4627d2 100644 --- a/docs/guides/commands/samples/typereader.cs +++ b/docs/guides/commands/samples/typereaders/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..f942c9341 --- /dev/null +++ b/docs/guides/commands/typereaders.md @@ -0,0 +1,70 @@ +--- +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` +* `enum` +* `DateTime`/`DateTimeOffset`/`TimeSpan` +* Any nullable value-type (e.g. `int?`, `bool?`) +* 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/typereaders/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/typereaders/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..3d7b77cb8 100644 --- a/docs/guides/concepts/connections.md +++ b/docs/guides/concepts/connections.md @@ -1,51 +1,50 @@ --- +uid: Guides.Concepts.ManageConnections title: Managing Connections --- +# Managing Connections with Discord.Net + 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 +## 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 +52,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/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/deployment/deployment.md b/docs/guides/deployment/deployment.md new file mode 100644 index 000000000..b75628dc0 --- /dev/null +++ b/docs/guides/deployment/deployment.md @@ -0,0 +1,86 @@ +--- +uid: Guides.Deployment +title: Deploying the Bot +--- + +# Deploying a Discord.Net 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/emoji/emoji.md b/docs/guides/emoji/emoji.md new file mode 100644 index 000000000..60a84409c --- /dev/null +++ b/docs/guides/emoji/emoji.md @@ -0,0 +1,100 @@ +--- +uid: Guides.Emoji +title: Emoji +--- + +# Emoji in Discord.Net + +Before we delve into the difference between an @Discord.Emoji and an +@Discord.Emote in Discord.Net, it is **crucial** to understand what +they both look like behind the scene. When the end-users are sending +or receiving an emoji or emote, they are typically in the form of +`:ok_hand:` or `:reeee:`; however, what goes under the hood is that, +depending on the type of emoji, they are sent in an entirely +different format. + +What does this all mean? It means that you should know that by +reacting with a string like `“:ok_hand:”` will **NOT** automatically +translate to `👌`; rather, it will be treated as-is, +like `:ok_hand:`, thus the server will return a `400 Bad Request`. + +## Emoji + +An emoji is a standard emoji that can be found anywhere else outside +of Discord, which means strings like `👌`, `♥`, `👀` are all +considered an emoji in Discord. However, from the +introduction paragraph we have learned that we cannot +simply send `:ok_hand:` and have Discord take +care of it, but what do we need to send exactly? + +To send an emoji correctly, one must send the emoji in its Unicode +form; this can be obtained in several different ways. + +1. (Easiest) Escape the emoji by using the escape character, `\`, in + your Discord chat client; this will reveal the emoji’s pure Unicode + form, which will allow you to copy-paste into your code. +2. Look it up on Emojipedia, from which you can copy the emoji + easily into your code. + ![Emojipedia](images/emojipedia.png) +3. (Recommended) Look it up in the Emoji list from [FileFormat.Info]; + this will give you the .NET-compatible code that + represents the emoji. + * This is the most recommended method because some systems or + IDE sometimes do not render the Unicode emoji correctly. + ![Fileformat Emoji Source Code](images/fileformat-emoji-src.png) + +### Emoji Declaration + +After obtaining the Unicode representation of the emoji, you may +create the @Discord.Emoji object by passing the string into its +constructor (e.g. `new Emoji("👌");` or `new Emoji("\uD83D\uDC4C");`). + +Your method of declaring an @Discord.Emoji should look similar to +this: + +[!code-csharp[Emoji Sample](samples/emoji-sample.cs)] + +[FileFormat.Info]: https://www.fileformat.info/info/emoji/list.htm + +## Emote + +The meat of the debate is here; what is an emote and how does it +differ from an emoji? An emote refers to a **custom emoji** +created on Discord. + +The underlying structure of an emote also differs drastically; an +emote looks sort-of like a mention on Discord. It consists of two +main elements as illustrated below: + +![Emote illustration](images/emote-format.png) + +As you can see, emote uses a completely different format. To obtain +the raw string as shown above for your emote, you would need to +escape the emote using the escape character `\` in chat somewhere. + +### Emote Declaration + +After obtaining the raw emote string, you would need to use +@Discord.Emote.Parse* or @Discord.Emote.TryParse* to create a valid +emote object. + +Your method of declaring an @Discord.Emote should look similar to +this: + +[!code[Emote Sample](samples/emote-sample.cs)] + +> [!TIP] +> For WebSocket users, you may also consider fetching the Emote +> via the @Discord.WebSocket.SocketGuild.Emotes collection. +> [!code-csharp[Socket emote sample](samples/socket-emote-sample.cs)] + +> [!TIP] +> On Discord, any user with Discord Nitro subscription may use +> custom emotes from any guilds they are currently in. This is also +> true for _any_ standard bot accounts; this does not require +> the bot owner to have a Nitro subscription. + +## Additional Information + +To learn more about emote and emojis and how they could be used, +see the documentation of @Discord.IEmote. \ No newline at end of file diff --git a/docs/guides/emoji/images/emojipedia.png b/docs/guides/emoji/images/emojipedia.png new file mode 100644 index 000000000..acad16f28 Binary files /dev/null and b/docs/guides/emoji/images/emojipedia.png differ diff --git a/docs/guides/emoji/images/emote-format.png b/docs/guides/emoji/images/emote-format.png new file mode 100644 index 000000000..981e18227 Binary files /dev/null and b/docs/guides/emoji/images/emote-format.png differ diff --git a/docs/guides/emoji/images/fileformat-emoji-src.png b/docs/guides/emoji/images/fileformat-emoji-src.png new file mode 100644 index 000000000..a43eebb62 Binary files /dev/null and b/docs/guides/emoji/images/fileformat-emoji-src.png differ diff --git a/docs/guides/emoji/samples/emoji-sample.cs b/docs/guides/emoji/samples/emoji-sample.cs new file mode 100644 index 000000000..a36e6f70a --- /dev/null +++ b/docs/guides/emoji/samples/emoji-sample.cs @@ -0,0 +1,6 @@ +public async Task ReactAsync(SocketUserMessage userMsg) +{ + // equivalent to "👌" + var emoji = new Emoji("\uD83D\uDC4C"); + await userMsg.AddReactionAsync(emoji); +} \ No newline at end of file diff --git a/docs/guides/emoji/samples/emote-sample.cs b/docs/guides/emoji/samples/emote-sample.cs new file mode 100644 index 000000000..b05ecc269 --- /dev/null +++ b/docs/guides/emoji/samples/emote-sample.cs @@ -0,0 +1,7 @@ +public async Task ReactWithEmoteAsync(SocketUserMessage userMsg, string escapedEmote) +{ + if (Emote.TryParse(escapedEmote, out var emote)) + { + await userMsg.AddReactionAsync(emote); + } +} \ No newline at end of file diff --git a/docs/guides/emoji/samples/socket-emote-sample.cs b/docs/guides/emoji/samples/socket-emote-sample.cs new file mode 100644 index 000000000..397111512 --- /dev/null +++ b/docs/guides/emoji/samples/socket-emote-sample.cs @@ -0,0 +1,11 @@ +private readonly DiscordSocketClient _client; + +public async Task ReactAsync(SocketUserMessage userMsg, string emoteName) +{ + var emote = _client.Guilds + .SelectMany(x => x.Emotes) + .FirstOrDefault(x => x.Name.IndexOf( + emoteName, StringComparison.OrdinalIgnoreCase) != -1); + if (emote == null) return; + await userMsg.AddReactionAsync(emote); +} \ No newline at end of file diff --git a/docs/guides/getting_started/first-bot.md b/docs/guides/getting_started/first-bot.md new file mode 100644 index 000000000..a33e72aff --- /dev/null +++ b/docs/guides/getting_started/first-bot.md @@ -0,0 +1,263 @@ +--- +uid: Guides.GettingStarted.FirstBot +title: Start making a bot +--- + +# Making Your First Bot with Discord.Net + +One of the 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. On the left-hand side, under `Settings`, click `Bot`. + + ![Step 4](images/intro-bot-settings.png) + +5. Click on `Add Bot`. + + ![Step 5](images/intro-add-bot.png) + +6. Confirm the popup. +7. (Optional) If this bot will be public, tick `Public Bot`. + + ![Step 7](images/intro-public-bot.png) + +[Discord Applications Portal]: https://discordapp.com/developers/applications/ + +## 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. On the left-hand side, under `Settings`, click `OAuth2`. + + ![Step 2](images/intro-oauth-settings.png) + +3. Scroll down to `OAuth2 URL Generator` and under `Scopes` tick `bot`. + + ![Step 3](images/intro-scopes-bot.png) + +4. Scroll down further to `Bot Permissions` and 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! + +5. Open the generated authorization URL in your browser. +6. Select a server. +7. Click on Authorize. + + > [!NOTE] + > Only servers where you have the `MANAGE_SERVER` permission will be + > present in this list. + + ![Step 6](images/intro-authorize.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 for these operations to be 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 allows 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 "log in 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] +> Getting a warning about `A supplied token was invalid.` and/or +> having trouble logging in? Double-check whether you have put in +> the correct credentials and make sure that it is _not_ a client +> secret, which is different from a token. + +> [!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 + +### 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/appveyor-artifacts.png b/docs/guides/getting_started/images/appveyor-artifacts.png new file mode 100644 index 000000000..2f31b77a9 Binary files /dev/null and b/docs/guides/getting_started/images/appveyor-artifacts.png differ diff --git a/docs/guides/getting_started/images/appveyor-nupkg.png b/docs/guides/getting_started/images/appveyor-nupkg.png new file mode 100644 index 000000000..0cf3cf6c4 Binary files /dev/null and b/docs/guides/getting_started/images/appveyor-nupkg.png differ 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-add-bot.png b/docs/guides/getting_started/images/intro-add-bot.png index e40997ed3..3b5343ac6 100644 Binary files a/docs/guides/getting_started/images/intro-add-bot.png and b/docs/guides/getting_started/images/intro-add-bot.png differ diff --git a/docs/guides/getting_started/images/intro-authorize.png b/docs/guides/getting_started/images/intro-authorize.png new file mode 100644 index 000000000..66ca4cb04 Binary files /dev/null and b/docs/guides/getting_started/images/intro-authorize.png differ diff --git a/docs/guides/getting_started/images/intro-bot-settings.png b/docs/guides/getting_started/images/intro-bot-settings.png new file mode 100644 index 000000000..6ac40bfe6 Binary files /dev/null and b/docs/guides/getting_started/images/intro-bot-settings.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-create-app.png b/docs/guides/getting_started/images/intro-create-app.png deleted file mode 100644 index 7aceb84b4..000000000 Binary files a/docs/guides/getting_started/images/intro-create-app.png and /dev/null differ diff --git a/docs/guides/getting_started/images/intro-create-bot.png b/docs/guides/getting_started/images/intro-create-bot.png deleted file mode 100644 index 0522358cf..000000000 Binary files a/docs/guides/getting_started/images/intro-create-bot.png and /dev/null differ diff --git a/docs/guides/getting_started/images/intro-oauth-settings.png b/docs/guides/getting_started/images/intro-oauth-settings.png new file mode 100644 index 000000000..7d8c2a64a Binary files /dev/null and b/docs/guides/getting_started/images/intro-oauth-settings.png differ diff --git a/docs/guides/getting_started/images/intro-public-bot.png b/docs/guides/getting_started/images/intro-public-bot.png new file mode 100644 index 000000000..da91366a6 Binary files /dev/null and b/docs/guides/getting_started/images/intro-public-bot.png differ diff --git a/docs/guides/getting_started/images/intro-scopes-bot.png b/docs/guides/getting_started/images/intro-scopes-bot.png new file mode 100644 index 000000000..fa17deb16 Binary files /dev/null and b/docs/guides/getting_started/images/intro-scopes-bot.png differ diff --git a/docs/guides/getting_started/images/intro-token.png b/docs/guides/getting_started/images/intro-token.png index 8617cb76f..0fcdac077 100644 Binary files a/docs/guides/getting_started/images/intro-token.png and b/docs/guides/getting_started/images/intro-token.png differ diff --git a/docs/guides/getting_started/images/nightlies-vs-note.png b/docs/guides/getting_started/images/nightlies-vs-note.png new file mode 100644 index 000000000..0dcf2dea3 Binary files /dev/null and b/docs/guides/getting_started/images/nightlies-vs-note.png differ diff --git a/docs/guides/getting_started/images/nightlies-vs-step1.png b/docs/guides/getting_started/images/nightlies-vs-step1.png new file mode 100644 index 000000000..a399ca66c Binary files /dev/null and b/docs/guides/getting_started/images/nightlies-vs-step1.png differ diff --git a/docs/guides/getting_started/images/nightlies-vs-step2.png b/docs/guides/getting_started/images/nightlies-vs-step2.png new file mode 100644 index 000000000..75cecbb8d Binary files /dev/null and b/docs/guides/getting_started/images/nightlies-vs-step2.png differ diff --git a/docs/guides/getting_started/images/nightlies-vs-step4.png b/docs/guides/getting_started/images/nightlies-vs-step4.png new file mode 100644 index 000000000..6462ab994 Binary files /dev/null and b/docs/guides/getting_started/images/nightlies-vs-step4.png differ diff --git a/docs/guides/getting_started/installing.md b/docs/guides/getting_started/installing.md index 5d4c85d81..bb2592c02 100644 --- a/docs/guides/getting_started/installing.md +++ b/docs/guides/getting_started/installing.md @@ -1,146 +1,136 @@ --- +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; the most +recommended way for you to install this library. Alternatively, you +may also compile this library yourself should you so desire. -# Supported Platforms +## 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]. +Discord.Net targets [.NET Standard] both 1.3 and 2.0; this also means +that creating applications using the latest version of [.NET Core] is +the most recommended. If you are bound by Windows-specific APIs or +other limitations, you may also consider targeting [.NET Framework] +4.6.1 or higher. -Since Discord.Net is built on the .NET Standard, it is also -recommended to create applications using [.NET Core], though 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 supported 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/ +[.NET Framework]: https://docs.microsoft.com/en-us/dotnet/framework/get-started/ [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]. - -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 -[without Visual Studio]. +Development builds of Discord.Net, as well as add-ons, will be +published to our [MyGet feed]. See +@Guides.GettingStarted.Installation.Nightlies to learn more. [official NuGet feed]: https://nuget.org [MyGet feed]: https://www.myget.org/feed/Packages/discord-net -[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 -> [!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! +### [Using Visual Studio](#tab/vs-install) -1. Create a solution for your bot. -2. In Solution Explorer, find the "Dependencies" element under your -bot's project. +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) - -## Using JetBrains Rider + ![Step 5](images/install-vs-nuget.png) -> [!TIP] -Make sure to check the "Prerelease" box if installing a dev build! +### [Using JetBrains Rider](#tab/rider-install) 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 - -> [!TIP] -Don't forget to add the package source to a [NuGet.Config file] if -you're installing from the developer feed. +### [Using Visual Studio Code](#tab/vs-code) 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) -# Compiling from Source +1. Open command-line and navigate to where your .csproj is located. +2. Enter `dotnet add package Discord.Net`. -In order to compile Discord.Net, you require the following: +*** + +## Compiling from Source + +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) +* [Visual Studio 2017](https://www.visualstudio.com/) +* [.NET Core SDK] -The .NET Core and Docker (Preview) workload is required during Visual -Studio installation. +The .NET Core and Docker 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 +## Additional Information -## Installing on .NET Standard 1.1 +### Installing on Unsupported WebSocket Platform -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 -installed and configured. +When running any Discord.Net-powered bot on an older operating system +(e.g. Windows 7) that does not natively support WebSocket, +you may encounter a @System.PlatformNotSupportedException upon +connecting. -First, install the following packages through NuGet, or compile -yourself, if you prefer: +You may resolve this by either targeting .NET Core 2.1 or later, or +by installing one or more custom packages as listed below. -- Discord.Net.Providers.WS4Net -- Discord.Net.Providers.UDPClient +#### [Targeting .NET Core 2.1](#tab/core2-1) -Note that `Discord.Net.Providers.UDPClient` is _only_ required if your -bot will be utilizing voice chat. +1. Download the latest [.NET Core SDK]. +2. Create or move your existing project to use .NET Core. +3. Modify your `` tag to at least `netcoreapp2.1`, or + by adding the `--framework netcoreapp2.1` switch when building. -Next, you will need to configure your [DiscordSocketClient] to use -these custom providers over the default ones. +#### [Custom Packages](#tab/custom-pkg) -To do this, set the `WebSocketProvider` and the optional -`UdpSocketProvider` properties on the [DiscordSocketConfig] that you -are passing into your client. +1. Install or compile the following packages: -[!code-csharp[NET Standard 1.1 Example](samples/netstd11.cs)] + * `Discord.Net.Providers.WS4Net` + * `Discord.Net.Providers.UDPClient` (Optional) + * This is _only_ required if your bot will be utilizing voice chat. -[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient -[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig +2. Configure your [DiscordSocketClient] to use these custom providers +over the default ones. -## Configuring NuGet without Visual Studio + * To do this, set the `WebSocketProvider` and the optional + `UdpSocketProvider` properties on the [DiscordSocketConfig] that you + are passing into your client. -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. +[!code-csharp[Example](samples/netstd11.cs)] -To do this, create a file named `nuget.config` alongside the root of -your application, where the project solution is located. +[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient +[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig -Paste the following snippets into this configuration file, adding any -additional feeds as necessary. +*** -[!code-xml[NuGet Configuration](samples/nuget.config)] +[.NET Core SDK]: https://www.microsoft.com/net/download/ \ No newline at end of file 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/nightlies.md b/docs/guides/getting_started/nightlies.md new file mode 100644 index 000000000..39bcc1c9f --- /dev/null +++ b/docs/guides/getting_started/nightlies.md @@ -0,0 +1,86 @@ +--- +uid: Guides.GettingStarted.Installation.Nightlies +title: Installing Nightly Build +--- + +# Installing Discord.Net Nightly Build + +Before Discord.Net pushes a new set of features into the stable +version, we use nightly builds to test the features with the +community for an extensive period of time. Each nightly build is +compiled by AppVeyor whenever a new commit is made and will be pushed +to our MyGet feed. + +> [!IMPORTANT] +> Although nightlies are generally stable and have more features +> and bug fixes than the current stable build on NuGet, there +> will be breaking changes during the development or +> breaking bugs; these bugs are usually fixed as soon as they +> are discovered, but you should still be aware of that. + +## Installing with MyGet (Recommended) + +MyGet is typically used by many development teams to publish their +latest pre-release packages before the features are finalized and +pushed to NuGet. + +The following is the feed link of Discord.Net, + +* `https://www.myget.org/F/discord-net/api/v3/index.json` + +Depending on which IDE you use, there are many different ways of +adding the feed to your package source. + +### [Visual Studio](#tab/vs) + +1. Go to `Tools` > `NuGet Package Manager` > `Package Manager Settings` + ![VS](images/nightlies-vs-step1.png) +2. Go to `Package Sources` + ![Package Sources](images/nightlies-vs-step2.png) +3. Click on the add icon +4. Fill in the desired name and source as shown below and hit `Update` + ![Add Source](images/nightlies-vs-step4.png) + +> [!NOTE] +> Remember to tick the `Include prerelease` checkbox to see the +> nightly builds! +> ![Checkbox](images/nightlies-vs-note.png) + +### [Local NuGet.Config](#tab/local-nuget-config) + +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 is located. + +Paste the following snippets into this configuration file, adding any +additional feeds if necessary. + +[!code[NuGet Configuration](samples/nuget.config)] + +After which, you may install the packages by directly modifying the +project file and specifying a version, or by using +the [Package Manager Console](https://docs.microsoft.com/en-us/nuget/tools/powershell-reference) +(`Install-Package Discord.Net -IncludePrerelease`). + +*** + +## Installing from AppVeyor Artifacts + +As mentioned in the first paragraph, we utilize AppVeyor to perform +automated tests and publish the new build. During the publishing +process, we also upload the NuGet packages onto +AppVeyor's Artifact collection. + +The latest build status can be found within our [AppVeyor project]. + +[AppVeyor project]: https://ci.appveyor.com/project/rogueexception/discord-net + +1. In the project, you may find our latest build including the + aforementioned artifacts. + ![Artifacts](images/appveyor-artifacts.png) +2. In the artifacts collection, you should see the latest packages + packed in `*.nupkg` form which you could download from and use. + ![NuPkgs](images/appveyor-nupkg.png) 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/first-bot/client.cs b/docs/guides/getting_started/samples/first-bot/client.cs new file mode 100644 index 000000000..bced2224d --- /dev/null +++ b/docs/guides/getting_started/samples/first-bot/client.cs @@ -0,0 +1,21 @@ +private DiscordSocketClient _client; + +public async Task MainAsync() +{ + _client = new DiscordSocketClient(); + + _client.Log += Log; + + // Remember to keep token private or to read it from an + // external source! In this case, we are reading the token + // from an environment variable. If you do not know how to set-up + // environment variables, you may find more information on the + // Internet or by using other methods such as reading from + // a configuration. + 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..3a7cb061d --- /dev/null +++ b/docs/guides/getting_started/samples/first-bot/complete.cs @@ -0,0 +1,34 @@ +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; + 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 100% rename from docs/guides/getting_started/samples/intro/structure.cs rename to docs/guides/getting_started/samples/first-bot/structure.cs 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/client.cs b/docs/guides/getting_started/samples/intro/client.cs deleted file mode 100644 index a73082052..000000000 --- a/docs/guides/getting_started/samples/intro/client.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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); - await _client.StartAsync(); - - // Block this task until the program is closed. - await Task.Delay(-1); -} 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..179d3f97b --- /dev/null +++ b/docs/guides/getting_started/samples/project.xml @@ -0,0 +1,17 @@ + + + + + + Exe + netcoreapp2.1 + + + + + + + diff --git a/docs/guides/getting_started/terminology.md b/docs/guides/getting_started/terminology.md index 74f7a6259..0f24edd6f 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,30 @@ 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.Rpc`, and `Discord.Net.WebSockets`. +Discord.Net is split into a core library and two different +implementations - `Discord.Net.Core`, `Discord.Net.Rest`, 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.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..14d4aa49e --- /dev/null +++ b/docs/guides/introduction/intro.md @@ -0,0 +1,54 @@ +--- +uid: Guides.Introduction +title: Introduction to Discord.Net +--- + +# Introduction + +## Looking to get started? + +Welcome! Before you dive into this 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. With all +that being said, feel free to visit us on Discord at the link below +if you have any questions! + +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. + +[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#? + +All examples or snippets featured in this guide and all API +documentation will be written in C#. + +If you are new to the language, using this wrapper 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..01c245301 100644 --- a/docs/guides/toc.yml +++ b/docs/guides/toc.yml @@ -1,27 +1,41 @@ +- name: Introduction + topicUid: Guides.Introduction - name: Getting Started items: - name: Installation - href: getting_started/installing.md + topicUid: Guides.GettingStarted.Installation + items: + - name: Nightly Builds + topicUid: Guides.GettingStarted.Installation.Nightlies - 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 -- name: The Command Service + topicUid: Guides.Concepts.Entities +- name: Working with Commands 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: Emoji + topicUid: Guides.Emoji - 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 +- name: Deployment + topicUid: Guides.Deployment \ No newline at end of file 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/langwordMapping.yml b/docs/langwordMapping.yml new file mode 100644 index 000000000..9ddd00cab --- /dev/null +++ b/docs/langwordMapping.yml @@ -0,0 +1,61 @@ +references: +- uid: langword_csharp_null + name.csharp: "null" + name.vb: "Nothing" +- uid: langword_vb_Nothing + name.csharp: "null" + name.vb: "Nothing" +- uid: langword_csharp_static + name.csharp: static + name.vb: Shared +- uid: langword_vb_Shared + name.csharp: static + name.vb: Shared +- uid: langword_csharp_virtual + name.csharp: virtual + name.vb: Overridable +- uid: langword_vb_Overridable + name.csharp: virtual + name.vb: Overridable +- uid: langword_csharp_true + name.csharp: "true" + name.vb: "True" +- uid: langword_vb_True + name.csharp: "true" + name.vb: "True" +- uid: langword_csharp_false + name.csharp: "false" + name.vb: "False" +- uid: langword_vb_False + name.csharp: "false" + name.vb: "False" +- uid: langword_csharp_abstract + name.csharp: abstract + name.vb: MustInherit +- uid: langword_vb_MustInherit + name.csharp: abstract + name.vb: MustInherit +- uid: langword_csharp_sealed + name.csharp: sealed + name.vb: NotInheritable +- uid: langword_vb_NotInheritable + name.csharp: sealed + name.vb: NotInheritable +- uid: langword_csharp_async + name.csharp: async + name.vb: Async +- uid: langword_vb_Async + name.csharp: async + name.vb: Async +- uid: langword_csharp_await + name.csharp: await + name.vb: Await +- uid: langword_vb_Await + name.csharp: await + name.vb: Await +- uid: langword_csharp_async/await + name.csharp: async/await + name.vb: Async/Await +- uid: langword_vb_Async/Await + name.csharp: async/await + name.vb: Async/Await \ 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/samples/01_basic_ping_bot/Program.cs b/samples/01_basic_ping_bot/Program.cs index 0fcc52b85..973a6ce95 100644 --- a/samples/01_basic_ping_bot/Program.cs +++ b/samples/01_basic_ping_bot/Program.cs @@ -5,14 +5,14 @@ using Discord.WebSocket; namespace _01_basic_ping_bot { - // This is a minimal, barebones example of using Discord.Net + // This is a minimal, bare-bones example of using Discord.Net // // If writing a bot with commands, we recommend using the Discord.Net.Commands // framework, rather than handling commands yourself, like we do in this sample. // // You can find samples of using the command framework: // - Here, under the 02_commands_framework sample - // - https://github.com/foxbot/DiscordBotBase - a barebones bot template + // - https://github.com/foxbot/DiscordBotBase - a bare-bones bot template // - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library class Program { @@ -54,7 +54,7 @@ namespace _01_basic_ping_bot return Task.CompletedTask; } - // This is not the recommmended way to write a bot - consider + // This is not the recommended way to write a bot - consider // reading over the Commands Framework sample. private async Task MessageReceivedAsync(SocketMessage message) { diff --git a/samples/02_commands_framework/Program.cs b/samples/02_commands_framework/Program.cs index 3fed652d3..136494bdf 100644 --- a/samples/02_commands_framework/Program.cs +++ b/samples/02_commands_framework/Program.cs @@ -15,7 +15,7 @@ namespace _02_commands_framework // // You can find samples of using the command framework: // - Here, under the 02_commands_framework sample - // - https://github.com/foxbot/DiscordBotBase - a barebones bot template + // - https://github.com/foxbot/DiscordBotBase - a bare-bones bot template // - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library class Program { diff --git a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs index 6cd0abbb7..16eb3ba73 100644 --- a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs @@ -2,14 +2,37 @@ using System; namespace Discord.Commands { - /// Provides aliases for a command. + /// + /// Marks the aliases for a command. + /// + /// + /// This attribute allows a command to have one or multiple aliases. In other words, the base command can have + /// multiple aliases when triggering the command itself, giving the end-user more freedom of choices when giving + /// hot-words to trigger the desired command. See the example for a better illustration. + /// + /// + /// In the following example, the command can be triggered with the base name, "stats", or either "stat" or + /// "info". + /// + /// [Command("stats")] + /// [Alias("stat", "info")] + /// public async Task GetStatsAsync(IUser user) + /// { + /// // ...pull stats + /// } + /// + /// [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. + /// + /// Creates a new with the given aliases. + /// public AliasAttribute(params string[] aliases) { Aliases = aliases; diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index bfc04641a..d4d9ee3bb 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -2,17 +2,32 @@ 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; } + /// 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..7dbe1a495 100644 --- a/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs @@ -2,6 +2,14 @@ using System; namespace Discord.Commands { + /// + /// Prevents the marked module from being loaded automatically. + /// + /// + /// This attribute tells to ignore the marked module from being loaded + /// automatically (e.g. the method). If a non-public module marked + /// with this attribute is attempted to be loaded manually, the loading process will also fail. + /// [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..72ca92fdf 100644 --- a/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs @@ -1,9 +1,31 @@ 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. + /// + /// + /// This attribute prevents the marked member from being injected into its parent module. Useful when you have a + /// public property that you do not wish to invoke the library's dependency injection service. + /// + /// + /// In the following example, DatabaseService will not be automatically injected into the module and will + /// not throw an error message if the dependency fails to be resolved. + /// + /// public class MyModule : ModuleBase + /// { + /// [DontInject] + /// public DatabaseService DatabaseService; + /// public MyModule() + /// { + /// DatabaseService = DatabaseFactory.Generate(); + /// } + /// } + /// + /// + [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 17e2310d4..85f5df10e 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -4,17 +4,46 @@ using System.Reflection; namespace Discord.Commands { + /// + /// Marks the to be read by the specified . + /// + /// + /// This attribute will override the to be used when parsing for the + /// desired type in the command. This is useful when one wishes to use a particular + /// without affecting other commands that are using the same target + /// type. + /// + /// If the given type reader does not inherit from , an + /// will be thrown. + /// + /// + /// + /// In this example, the will be read by a custom + /// , FriendlyTimeSpanTypeReader, instead of the + /// shipped by Discord.Net. + /// + /// [Command("time")] + /// public Task GetTimeAsync([OverrideTypeReader(typeof(FriendlyTimeSpanTypeReader))]TimeSpan time) + /// => ReplyAsync(time); + /// + /// [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class OverrideTypeReaderAttribute : Attribute { 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)}"); + 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..8ee46f9f9 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 parameter. + /// 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..76d18449e 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..810b62014 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -1,35 +1,48 @@ 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. /// - /// - /// [Command("private_only")] + /// + /// [Command("secret")] /// [RequireContext(ContextType.DM | ContextType.Group)] - /// public async Task PrivateOnly() + /// public Task PrivateOnlyAsync() /// { + /// return ReplyAsync("shh, this command is a secret"); /// } /// /// @@ -38,12 +51,13 @@ namespace Discord.Commands Contexts = contexts; } + /// public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { bool isValid = false; if ((Contexts & ContextType.Guild) != 0) - isValid = isValid || context.Channel is IGuildChannel; + isValid = context.Channel is IGuildChannel; if ((Contexts & ContextType.DM) != 0) isValid = isValid || context.Channel is IDMChannel; if ((Contexts & ContextType.Group) != 0) @@ -52,7 +66,7 @@ namespace Discord.Commands if (isValid) return Task.FromResult(PreconditionResult.FromSuccess()); else - return Task.FromResult(PreconditionResult.FromError($"Invalid context for command; accepted contexts: {Contexts}")); + return Task.FromResult(PreconditionResult.FromError($"Invalid context for command; accepted contexts: {Contexts}.")); } } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs index 273c764bd..930511da1 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs @@ -4,11 +4,33 @@ 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. /// + /// + /// The precondition will restrict the access of the command or module to be accessed within a guild channel + /// that has been marked as mature or NSFW. If the channel is not of type or the + /// channel is not marked as NSFW, the precondition will fail with an erroneous . + /// + /// + /// The following example restricts the command too-cool to an NSFW-enabled channel only. + /// + /// public class DankModule : ModuleBase + /// { + /// [Command("cool")] + /// public Task CoolAsync() + /// => ReplyAsync("I'm cool for everyone."); + /// + /// [RequireNsfw] + /// [Command("too-cool")] + /// public Task TooCoolAsync() + /// => ReplyAsync("You can only see this if you're cool enough."); + /// } + /// + /// [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..53021ad10 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -4,20 +4,45 @@ 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. + /// + /// This precondition will restrict the access of the command or module to the owner of the Discord application. + /// If the precondition fails to be met, an erroneous will be returned with the + /// message "Command can only be run by the owner of the bot." + /// + /// This precondition will only work if the account has a of + /// ;otherwise, this precondition will always fail. + /// + /// + /// + /// The following example restricts the command to a set of sensitive commands that only the owner of the bot + /// application should be able to access. + /// + /// [RequireOwner] + /// [Group("admin")] + /// public class AdminModule : ModuleBase + /// { + /// [Command("exit")] + /// public async Task ExitAsync() + /// { + /// Environment.Exit(0); + /// } + /// } + /// + /// [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..e9b1c0c5d 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/Attributes/RemarksAttribute.cs b/src/Discord.Net.Commands/Attributes/RemarksAttribute.cs index c11f790a7..2fbe2bf4a 100644 --- a/src/Discord.Net.Commands/Attributes/RemarksAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/RemarksAttribute.cs @@ -3,6 +3,9 @@ using System; namespace Discord.Commands { // Extension of the Cosmetic Summary, for Groups, Commands, and Parameters + /// + /// Attaches remarks to your commands. + /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class RemarksAttribute : Attribute { diff --git a/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs b/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs index 641163408..57e9b0277 100644 --- a/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs @@ -3,6 +3,9 @@ using System; namespace Discord.Commands { // Cosmetic Summary, for Groups and Commands + /// + /// Attaches a summary to your command. + /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class SummaryAttribute : 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 307874ca6..3b71c87b0 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..b487d8a1e 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 object 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..6c5ab0ae5 100644 --- a/src/Discord.Net.Commands/CommandException.cs +++ b/src/Discord.Net.Commands/CommandException.cs @@ -2,11 +2,24 @@ using System; namespace Discord.Commands { + /// + /// The exception that is thrown if another exception occurs 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 7b7cffda2..11f4ce276 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -11,11 +11,44 @@ using Discord.Logging; namespace Discord.Commands { + /// + /// Provides a framework for building Discord commands. + /// + /// + /// + /// The service provides a framework for building Discord commands both dynamically via runtime builders or + /// statically via compile-time modules. To create a command module at compile-time, see + /// (most common); otherwise, see . + /// + /// + /// This service also provides several events for monitoring command usages; such as + /// for any command-related log events, and + /// for information about commands that have + /// been successfully executed. + /// + /// 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 error. + /// + /// + /// + /// This event is fired when a command has been successfully executed without any of the following errors: + /// + /// * Parsing error + /// * Precondition error + /// * Runtime exception + /// + /// Should the command encounter any of the aforementioned error, this event will not be raised. + /// + /// public event Func CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } } internal readonly AsyncEvent> _commandExecutedEvent = new AsyncEvent>(); @@ -34,11 +67,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 +157,39 @@ 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 following example registers the module MyModule to commandService. + /// + /// await commandService.AddModuleAsync<MyModule>(serviceProvider); + /// + /// + /// The type of module. + /// The for your dependency injection solution if using one; otherwise, pass null. + /// This module has already been added. + /// + /// The fails to be built; an invalid type may have been provided. + /// + /// + /// A task that represents the asynchronous operation for adding the module. The task result contains the + /// built module. + /// 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 . + /// This module has already been added. + /// + /// The fails to be built; an invalid type may have been provided. + /// + /// + /// A task that represents the asynchronous operation for adding the module. The task result contains the + /// built module. + /// public async Task AddModuleAsync(Type type, IServiceProvider services) { services = services ?? EmptyServiceProvider.Instance; @@ -135,11 +217,14 @@ 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. + /// The for your dependency injection solution if using one; otherwise, pass null. + /// + /// A task that represents the asynchronous operation for adding the command modules. The task result + /// contains an enumerable collection of modules added. + /// public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services) { services = services ?? EmptyServiceProvider.Instance; @@ -175,7 +260,14 @@ namespace Discord.Commands return module; } - + /// + /// Removes the command module. + /// + /// The to be removed from the service. + /// + /// A task that represents the asynchronous removal operation. The task result contains a value that + /// indicates whether the is successfully removed. + /// public async Task RemoveModuleAsync(ModuleInfo module) { await _moduleLock.WaitAsync().ConfigureAwait(false); @@ -188,7 +280,23 @@ namespace Discord.Commands _moduleLock.Release(); } } + /// + /// Removes the command module. + /// + /// The of the module. + /// + /// A task that represents the asynchronous removal operation. The task result contains a value that + /// indicates whether the module is successfully removed. + /// public Task RemoveModuleAsync() => RemoveModuleAsync(typeof(T)); + /// + /// Removes the command module. + /// + /// The of the module. + /// + /// A task that represents the asynchronous removal operation. The task result contains a value that + /// indicates whether the module is successfully removed. + /// public async Task RemoveModuleAsync(Type type) { await _moduleLock.WaitAsync().ConfigureAwait(false); @@ -222,21 +330,27 @@ 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. + /// 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)) @@ -245,21 +359,31 @@ namespace Discord.Commands 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. + /// 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 && HasDefaultTypeReader(type)) @@ -331,8 +455,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.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) => Search(input); public SearchResult Search(string input) @@ -346,8 +482,30 @@ 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. + /// + /// A task that represents the asynchronous execution operation. The task result contains 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. + /// + /// A task that represents the asynchronous execution operation. The task result contains 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 00091634d..2dedceaa5 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -1,29 +1,62 @@ -using System; 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; + /// + /// 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; - /// Collection of aliases that can wrap strings for command parsing. - /// represents the opening quotation mark and the value is the corresponding closing mark. + /// + /// Collection of aliases for matching pairs of string delimiters. + /// The dictionary stores the opening delimiter as a key, and the matching closing delimiter as the value. + /// If no value is supplied will be used, which contains + /// many regional equivalents. + /// Only values that are specified in this map will be used as string delimiters, so if " is removed then + /// it won't be used. + /// If this map is set to null or empty, the default delimiter of " will be used. + /// + /// + /// + /// QuotationMarkAliasMap = new Dictionary<char, char%gt;() + /// { + /// {'\"', '\"' }, + /// {'“', '”' }, + /// {'「', '」' }, + /// } + /// + /// public Dictionary QuotationMarkAliasMap { get; set; } = QuotationAliasUtils.GetDefaultAliasMap; - /// Determines whether extra parameters should be ignored. + /// + /// Gets or sets a value that indicates whether extra parameters should be ignored. + /// public bool IgnoreExtraArgs { get; set; } = false; } } diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs index a27c5f322..f76df1f2a 100644 --- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs @@ -2,8 +2,20 @@ using System; namespace Discord.Commands { + /// + /// Provides extension methods for that relates to commands. + /// public static class MessageExtensions { + /// + /// Gets whether the message starts with the provided character. + /// + /// The message to check against. + /// The char prefix. + /// References where the command starts. + /// + /// true if the message begins with the char ; otherwise false. + /// public static bool HasCharPrefix(this IUserMessage msg, char c, ref int argPos) { var text = msg.Content; @@ -14,6 +26,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 +39,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; diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 604bfbc93..d27a1ed7b 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/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 7c144599b..7b9959efe 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -6,20 +6,59 @@ using Discord.Commands.Builders; namespace Discord.Commands { + /// + /// Provides the information of a module. + /// public class ModuleInfo { + /// + /// Gets the command service associated with this module. + /// public CommandService Service { get; } + /// + /// Gets the name of this module. + /// public string Name { get; } + /// + /// Gets the summary of this module. + /// public string Summary { get; } + /// + /// Gets the remarks of this module. + /// public string Remarks { get; } + /// + /// Gets the group name (main prefix) of this module. + /// public string Group { get; } + /// + /// Gets a read-only list of aliases associated with this module. + /// public IReadOnlyList Aliases { get; } + /// + /// Gets a read-only list of commands associated with this module. + /// public IReadOnlyList Commands { get; } + /// + /// Gets a read-only list of preconditions that apply to this module. + /// public IReadOnlyList Preconditions { get; } + /// + /// Gets a read-only list of attributes that apply to this module. + /// public IReadOnlyList Attributes { get; } + /// + /// Gets a read-only list of submodules associated with this module. + /// public IReadOnlyList Submodules { get; } + /// + /// Gets the parent module of this submodule if applicable. + /// public ModuleInfo Parent { get; } + /// + /// Gets a value that indicates whether this module is a submodule or not. + /// public bool IsSubmodule => Parent != null; internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, ModuleInfo parent = null) diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 4a56415e5..b435b301a 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -2,25 +2,56 @@ 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 { + /// + /// Provides the information of a parameter. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class ParameterInfo { private readonly TypeReader _reader; + /// + /// Gets the command that associates with this parameter. + /// public CommandInfo Command { get; } + /// + /// Gets the name of this parameter. + /// public string Name { get; } + /// + /// Gets the summary of this parameter. + /// public string Summary { get; } + /// + /// Gets a value that indicates whether this parameter is optional or not. + /// public bool IsOptional { get; } + /// + /// Gets a value that indicates whether this parameter is a remainder parameter or not. + /// public bool IsRemainder { get; } public bool IsMultiple { get; } + /// + /// Gets the type of the parameter. + /// public Type Type { get; } + /// + /// Gets the default value for this optional parameter if applicable. + /// public object DefaultValue { get; } + /// + /// Gets a read-only list of precondition that apply to this parameter. + /// public IReadOnlyList Preconditions { get; } + /// + /// Gets a read-only list of attributes that apply to this parameter. + /// public IReadOnlyList Attributes { get; } internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service) @@ -65,4 +96,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/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index bd3067718..16f469cde 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -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); diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index 3e6fbbd9b..9cd4ea15d 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -4,32 +4,57 @@ using Discord.Commands.Builders; namespace Discord.Commands { + /// + /// Provides a base class for a command module to inherit from. + /// public abstract class ModuleBase : ModuleBase { } + /// + /// Provides a base class for a command module to inherit from. + /// + /// A class that implements . 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 +63,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..cdbc59cef 100644 --- a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs @@ -6,19 +6,29 @@ using System.Threading.Tasks; namespace Discord.Commands { + /// + /// A for parsing objects implementing . + /// + /// + /// This is shipped with Discord.Net and is used by default to parse any + /// implemented object within a command. The TypeReader will attempt to first parse the + /// input by mention, then the snowflake identifier, then by name; the highest candidate will be chosen as the + /// final output; otherwise, an erroneous is returned. + /// + /// 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..356d704c9 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; @@ -44,6 +44,7 @@ namespace Discord.Commands _enumsByValue = byValueBuilder.ToImmutable(); } + /// public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { object enumValue; @@ -53,14 +54,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..f68bf6e2c 100644 --- a/src/Discord.Net.Commands/Readers/NullableTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/NullableTypeReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -24,11 +24,12 @@ namespace Discord.Commands _baseTypeReader = baseTypeReader; } + /// public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { 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/TimeSpanTypeReader.cs b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs index 314fbb322..b4a27cb5b 100644 --- a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs @@ -24,6 +24,7 @@ namespace Discord.Commands "%s's'", // 1s }; + /// public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { return (TimeSpan.TryParseExact(input.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan)) diff --git a/src/Discord.Net.Commands/Readers/TypeReader.cs b/src/Discord.Net.Commands/Readers/TypeReader.cs index af45a0aac..af780993d 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. + /// + /// A task that represents the asynchronous parsing operation. The task result contains the parsing result. + /// 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 498a214e4..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,7 +74,8 @@ 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.Nickname, StringComparison.OrdinalIgnoreCase))) AddResult(results, guildUser as T, guildUser.Nickname == input ? 0.60f : 0.50f); diff --git a/src/Discord.Net.Commands/Results/ExecuteResult.cs b/src/Discord.Net.Commands/Results/ExecuteResult.cs index bad39e230..055999596 100644 --- a/src/Discord.Net.Commands/Results/ExecuteResult.cs +++ b/src/Discord.Net.Commands/Results/ExecuteResult.cs @@ -1,16 +1,25 @@ -using System; +using System; using System.Diagnostics; namespace Discord.Commands { + /// + /// Contains information of the command's overall execution result. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct ExecuteResult : IResult { + /// + /// Gets the exception that may have occurred during the command execution. + /// public Exception Exception { get; } + /// public CommandError? Error { get; } + /// public string ErrorReason { get; } + /// public bool IsSuccess => !Error.HasValue; private ExecuteResult(Exception exception, CommandError? error, string errorReason) @@ -20,15 +29,56 @@ namespace Discord.Commands ErrorReason = errorReason; } + /// + /// Initializes a new with no error, indicating a successful execution. + /// + /// + /// A that does not contain any errors. + /// public static ExecuteResult FromSuccess() => new ExecuteResult(null, null, null); + /// + /// Initializes a new with a specified and its + /// reason, indicating an unsuccessful execution. + /// + /// The type of error. + /// The reason behind the error. + /// + /// A that contains a and reason. + /// public static ExecuteResult FromError(CommandError error, string reason) => new ExecuteResult(null, error, reason); + /// + /// Initializes a new with a specified exception, indicating an unsuccessful + /// execution. + /// + /// The exception that caused the command execution to fail. + /// + /// A that contains the exception that caused the unsuccessful execution, along + /// with a of type Exception as well as the exception message as the + /// reason. + /// public static ExecuteResult FromError(Exception ex) => new ExecuteResult(ex, CommandError.Exception, ex.Message); + /// + /// Initializes a new with a specified result; this may or may not be an + /// successful execution depending on the and + /// specified. + /// + /// The result to inherit from. + /// + /// A that inherits the error type and reason. + /// public static ExecuteResult FromError(IResult result) => new ExecuteResult(null, result.Error, result.ErrorReason); - + + /// + /// Gets a string that indicates the execution result. + /// + /// + /// Success if is true; otherwise ": + /// ". + /// public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; } diff --git a/src/Discord.Net.Commands/Results/IResult.cs b/src/Discord.Net.Commands/Results/IResult.cs index 928d1139e..c11b58001 100644 --- a/src/Discord.Net.Commands/Results/IResult.cs +++ b/src/Discord.Net.Commands/Results/IResult.cs @@ -1,9 +1,31 @@ -namespace Discord.Commands +namespace Discord.Commands { + /// + /// Contains information of the result related to a command. + /// public interface IResult { + /// + /// Describes the error type that may have occurred during the operation. + /// + /// + /// A indicating the type of error that may have occurred during the operation; + /// null if the operation was successful. + /// CommandError? Error { get; } + /// + /// Describes the reason for the error. + /// + /// + /// A string containing the error reason. + /// string ErrorReason { get; } + /// + /// Indicates whether the operation was successful or not. + /// + /// + /// true if the result is positive; otherwise false. + /// bool IsSuccess { get; } } } diff --git a/src/Discord.Net.Commands/Results/ParseResult.cs b/src/Discord.Net.Commands/Results/ParseResult.cs index 3a0692b2d..8e10dc66c 100644 --- a/src/Discord.Net.Commands/Results/ParseResult.cs +++ b/src/Discord.Net.Commands/Results/ParseResult.cs @@ -4,15 +4,21 @@ using System.Diagnostics; namespace Discord.Commands { + /// + /// Contains information for the parsing result from the command service's parser. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct ParseResult : IResult { 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) @@ -22,7 +28,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 ee650600a..7ecade4f8 100644 --- a/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs +++ b/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs @@ -15,7 +15,7 @@ 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); diff --git a/src/Discord.Net.Commands/Results/PreconditionResult.cs b/src/Discord.Net.Commands/Results/PreconditionResult.cs index 01fc1a3fd..d8e399a01 100644 --- a/src/Discord.Net.Commands/Results/PreconditionResult.cs +++ b/src/Discord.Net.Commands/Results/PreconditionResult.cs @@ -3,29 +3,56 @@ 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); public static PreconditionResult FromError(Exception ex) => new PreconditionResult(CommandError.Exception, ex.Message); + /// + /// 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 6a5878ea2..d1f1ea0d7 100644 --- a/src/Discord.Net.Commands/Results/SearchResult.cs +++ b/src/Discord.Net.Commands/Results/SearchResult.cs @@ -10,9 +10,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 e696dbc17..2fbdb45d6 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/QuotationAliasUtils.cs b/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs index 15a08b9b3..2612e99e5 100644 --- a/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs +++ b/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs @@ -1,19 +1,18 @@ -using System; using System.Collections.Generic; -using System.Text; -using System.Globalization; namespace Discord.Commands { /// - /// Utility methods for generating matching pairs of unicode quotation marks for CommandServiceConfig + /// Utility class which contains the default matching pairs of quotation marks for CommandServiceConfig /// internal static class QuotationAliasUtils { /// - /// Generates an IEnumerable of characters representing open-close pairs of - /// quotation punctuation. + /// A default map of open-close pairs of quotation marks. + /// Contains many regional and Unicode equivalents. + /// Used in the . /// + /// internal static Dictionary GetDefaultAliasMap { get diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index ec981cf52..062af0481 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,12 +45,12 @@ 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(); while (ownerType != ObjectTypeInfo) @@ -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..2287d47fa 100644 --- a/src/Discord.Net.Core/Audio/AudioStream.cs +++ b/src/Discord.Net.Core/Audio/AudioStream.cs @@ -11,10 +11,9 @@ 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"); - } + /// 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 +29,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 8c6cfc28c..d0c1c3a84 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -2,35 +2,143 @@ using System.Reflection; namespace Discord { + /// + /// Defines various behaviors of Discord.Net. + /// public class DiscordConfig { + /// + /// Returns the API version Discord.Net uses. + /// + /// + /// An 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; + /// + /// Returns the Voice API version Discord.Net uses. + /// + /// + /// An representing the API version that Discord.Net uses to communicate with Discord's + /// voice server. + /// public const int VoiceAPIVersion = 3; + /// + /// 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..5be99cacb 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..7217bded3 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameAsset.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameAsset.cs @@ -1,14 +1,37 @@ 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. + /// + /// + /// A string containing the description of the asset. + /// public string Text { get; internal set; } + /// + /// Gets the image ID of the asset. + /// + /// + /// A string containing the unique image identifier of the asset. + /// public string ImageId { get; internal set; } - + + /// + /// Returns the image URL of the asset. + /// + /// The size of the image to return in. This can be any power of two between 16 and 2048. + /// The format to return. + /// + /// A string pointing to the image URL of the asset; 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..0cfa9980d 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameParty.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameParty.cs @@ -1,11 +1,26 @@ namespace Discord { + /// + /// Party information for a object. + /// public class GameParty { internal GameParty() { } + /// + /// Gets the ID of the party. + /// + /// + /// A string containing the unique identifier of the party. + /// public string Id { get; internal set; } public long Members { get; internal set; } + /// + /// Gets the party's current and maximum size. + /// + /// + /// A representing the capacity of the party. + /// 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..ac0c1b5d7 100644 --- a/src/Discord.Net.Core/Entities/Activities/IActivity.cs +++ b/src/Discord.Net.Core/Entities/Activities/IActivity.cs @@ -1,8 +1,23 @@ -namespace Discord +namespace Discord { + /// + /// A user's activity status, typically a . + /// public interface IActivity { + /// + /// Gets the name of the activity. + /// + /// + /// A string containing the name of the activity that the user is doing. + /// string Name { get; } + /// + /// Gets the type of the activity. + /// + /// + /// The type of 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/ActionType.cs b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs index e5a4ff30a..2561a0970 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs @@ -1,50 +1,122 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// - /// The action type within a + /// Representing a type of action within an . /// public enum ActionType { + /// + /// this guild was updated. + /// GuildUpdated = 1, + /// + /// A channel was created. + /// ChannelCreated = 10, + /// + /// A channel was updated. + /// ChannelUpdated = 11, + /// + /// A channel was deleted. + /// ChannelDeleted = 12, + /// + /// A permission overwrite was created for a channel. + /// OverwriteCreated = 13, + /// + /// A permission overwrite was updated for a channel. + /// OverwriteUpdated = 14, + /// + /// A permission overwrite was deleted for a channel. + /// OverwriteDeleted = 15, + /// + /// A user was kicked from this guild. + /// Kick = 20, + /// + /// A prune took place in this guild. + /// Prune = 21, + /// + /// A user banned another user from this guild. + /// Ban = 22, + /// + /// A user unbanned another user from this guild. + /// Unban = 23, + /// + /// A guild member whose information was updated. + /// MemberUpdated = 24, + /// + /// A guild member's role collection was updated. + /// MemberRoleUpdated = 25, + /// + /// A role was created in this guild. + /// RoleCreated = 30, + /// + /// A role was updated in this guild. + /// RoleUpdated = 31, + /// + /// A role was deleted from this guild. + /// RoleDeleted = 32, + /// + /// An invite was created in this guild. + /// InviteCreated = 40, + /// + /// An invite was updated in this guild. + /// InviteUpdated = 41, + /// + /// An invite was deleted from this guild. + /// InviteDeleted = 42, + /// + /// A Webhook was created in this guild. + /// WebhookCreated = 50, + /// + /// A Webhook was updated in this guild. + /// WebhookUpdated = 51, + /// + /// A Webhook was deleted from this guild. + /// WebhookDeleted = 52, + /// + /// An emoji was created in this guild. + /// EmojiCreated = 60, + /// + /// An emoji was updated in this guild. + /// EmojiUpdated = 61, + /// + /// An emoji was deleted from this guild. + /// EmojiDeleted = 62, + /// + /// A message was deleted from this guild. + /// MessageDeleted = 72 } } diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs index 47aaffb26..a99a14eda 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 150c59a42..15ae5fd89 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs @@ -7,28 +7,40 @@ using System.Threading.Tasks; namespace Discord { /// - /// Represents an entry in an audit log + /// Represents a generic audit log entry. /// public interface IAuditLogEntry : ISnowflakeEntity { /// - /// 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/ChannelType.cs b/src/Discord.Net.Core/Entities/Channels/ChannelType.cs index e9f069a50..7759622c2 100644 --- a/src/Discord.Net.Core/Entities/Channels/ChannelType.cs +++ b/src/Discord.Net.Core/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.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..4e92a2369 100644 --- a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs @@ -1,33 +1,28 @@ -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} + /// This property defines the new name for this channel. + /// + /// When modifying an , the must be alphanumeric with + /// dashes. It must match the 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 a152ff744..179f4b03e 100644 --- a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs @@ -1,15 +1,31 @@ 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. + /// + /// Determines whether the client should deaf itself upon connection. + /// Determines whether the client should mute itself upon connection. + /// Determines whether the audio client is an external one or not. + /// + /// A task representing the asynchronous connection operation. The task result contains the + /// responsible for the connection. + /// Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false); - /// Disconnects from this audio channel. + /// + /// Disconnects from this audio channel. + /// + /// + /// A task representing the asynchronous operation for disconnecting from the audio channel. + /// Task DisconnectAsync(); } } 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..b66f778fb 100644 --- a/src/Discord.Net.Core/Entities/Channels/IChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs @@ -1,17 +1,43 @@ -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. + /// + /// + /// A string containing 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. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A paged collection containing a collection of users that can access this channel. Flattening the + /// paginated response into a collection of users with + /// is required if you wish to access the users. + /// 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. + /// + /// The snowflake identifier of the user (e.g. 168693960628371456). + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a user object that + /// represents the found user; null if none is found. + /// 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..f0ef7f3f3 100644 --- a/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs @@ -1,13 +1,27 @@ -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. + /// + /// + /// A user object that represents the other user 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. + /// + /// A task that represents the asynchronous close operation. + /// 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..77af3450e 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs @@ -1,10 +1,19 @@ -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. + /// + /// A task that represents the asynchronous leave operation. + /// 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..c3a2161cc 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -4,45 +4,172 @@ 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. + /// + /// + /// An representing 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. + /// + /// + /// A guild object 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. + /// + /// + /// An representing the guild snowflake identifier for the guild 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 following example creates a new invite to this channel; the invite lasts for 12 hours and can only + /// be used 3 times throughout its lifespan. + /// + /// await guildChannel.CreateInviteAsync(maxAge: 43200, maxUses: 3); + /// + /// + /// 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, the 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. + /// + /// A task that represents the asynchronous invite creation operation. The task result contains an invite + /// metadata object containing information for the created invite. + /// 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. + /// + /// Gets a collection of all invites to this channel. + /// + /// + /// The following example gets all of the invites that have been created in this channel and selects the + /// most used invite. + /// + /// var invites = await channel.GetInvitesAsync(); + /// if (invites.Count == 0) return; + /// var invite = invites.OrderByDescending(x => x.Uses).FirstOrDefault(); + /// + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of invite metadata that are created for this channel. + /// Task> GetInvitesAsync(RequestOptions options = null); - /// Modifies this guild channel. + /// + /// Modifies this guild channel. + /// + /// The delegate containing the properties to modify the channel with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// 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. + /// + /// The role to get the overwrite from. + /// + /// An overwrite object for the targeted role; null if none is set. + /// 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. + /// + /// The user to get the overwrite from. + /// + /// An overwrite object for the targeted user; null if none is set. + /// 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. + /// + /// A task representing the asynchronous operation for removing the specified permissions from the channel. + /// 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. + /// + /// A task representing the asynchronous operation for removing the specified permissions from the channel. + /// 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. + /// + /// A task representing the asynchronous permission operation for adding the specified permissions to the channel. + /// 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. + /// + /// A task representing the asynchronous permission operation for adding the specified permissions to the channel. + /// Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null); - /// Gets a collection of all users in this channel. + /// + /// Gets a collection of users that are able to view the channel. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A paged collection containing a collection of guild users that can access this channel. Flattening the + /// paginated response into a collection of users with + /// is required if you wish to access the users. + /// 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. + /// + /// The snowflake identifier of the user. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task representing the asynchronous get operation. The task result contains a guild user object that + /// represents the user; null if none is found. + /// 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..919b3f60b 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -5,38 +5,276 @@ 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 following example sends a message with the current system time in RFC 1123 format to the channel and + /// deletes itself after 5 seconds. + /// + /// var message = await channel.SendMessageAsync(DateTimeOffset.UtcNow.ToString("R")); + /// await Task.Delay(TimeSpan.FromSeconds(5)) + /// .ContinueWith(x => message.DeleteAsync()); + /// + /// + /// The message to be sent. + /// Determines whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// 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 following example uploads a local file called wumpus.txt along with the text + /// good discord boi to the channel. + /// + /// await channel.SendFileAsync("wumpus.txt", "good discord boi"); + /// + /// + /// The following example uploads a local image called b1nzy.jpg embedded inside a rich embed to the + /// channel. + /// + /// await channel.SendFileAsync("b1nzy.jpg", + /// embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); + /// + /// + /// + /// This method sends a file as if you are uploading an attachment directly from your Discord client. + /// + /// 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 + /// . See the example section for its usage. + /// + /// + /// 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. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// 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 following example uploads a streamed image that will be called b1nzy.jpg embedded inside a + /// rich embed to the channel. + /// + /// await channel.SendFileAsync(b1nzyStream, "b1nzy.jpg", + /// embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); + /// + /// + /// + /// This method sends a file as if you are uploading an attachment directly from your Discord client. + /// + /// 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 + /// . See the example section for its usage. + /// + /// + /// 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. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// 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. + /// + /// The snowflake identifier of the message. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents an asynchronous get operation for retrieving the message. The task result contains + /// the retrieved message; null if no message is found with the specified identifier. + /// 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 returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under . The + /// library will attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example downloads 300 messages and gets messages that belong to the user + /// 53905483156684800. + /// + /// var messages = await messageChannel.GetMessagesAsync(300).FlattenAsync(); + /// var userMessages = messages.Where(x => x.Author.Id == 53905483156684800); + /// + /// + /// 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. + /// 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 returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under around + /// the message depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example gets 5 message prior to the message identifier 442012544660537354. + /// + /// var messages = await channel.GetMessagesAsync(442012544660537354, Direction.Before, 5).FlattenAsync(); + /// + /// + /// 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. + /// 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 returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under around + /// the message depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example gets 5 message prior to a specific message, oldMessage. + /// + /// var messages = await channel.GetMessagesAsync(oldMessage, Direction.Before, 5).FlattenAsync(); + /// + /// + /// 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. + /// 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. + /// + /// A task that represents the asynchronous get operation for retrieving pinned messages in this channel. + /// The task result contains a collection of messages found in the pinned messages. + /// Task> GetPinnedMessagesAsync(RequestOptions options = null); - /// Deletes a message based on the message ID in this channel. + /// + /// Deletes a message. + /// + /// The snowflake identifier of the message that would be removed. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation. + /// Task DeleteMessageAsync(ulong messageId, RequestOptions options = null); /// Deletes a message based on the provided message in this channel. + /// The message that would be removed. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation. + /// 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. + /// + /// A task that represents the asynchronous operation that triggers the broadcast. + /// 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 following example keeps the client in the typing state until LongRunningAsync has finished. + /// + /// using (messageChannel.EnterTypingState()) + /// { + /// await LongRunningAsync(); + /// } + /// + /// + /// The options to be used when sending the request. + /// + /// A disposable object that, upon its disposal, will stop the client from broadcasting its typing state in + /// this channel. + /// IDisposable EnterTypingState(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs b/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs index c8d2bcaaf..22182a4ca 100644 --- a/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs @@ -3,14 +3,27 @@ using System.Threading.Tasks; namespace Discord { /// - /// A type of guild channel that can be nested within a category. - /// Contains a CategoryId that is set to the parent category, if it is set. + /// Represents a type of guild channel that can be nested within a category. /// public interface INestedChannel : IGuildChannel { - /// Gets the parentid (category) of this channel in the guild's channel list. + /// + /// Gets the parent (category) ID of this channel in the guild's channel list. + /// + /// + /// A representing the snowflake identifier of the parent of this channel; + /// null if none is set. + /// ulong? CategoryId { get; } - /// Gets the parent channel (category) of this channel, if it is set. If unset, returns null. + /// + /// Gets the parent (category) channel of this channel. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the category channel + /// representing the parent of this channel; null if none is set. + /// Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, 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..cd2307c2d 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 read-only 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 89e10e65e..440349eca 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -5,30 +5,114 @@ 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. + /// + /// Gets a value that indicates 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. + /// + /// + /// A string representing the topic set in the channel; null if none is set. + /// string Topic { get; } - /// Gets the current slow-mode delay for this channel. 0 if disabled. + /// + /// Gets the current slow-mode delay for this channel. + /// + /// + /// An representing the time in seconds required before the user can send another + /// message; 0 if disabled. + /// int SlowModeInterval { get; } - /// Bulk deletes multiple messages. + /// + /// Bulk-deletes multiple messages. + /// + /// + /// The following example gets 250 messages from the channel and deletes them. + /// + /// var messages = await textChannel.GetMessagesAsync(250).FlattenAsync(); + /// await textChannel.DeleteMessagesAsync(messages); + /// + /// + /// + /// This method attempts to remove the messages specified in bulk. + /// + /// Due to the limitation set by Discord, 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. + /// + /// A task that represents the asynchronous bulk-removal operation. + /// Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); - /// Bulk deletes multiple messages. + /// + /// Bulk-deletes multiple messages. + /// + /// + /// This method attempts to remove the messages specified in bulk. + /// + /// Due to the limitation set by Discord, this method can only remove messages that are posted within 14 days! + /// + /// + /// The snowflake identifier of the messages to be bulk-deleted. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous bulk-removal operation. + /// Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null); - /// Modifies this text channel. + /// + /// Modifies this text channel. + /// + /// The delegate containing the properties to modify the channel with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + /// 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly 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 a webhook available in this text channel. + /// + /// The identifier of the webhook. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a webhook associated + /// with the identifier; null if the webhook is not found. + /// Task GetWebhookAsync(ulong id, RequestOptions options = null); - /// Gets the webhooks for this text channel. + /// + /// Gets the webhooks available in this text channel. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of webhooks that is available in this channel. + /// 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..9c2d008ee 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -3,14 +3,37 @@ 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 bit-rate that the clients in this voice channel are requested to use. + /// + /// + /// An representing the bit-rate (bps) that this voice channel defines and requests the + /// client(s) to use. + /// int Bitrate { get; } - /// Gets the max amount of users allowed to be connected to this channel at one time. + /// + /// Gets the max number of users allowed to be connected to this channel at once. + /// + /// + /// An representing the maximum number of users that are allowed to be connected to this + /// channel at once; null if a limit is not 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. + /// + /// A task that represents the asynchronous modification operation. + /// + /// 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..ffd90dae6 100644 --- a/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs @@ -1,12 +1,28 @@ -namespace Discord +namespace Discord { + /// + /// Provides 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. + /// + /// + /// A representing the snowflake identifier of this channel. + /// public ulong Id { get; } - /// The new zero-based position of this channel. + /// + /// Gets the new zero-based position of this channel. + /// + /// + /// An representing the new position of this channel. + /// public int Position { get; } + /// Initializes a new instance of the class 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 87adccb85..fbe4bfa43 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -1,27 +1,29 @@ -using System; - namespace Discord { - /// + /// + /// Provides 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; } /// - /// What the slow-mode ratelimit for this channel should be set to; 0 will disable slow-mode. + /// Gets or sets the slow-mode ratelimit in seconds for this channel. /// /// - /// This value must fall within [0, 120] - /// - /// Users with will be exempt from slow-mode. + /// Setting this value to 0 will disable slow-mode for this channel. + /// + /// Users with will be exempt from slow-mode. + /// /// - /// Throws ArgummentOutOfRange if the value does not fall within [0, 120] + /// Thrown if the value does not fall within [0, 120]. public Optional SlowModeInterval { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs index 81dd8063e..fb4d47800 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 { - /// + /// + /// Provides 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..d5e795094 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs @@ -1,28 +1,35 @@ -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; } - + /// + /// A string that resolves to . + /// public override string ToString() => Name; /// - /// Creates a unicode emoji. + /// Initializes a new class with the provided Unicode. /// - /// 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 one. + /// + /// The object to compare with the current object. public override bool Equals(object other) { if (other == null) return false; @@ -34,6 +41,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 a71a73327..bebd9fa4f 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -1,26 +1,34 @@ using System; using System.Globalization; +using System.Diagnostics; 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. /// + /// + /// A boolean that determines whether or not this emote is an animated one. + /// public bool Animated { get; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// + /// Gets the image URL of this emote. + /// + /// + /// A string that points to the URL of this emote. + /// public string Url => CDN.GetEmojiUrl(Id, Animated); internal Emote(ulong id, string name, bool animated) @@ -30,6 +38,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 +53,7 @@ namespace Discord return string.Equals(Name, otherEmote.Name) && Id == otherEmote.Id; } + /// public override int GetHashCode() { unchecked @@ -49,18 +62,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 (e.g. <:dab:277855270321782784>). + /// An emote. + /// Invalid emote format. public static Emote Parse(string text) { if (TryParse(text, out Emote result)) return result; - throw new ArgumentException(message: "Invalid emote format", paramName: nameof(text)); + throw new ArgumentException(message: "Invalid emote format.", paramName: 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 +100,12 @@ namespace Discord } private string DebuggerDisplay => $"{Name} ({Id})"; + /// + /// Returns the raw representation of the emote. + /// + /// + /// A string representing the raw presentation of the emote (e.g. <:thonkang:282745590985523200>). + /// 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..41679d2af 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 { + /// + /// Provides 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..9affb1e89 100644 --- a/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs @@ -1,16 +1,34 @@ -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 by an integration. + /// + /// + /// A boolean that determines whether or not this emote is managed by a Twitch integration. + /// public bool IsManaged { get; } + /// + /// Gets whether this emoji must be wrapped in colons. + /// + /// + /// A boolean that determines whether or not this emote requires the use of colons in chat to be used. + /// public bool RequireColons { get; } + /// + /// Gets the roles that are allowed to use this emoji. + /// + /// + /// A read-only list containing snowflake identifiers for roles that are allowed to use this emoji. + /// public IReadOnlyList RoleIds { get; } internal GuildEmote(ulong id, string name, bool animated, bool isManaged, bool requireColons, IReadOnlyList roleIds) : base(id, name, animated) @@ -21,6 +39,12 @@ namespace Discord } private string DebuggerDisplay => $"{Name} ({Id})"; + /// + /// Gets the raw representation of the emote. + /// + /// + /// A string representing the raw presentation of the emote (e.g. <:thonkang:282745590985523200>). + /// 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..9141e8537 100644 --- a/src/Discord.Net.Core/Entities/Emotes/IEmote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/IEmote.cs @@ -1,13 +1,16 @@ -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. /// + /// + /// A string representing the display name or the Unicode representation (e.g. 🤔) 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..34473e93c 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 + /// Provides 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..2ca19b50a 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 { + /// + /// Provides 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..0ffe8db35 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 + /// Provides 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..617f2fe04 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 user 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 bbe7051cb..8f9108d95 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -5,165 +5,668 @@ 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) + /// + /// Gets a value that indicates whether 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. + /// + /// + /// A 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 default channel for this guild. + /// + /// + /// This property retrieves the snowflake identifier of the first viewable text channel for this guild. + /// + /// This channel does not guarantee the user can send message to it, as it only looks for the first viewable + /// text channel. + /// + /// + /// + /// A representing the snowflake identifier of the default text channel; 0 if + /// none can be found. + /// ulong DefaultChannelId { get; } - /// Gets the id of the embed channel for this guild if set, or null if not. + /// + /// Gets the ID of the widget embed channel of this guild. + /// + /// + /// A representing the snowflake identifier of the embedded channel found within the + /// widget settings of this guild; 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. + /// + /// + /// A representing the snowflake identifier of the system channel where randomized + /// welcome messages are sent; 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 owns this guild. + /// + /// + /// A representing the snowflake identifier of the user that owns 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. + /// + /// + /// A string containing the identifier for the voice region that this guild uses (e.g. eu-central). + /// string VoiceRegionId { get; } - /// Gets the IAudioClient currently associated with this guild. + /// + /// Gets the currently associated with this guild. + /// + /// + /// An 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. + /// + /// + /// A role object 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 read-only 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 read-only 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 read-only 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. + /// + /// A task that represents the asynchronous modification operation. + /// 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. + /// + /// A task that represents the asynchronous modification operation. + /// 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. + /// + /// A task that represents the asynchronous reorder operation. + /// 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. + /// + /// A task that represents the asynchronous reorder operation. + /// 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 of this guild, use instead. + /// + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous leave operation. + /// Task LeaveAsync(RequestOptions options = null); - /// Gets a collection of all users banned on this guild. + /// + /// Gets a collection of all users banned in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// ban objects that this guild currently possesses, with each object containing the user banned and reason + /// behind the ban. + /// 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. + /// A task that represents the asynchronous get operation. The task result contains a ban object, which + /// contains the user information and the 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. + /// A task that represents the asynchronous get operation. The task result contains a ban object, which + /// contains the user information and the 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 user for, and this number 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. + /// + /// A task that represents the asynchronous add operation for the ban. + /// 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, and this number 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. + /// + /// A task that represents the asynchronous add operation for the ban. + /// Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null); - /// Unbans the provided user if it is currently banned. + /// + /// Unbans the user if they are currently banned. + /// + /// The user to be unbanned. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation for the ban. + /// Task RemoveBanAsync(IUser user, RequestOptions options = null); - /// Unbans the provided user id if it is currently banned. + /// + /// Unbans the user if they are currently banned. + /// + /// The snowflake identifier of the user to be unbanned. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation for the ban. + /// 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. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only 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 a channel in this guild. + /// + /// The snowflake identifier for the channel. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the generic channel + /// associated with the specified ; 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. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// message channels found within this guild. + /// Task> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a text channel in this guild. + /// + /// The snowflake identifier for the text channel. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the text channel + /// associated with the specified ; 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. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only 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. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// category channels found within this guild. + /// Task> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a voice channel in this guild. + /// + /// The snowflake identifier for the voice channel. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the voice channel associated + /// with the specified ; 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. + /// + /// A task that represents the asynchronous get operation. The task result contains the voice channel that the + /// AFK users will be moved to after they have idled for too long; null if none is set. + /// Task GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets the system channel where randomized welcome messages are sent in this guild. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the text channel where + /// randomized welcome messages will be sent to; null if none is set. + /// Task GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets the first viewable text channel in this guild. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the first viewable text + /// channel in this guild; 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. + /// + /// A task that represents the asynchronous get operation. The task result contains the embed channel set + /// within the server's widget settings; null if none is set. + /// Task GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Creates a new text channel. + + /// + /// Creates a new text channel in this guild. + /// + /// + /// The following example creates a new text channel under an existing category named Wumpus with a set topic. + /// + /// var categories = await guild.GetCategoriesAsync(); + /// var targetCategory = categories.FirstOrDefault(x => x.Name == "wumpus"); + /// if (targetCategory == null) return; + /// await Context.Guild.CreateTextChannelAsync(name, x => + /// { + /// x.CategoryId = targetCategory.Id; + /// x.Topic = $"This channel was created at {DateTimeOffset.UtcNow} by {user}."; + /// }); + /// + /// + /// 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains 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 in this guild. + /// + /// 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains 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 in this guild. + /// + /// The new name for the category. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains 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 in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// invite metadata, each representing information for an invite found within this guild. + /// Task> GetInvitesAsync(RequestOptions options = null); /// /// Gets the vanity invite URL of this guild. /// /// The options to be used when sending the request. /// - /// An awaitable containing the partial metadata of the vanity invite found within - /// this guild. + /// A task that represents the asynchronous get operation. The task result contains the partial metadata of + /// the vanity invite found within this guild; null if none is found. /// Task GetVanityInviteAsync(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 snowflake identifier for the role. + /// + /// A role that is associated with the specified ; 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// 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 collection in the WebSocket implementation due to how Discord does not + /// send a complete user list for large guilds. + /// + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a collection of guild + /// users found within this guild. + /// + Task> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a user from this guild. + /// + /// + /// This method retrieves a user found within this guild. + /// + /// This may return null in the WebSocket implementation due to incomplete user collection in + /// large guilds. + /// + /// + /// The snowflake identifier of the user. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the guild user + /// associated with the specified ; 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. + /// + /// A task that represents the asynchronous get operation. The task result contains 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. + /// + /// A task that represents the asynchronous get operation. The task result contains 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. + /// + /// + /// A task that represents the asynchronous download operation. + /// 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 . + /// + /// + /// If is true, this method will only return the number of users that + /// would be removed without kicking the users. + /// + /// + /// 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. + /// + /// A task that represents the asynchronous prune operation. The task result contains 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. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of the requested audit log entries. + /// Task> GetAuditLogsAsync(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 identifier for the webhook. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the webhook with the + /// specified ; 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. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only 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 snowflake identifier for the guild emote. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the emote found with the + /// specified ; 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains 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. + /// + /// A task that represents the asynchronous modification operation. The task result contains the 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. + /// + /// A task that represents the asynchronous removal operation. + /// Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs b/src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs index 225ce05d6..6fe3f7b55 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs @@ -1,17 +1,69 @@ -using System; +using System; namespace Discord { + /// + /// Holds information for a guild integration feature. + /// public interface IGuildIntegration { + /// + /// Gets the integration ID. + /// + /// + /// An representing the unique identifier value of this integration. + /// ulong Id { get; } + /// + /// Gets the integration name. + /// + /// + /// A string containing the name of this integration. + /// string Name { get; } + /// + /// Gets the integration type (Twitch, YouTube, etc). + /// + /// + /// A string containing the name of the type of integration. + /// string Type { get; } + /// + /// Gets a value that indicates whether this integration is enabled or not. + /// + /// + /// true if this integration is enabled; otherwise false. + /// bool IsEnabled { get; } + /// + /// Gets a value that indicates whether this integration is syncing or not. + /// + /// + /// An integration with syncing enabled will update its "subscribers" on an interval, while one with syncing + /// disabled will not. A user must manually choose when sync the integration if syncing is disabled. + /// + /// + /// true if this integration is syncing; otherwise false. + /// bool IsSyncing { get; } + /// + /// Gets the ID that this integration uses for "subscribers". + /// ulong ExpireBehavior { get; } + /// + /// Gets the grace period before expiring "subscribers". + /// ulong ExpireGracePeriod { get; } + /// + /// Gets when this integration was last synced. + /// + /// + /// A containing a date and time of day when the integration was last synced. + /// DateTimeOffset SyncedAt { get; } + /// + /// Gets integration account information. + /// IntegrationAccount Account { get; } IGuild Guild { get; } 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..9cef84914 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs @@ -1,18 +1,51 @@ 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. + /// + /// + /// A string that represents the identifier for this voice region (e.g. eu-central). + /// string Id { get; } - /// Gets the name of this voice region. + /// + /// Gets the name of this voice region. + /// + /// + /// A string that represents the human-readable name of this voice region (e.g. Central Europe). + /// string Name { get; } - /// Returns true if this voice region is exclusive to VIP accounts. + /// + /// Gets a value that indicates whether or not this voice region is exclusive to partnered servers. + /// + /// + /// true if this voice region is exclusive to VIP accounts; otherwise false. + /// bool IsVip { get; } - /// Returns true if this voice region is the closest to your machine. + /// + /// Gets a value that indicates whether this voice region is optimal for your client in terms of latency. + /// + /// + /// true if this voice region is the closest to your machine; otherwise false . + /// bool IsOptimal { get; } - /// Returns true if this is a deprecated voice region (avoid switching to these). + /// + /// Gets a value that indicates whether this voice region is no longer being maintained. + /// + /// + /// true if this is a deprecated voice region; otherwise false. + /// bool IsDeprecated { get; } - /// Returns true if this is a custom voice region (used for events/etc) + /// + /// Gets a value that indicates whether this voice region is custom-made for events. + /// + /// + /// true if this is a custom voice region (used for events/etc); otherwise false/ + /// bool IsCustom { get; } } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs b/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs index 71bcf10ed..340115fde 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs @@ -1,11 +1,15 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Discord { [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct IntegrationAccount { + /// Gets the ID of the account. + /// A unique identifier of this integration account. public string Id { get; } + /// Gets the name of the account. + /// A string containing the name of this integration account. public string Name { get; private set; } public override string ToString() => Name; 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..78a87dc19 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..6f2c7512b 100644 --- a/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs +++ b/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs @@ -2,8 +2,15 @@ using System; namespace Discord { + /// Represents a Discord snowflake entity. public interface ISnowflakeEntity : IEntity { + /// + /// Gets when the snowflake was created. + /// + /// + /// A representing when the entity was first 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..d5a9e26a3 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 1ab26de8f..6455b9f5c 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInvite.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInvite.cs @@ -1,30 +1,85 @@ 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 type of the channel this invite is linked to. ChannelType ChannelType { 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 guild object 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 0e026ab62..471dc377f 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs @@ -2,21 +2,62 @@ 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 user that created this invite. + /// IUser Inviter { get; } - /// Returns true if this invite was revoked. + /// + /// Gets a value that indicates 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. + /// + /// Gets a value that indicates whether the invite is a temporary one. + /// + /// + /// 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; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/Embed.cs b/src/Discord.Net.Core/Entities/Messages/Embed.cs index dc62066eb..7fa6f6f36 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..3b11f6a8b 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 82e94e39f..39d24fa03 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(message: "Url must be a well-formed URI", paramName: nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: 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(message: "Url must be a well-formed URI", paramName: nameof(ThumbnailUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: 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(message: "Url must be a well-formed URI", paramName: nameof(ImageUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: 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(paramName: nameof(Fields), message: "Cannot set an embed builder's fields collection to null"); + if (value == null) throw new ArgumentNullException(paramName: nameof(Fields), message: "Cannot set an embed builder's fields collection to null."); if (value.Count > MaxFieldCount) throw new ArgumentException(message: $"Field count must be less than or equal to {MaxFieldCount}.", paramName: 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,13 +431,33 @@ 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; @@ -245,6 +469,17 @@ namespace Discord } } + /// + /// 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; @@ -256,35 +491,84 @@ namespace Discord _value = stringValue; } } + /// + /// Gets or sets a value that indicates 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(message: "Url must be a well-formed URI", paramName: nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: 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(message: "Url must be a well-formed URI", paramName: nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: 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(message: "Url must be a well-formed URI", paramName: nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: 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..f6aa2af3b 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; } + /// + /// Gets a value that indicates 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,12 @@ namespace Discord } private string DebuggerDisplay => $"{Name} ({Value}"; + /// + /// Gets the name of the field. + /// + /// + /// A string that resolves to . + /// 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..4c507d017 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs @@ -1,14 +1,32 @@ -using System; using System.Diagnostics; namespace Discord { + /// A footer field for an . [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedFooter { - public string Text { get; internal set; } - public string IconUrl { get; internal set; } - public string ProxyUrl { get; internal set; } + /// + /// Gets the text of the footer field. + /// + /// + /// A string containing the text of the footer field. + /// + public string Text { get; } + /// + /// Gets the URL of the footer icon. + /// + /// + /// A string containing the URL of the footer icon. + /// + public string IconUrl { get; } + /// + /// Gets the proxied URL of the footer icon link. + /// + /// + /// A string containing the proxied URL of the footer icon. + /// + public string ProxyUrl { get; } internal EmbedFooter(string text, string iconUrl, string proxyUrl) { @@ -18,6 +36,12 @@ namespace Discord } private string DebuggerDisplay => $"{Text} ({IconUrl})"; + /// + /// Gets the text of the footer field. + /// + /// + /// A string that resolves to . + /// public override string ToString() => Text; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index e12ffc53f..9ce2bfe73 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -1,14 +1,40 @@ -using System; using System.Diagnostics; namespace Discord { + /// An image for an . [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedImage { + /// + /// Gets the URL of the image. + /// + /// + /// A string containing the URL of the image. + /// public string Url { get; } + /// + /// Gets a proxied URL of this image. + /// + /// + /// A string containing the proxied URL of this image. + /// public string ProxyUrl { get; } + /// + /// Gets the height of this image. + /// + /// + /// A representing the height of this image if it can be retrieved; otherwise + /// null. + /// public int? Height { get; } + /// + /// Gets the width of this image. + /// + /// + /// A representing the width of this image if it can be retrieved; otherwise + /// null. + /// public int? Width { get; } internal EmbedImage(string url, string proxyUrl, int? height, int? width) @@ -20,6 +46,12 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; + /// + /// Gets the URL of the thumbnail. + /// + /// + /// A string that resolves to . + /// public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs index 24722b158..960fb3d78 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs @@ -1,12 +1,24 @@ -using System; using System.Diagnostics; namespace Discord { + /// A provider field for an . [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedProvider { + /// + /// Gets the name of the provider. + /// + /// + /// A string representing the name of the provider. + /// public string Name { get; } + /// + /// Gets the URL of the provider. + /// + /// + /// A string representing the link to the provider. + /// public string Url { get; } internal EmbedProvider(string name, string url) @@ -16,6 +28,12 @@ namespace Discord } private string DebuggerDisplay => $"{Name} ({Url})"; + /// + /// Gets the name of the provider. + /// + /// + /// A string that resolves to . + /// public override string ToString() => Name; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 9b3d6153a..7f7b582dc 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -1,14 +1,40 @@ -using System; using System.Diagnostics; namespace Discord { + /// A thumbnail featured in an . [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedThumbnail { + /// + /// Gets the URL of the thumbnail. + /// + /// + /// A string containing the URL of the thumbnail. + /// public string Url { get; } + /// + /// Gets a proxied URL of this thumbnail. + /// + /// + /// A string containing the proxied URL of this thumbnail. + /// public string ProxyUrl { get; } + /// + /// Gets the height of this thumbnail. + /// + /// + /// A representing the height of this thumbnail if it can be retrieved; otherwise + /// null. + /// public int? Height { get; } + /// + /// Gets the width of this thumbnail. + /// + /// + /// A representing the width of this thumbnail if it can be retrieved; otherwise + /// null. + /// public int? Width { get; } internal EmbedThumbnail(string url, string proxyUrl, int? height, int? width) @@ -20,6 +46,12 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; + /// + /// Gets the URL of the thumbnail. + /// + /// + /// A string that resolves to . + /// public override string ToString() => Url; } } 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 5725e0e14..ca0300e80 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -1,13 +1,35 @@ -using System; using System.Diagnostics; namespace Discord { + /// + /// A video featured in an . + /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedVideo { + /// + /// Gets the URL of the video. + /// + /// + /// A string containing the URL of the image. + /// public string Url { get; } + /// + /// Gets the height of the video. + /// + /// + /// A representing the height of this video if it can be retrieved; otherwise + /// null. + /// public int? Height { get; } + /// + /// Gets the weight of the video. + /// + /// + /// A representing the width of this video if it can be retrieved; otherwise + /// null. + /// public int? Width { get; } internal EmbedVideo(string url, int? height, int? width) @@ -18,6 +40,12 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; + /// + /// Gets the URL of the video. + /// + /// + /// A string that resolves to . + /// 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..13b2cf48a 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..87754eecd 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -1,41 +1,104 @@ -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. + /// + /// Gets the value that indicates whether this message was meant to be read-aloud by Discord. + /// + /// + /// true if this message was sent as a text-to-speech message; otherwise false. + /// bool IsTTS { get; } - /// Returns true if this message was added to its channel's pinned messages. + /// + /// Gets the value that indicates whether this message is pinned. + /// + /// + /// true if this message was added to its channel's pinned messages; otherwise false. + /// bool IsPinned { get; } - /// Returns the content for this message. + /// + /// Gets the content for this message. + /// + /// + /// A string that contains the body of the message; note that this field may be empty if there is an embed. + /// 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. + /// + /// + /// A read-only collection of attachments. + /// IReadOnlyCollection Attachments { get; } - /// Returns all embeds included in this message. + /// + /// Returns all embeds included in this message. + /// + /// + /// A read-only 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. + /// + /// + /// A read-only 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. + /// + /// + /// A read-only 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. + /// + /// + /// A read-only 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..b31890e51 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -4,28 +4,114 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic message sent by a user. + /// public interface IUserMessage : IMessage { - /// Modifies this message. + /// + /// Modifies this message. + /// + /// + /// + /// await msg.ModifyAsync(x => x.Content = "Hello World!"); + /// + /// + /// A delegate containing the properties to modify the message with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// 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. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous operation for pinning this message. + /// Task PinAsync(RequestOptions options = null); - /// Removes this message from its channel's pinned messages. + /// + /// Removes this message from its channel's pinned messages. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous operation for unpinning this message. + /// Task UnpinAsync(RequestOptions options = null); - /// Returns all reactions included in this message. + /// + /// Gets all reactions included in this message. + /// IReadOnlyDictionary Reactions { get; } - /// Adds a reaction to this message. + /// + /// Adds a reaction to this message. + /// + /// + /// + /// await msg.AddReactionAsync(new Emoji("\U0001f495")); + /// + /// + /// The emoji used to react to this message. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous operation for adding a reaction to this message. + /// + /// Task AddReactionAsync(IEmote emote, RequestOptions options = null); - /// Removes a reaction from message. + /// + /// Removes a reaction from message. + /// + /// + /// + /// await msg.RemoveReactionAsync(new Emoji("\U0001f495"), msg.Author); + /// + /// + /// The emoji used to react to this message. + /// The user that added the emoji. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous operation for removing a reaction to this message. + /// + /// Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null); - /// Removes all reactions from this message. + /// + /// Removes all reactions from this message. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation. + /// 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. + /// + /// + /// + /// var emoji = new Emoji("\U0001f495"); + /// var reactedUsers = await message.GetReactionUsersAsync(emoji, 100).FlattenAsync(); + /// + /// + /// The emoji that represents the reaction that you wish to get. + /// The number of users to request. + /// The options to be used when sending the request. + /// + /// A paged collection containing a read-only collection of users that has reacted to this message. + /// Flattening the paginated response into a collection of users with + /// is required if you wish to access the users. + /// 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. + /// + /// Determines how the user tag should be handled. + /// Determines how the channel tag should be handled. + /// Determines how the role tag should be handled. + /// Determines how the @everyone tag should be handled. + /// Determines how the emoji tag should be handled. 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..850666921 100644 --- a/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs +++ b/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs @@ -1,11 +1,24 @@ -namespace Discord +namespace Discord { + /// + /// A metadata containing reaction information. + /// public struct ReactionMetadata { - /// Gets the number of reactions + /// + /// Gets the number of reactions. + /// + /// + /// An representing the number of this reactions that has been added to this message. + /// public int ReactionCount { get; internal set; } - /// Returns true if the current user has used this reaction + /// + /// Gets a value that indicates whether the current user has reacted to this. + /// + /// + /// true if the user has reacted to the message; otherwise false. + /// 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 0fbd22c4e..e3cfc0e19 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -2,38 +2,103 @@ 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 messages. This flag is obsolete, use instead. + /// [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, PrioritySpeaker = 0x00_00_01_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 ea56734ff..7bef3251a 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -7,19 +7,21 @@ namespace Discord [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct ChannelPermissions { - /// Gets a blank ChannelPermissions that grants no permissions. + /// Gets a blank that grants no permissions. + /// A structure that does not contain any set 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_0000000010100_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,64 +31,64 @@ namespace Discord case ICategoryChannel _: return Category; case IDMChannel _: return DM; case IGroupChannel _: return Group; - default: throw new ArgumentException(message: "Unknown channel type", paramName: nameof(channel)); + default: throw new ArgumentException(message: "Unknown channel type.", paramName: 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 use priority speaker in a voice channel. + /// If true, a user may use priority speaker in a voice channel. public bool PrioritySpeaker => Permissions.GetValue(RawValue, ChannelPermission.PrioritySpeaker); - /// 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, @@ -139,7 +141,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, @@ -167,7 +169,7 @@ namespace Discord speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, 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 13a9e32b1..c010d90b1 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -2,46 +2,163 @@ 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, + /// + /// Allows kicking members. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + KickMembers = 0x00_00_00_02, + /// + /// Allows banning members. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + BanMembers = 0x00_00_00_04, + /// + /// Allows all permissions and bypasses channel permission overwrites. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// Administrator = 0x00_00_00_08, + /// + /// Allows management and editing of channels. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// ManageChannels = 0x00_00_00_10, + /// + /// Allows management and editing of the guild. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// 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, + /// + /// Allows for deletion of other users messages. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// 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 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 + /// + /// 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, PrioritySpeaker = 0x00_00_01_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. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// ManageRoles = 0x10_00_00_00, + /// + /// Allows management and editing of webhooks. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// ManageWebhooks = 0x20_00_00_00, + /// + /// Allows management and editing of emojis. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// 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 c9cb90ec8..2d2a9e56a 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_1111111110111_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,48 +42,48 @@ 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 use priority speaker in a voice channel. public bool PrioritySpeaker => Permissions.GetValue(RawValue, ChannelPermission.PrioritySpeaker); - /// 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, @@ -152,7 +152,7 @@ namespace Discord RawValue = value; } - /// Creates a new GuildPermissions with the provided permissions. + /// Creates a new structure with the provided permissions. public GuildPermissions( bool createInstantInvite = false, bool kickMembers = false, @@ -215,7 +215,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, @@ -251,8 +251,19 @@ namespace Discord readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); + /// + /// Returns a value that indicates if a specific is enabled + /// in these permissions. + /// + /// The permission value to check for. + /// true if the permission is enabled, false otherwise. public bool Has(GuildPermission permission) => Permissions.GetValue(RawValue, permission); + /// + /// Returns a containing all of the + /// flags that are enabled. + /// + /// A containing flags. Empty if none are enabled. public List ToList() { var perms = new List(); diff --git a/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs index bda67a870..f8f3fff44 100644 --- a/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs +++ b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs @@ -1,15 +1,26 @@ -namespace Discord +namespace Discord { + /// + /// Represent a permission object. + /// 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. + /// + /// Initializes 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 b8b4b83e2..04bb2f668 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -4,21 +4,36 @@ using System.Diagnostics; namespace Discord { + /// + /// Represents a container for a series of overwrite permissions. + /// [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. + /// + /// Unknown channel type. 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. + /// + /// Unknown channel type. 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 +77,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 +136,9 @@ namespace Discord DenyValue = denyValue; } - /// Creates a new ChannelPermissions with the provided permissions. + /// + /// Initializes a new struct with the provided permissions. + /// public OverwritePermissions( PermValue createInstantInvite = PermValue.Inherit, PermValue manageChannel = PermValue.Inherit, @@ -147,7 +164,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. + /// + /// Initializes a new from the current one, changing the provided + /// non-null permissions. + /// public OverwritePermissions Modify( PermValue? createInstantInvite = null, PermValue? manageChannel = null, @@ -173,6 +193,10 @@ namespace Discord embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks); + /// + /// Creates a of all the values that are allowed. + /// + /// A of all allowed flags. If none, the list will be empty. public List ToAllowList() { var perms = new List(); @@ -185,6 +209,11 @@ namespace Discord } return perms; } + + /// + /// Creates a of all the values that are denied. + /// + /// A of all denied flags. If none, the list will be empty. public List ToDenyList() { var perms = new List(); 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 727049dcc..10bed840d 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -6,53 +6,79 @@ using StandardColor = System.Drawing.Color; namespace Discord { + /// + /// Represents a color used in Discord. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Color { /// Gets the default user color value. public static readonly Color Default = new Color(0); - /// Gets the teal color value + /// Gets the teal color value. + /// A color struct with the hex value of 1ABC9C. public static readonly Color Teal = new Color(0x1ABC9C); - /// Gets the dark teal color value + /// Gets the dark teal color value. public static readonly Color DarkTeal = new Color(0x11806A); - /// Gets the green color value + /// Gets the green color value. + /// A color struct with the hex value of 11806A. public static readonly Color Green = new Color(0x2ECC71); - /// Gets the dark green color value + /// Gets the dark green color value. + /// A color struct with the hex value of 2ECC71. public static readonly Color DarkGreen = new Color(0x1F8B4C); - /// Gets the blue color value + /// Gets the blue color value. + /// A color struct with the hex value of 1F8B4C. public static readonly Color Blue = new Color(0x3498DB); - /// Gets the dark blue color value + /// Gets the dark blue color value. + /// A color struct with the hex value of 3498DB. public static readonly Color DarkBlue = new Color(0x206694); - /// Gets the purple color value + /// Gets the purple color value. + /// A color struct with the hex value of 206694. public static readonly Color Purple = new Color(0x9B59B6); - /// Gets the dark purple color value + /// Gets the dark purple color value. + /// A color struct with the hex value of 9B59B6. public static readonly Color DarkPurple = new Color(0x71368A); - /// Gets the magenta color value + /// Gets the magenta color value. + /// A color struct with the hex value of 71368A. public static readonly Color Magenta = new Color(0xE91E63); - /// Gets the dark magenta color value + /// Gets the dark magenta color value. + /// A color struct with the hex value of E91E63. public static readonly Color DarkMagenta = new Color(0xAD1457); - /// Gets the gold color value + /// Gets the gold color value. + /// A color struct with the hex value of AD1457. public static readonly Color Gold = new Color(0xF1C40F); - /// Gets the light orange color value + /// Gets the light orange color value. + /// A color struct with the hex value of F1C40F. public static readonly Color LightOrange = new Color(0xC27C0E); - /// Gets the orange color value + /// Gets the orange color value. + /// A color struct with the hex value of C27C0E. public static readonly Color Orange = new Color(0xE67E22); - /// Gets the dark orange color value + /// Gets the dark orange color value. + /// A color struct with the hex value of E67E22. public static readonly Color DarkOrange = new Color(0xA84300); - /// Gets the red color value + /// Gets the red color value. + /// A color struct with the hex value of A84300. public static readonly Color Red = new Color(0xE74C3C); - /// Gets the dark red color value + /// Gets the dark red color value. + /// A color struct with the hex value of E74C3C. public static readonly Color DarkRed = new Color(0x992D22); - /// Gets the light grey color value + /// Gets the light grey color value. + /// A color struct with the hex value of 992D22. public static readonly Color LightGrey = new Color(0x979C9F); - /// Gets the lighter grey color value + /// Gets the lighter grey color value. + /// A color struct with the hex value of 979C9F. public static readonly Color LighterGrey = new Color(0x95A5A6); - /// Gets the dark grey color value + /// Gets the dark grey color value. + /// A color struct with the hex value of 95A5A6. public static readonly Color DarkGrey = new Color(0x607D8B); - /// Gets the darker grey color value + /// Gets the darker grey color value. + /// A color struct with the hex value of 607D8B. public static readonly Color DarkerGrey = new Color(0x546E7A); /// Gets the encoded value for this color. + /// + /// This value is encoded as an unsigned integer value. The most-significant 8 bits contain the red value, + /// the middle 8 bits contain the green value, and the least-significant 8 bits contain the blue value. + /// public uint RawValue { get; } /// Gets the red component for this color. @@ -62,10 +88,34 @@ namespace Discord /// Gets the blue component for this color. public byte B => (byte)(RawValue); + /// + /// Initializes a struct with the given raw value. + /// + /// + /// The following will create a color that has a hex value of + /// #607D8B. + /// + /// Color darkGrey = new Color(0x607D8B); + /// + /// + /// The raw value of the color (e.g. 0x607D8B). public Color(uint rawValue) { RawValue = rawValue; } + /// + /// Initializes a struct with the given RGB bytes. + /// + /// + /// The following will create a color that has a value of + /// #607D8B. + /// + /// Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011); + /// + /// + /// 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 +123,56 @@ namespace Discord ((uint)g << 8) | (uint)b; } + + /// + /// Initializes a struct with the given RGB value. + /// + /// + /// The following will create a color that has a value of + /// #607D8B. + /// + /// Color darkGrey = new Color(96, 125, 139); + /// + /// + /// 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 following will create a color that has a value of + /// #607c8c. + /// + /// Color darkGrey = new Color(0.38f, 0.49f, 0.55f); + /// + /// + /// 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) | @@ -118,6 +197,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..dcfc89cc6 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -1,29 +1,89 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord { + /// + /// Represents a generic role object to be given to a guild user. + /// public interface IRole : ISnowflakeEntity, IDeletable, IMentionable, IComparable { - /// Gets the guild owning this role. + /// + /// Gets the guild that owns this role. + /// + /// + /// A guild representing the parent guild of this role. + /// IGuild Guild { get; } - /// Gets the color given to users of this role. + /// + /// Gets the color given to users of this role. + /// + /// + /// A struct representing the color of this role. + /// Color Color { get; } - /// Returns true if users of this role are separated in the user list. + /// + /// Gets a value that indicates whether the role can be separated in the user list. + /// + /// + /// true if users of this role are separated in the user list; otherwise false. + /// bool IsHoisted { get; } - /// Returns true if this role is automatically managed by Discord. + /// + /// Gets a value that indicates whether the role is managed by Discord. + /// + /// + /// true if this role is automatically managed by Discord; otherwise false. + /// bool IsManaged { get; } - /// Returns true if this role may be mentioned in messages. + /// + /// Gets a value that indicates whether the role is mentionable. + /// + /// + /// true if this role may be mentioned in messages; otherwise false. + /// bool IsMentionable { get; } - /// Gets the name of this role. + /// + /// Gets the name of this role. + /// + /// + /// A string containing 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. + /// + /// + /// A struct that this role possesses. + /// 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. + /// + /// + /// An representing the position of the role in the role list of the guild. + /// int Position { get; } - ///// Modifies this role. + /// + /// Modifies this role. + /// + /// + /// + /// await role.ModifyAsync(x => + /// { + /// x.Name = "Sonic"; + /// x.Color = new Color(0x1A50BC); + /// x.Mentionable = true; + /// }); + /// + /// + /// A delegate containing the properties to modify the role with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// 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..0074c0a3b 100644 --- a/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs +++ b/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs @@ -1,12 +1,30 @@ -namespace Discord +namespace Discord { + /// + /// Properties that are used to reorder an . + /// public class ReorderRoleProperties { - /// The id of the role to be edited + /// + /// Gets the identifier of the role to be edited. + /// + /// + /// A representing the snowflake identifier of the role to be modified. + /// public ulong Id { get; } - /// The new zero-based position of the role. + /// + /// Gets the new zero-based position of the role. + /// + /// + /// An representing 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..df23cf7b1 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. + /// This value may not be set if the role is an @everyone role. /// 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. + /// This value may not be set if the role is an @everyone role. /// 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. + /// This value may not be set if the role is an @everyone role. /// 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. + /// This value may not be set if the role is an @everyone role. /// 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. + /// This value may not be set if the role is an @everyone role. /// 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..bea26e656 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/IConnection.cs b/src/Discord.Net.Core/Entities/Users/IConnection.cs index cc981ccf0..1e65d971f 100644 --- a/src/Discord.Net.Core/Entities/Users/IConnection.cs +++ b/src/Discord.Net.Core/Entities/Users/IConnection.cs @@ -1,14 +1,27 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Discord { public interface IConnection { + /// Gets the ID of the connection account. + /// A representing the unique identifier value of this connection. string Id { get; } + /// Gets the service of the connection (twitch, youtube). + /// A string containing the name of this type of connection. string Type { get; } + /// Gets the username of the connection account. + /// A string containing the name of this connection. string Name { get; } + /// Gets whether the connection is revoked. + /// A value which if true indicates that this connection has been revoked, otherwise false. bool IsRevoked { get; } + /// Gets a of integration IDs. + /// + /// An containing + /// representations of unique identifier values of integrations. + /// IReadOnlyCollection IntegrationIds { get; } } } 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..633056733 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -1,41 +1,126 @@ -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 the guild. + /// + /// + /// A representing the time of which the user has joined the guild; + /// null when it cannot be obtained. + /// DateTimeOffset? JoinedAt { get; } - /// Gets the nickname for this user. + /// + /// Gets the nickname for this user. + /// + /// + /// A string representing the nickname of the user; null if none is set. + /// string Nickname { get; } - /// Gets the guild-level permissions for this user. + /// + /// Gets the guild-level permissions for this user. + /// + /// + /// A structure for this user, representing what + /// permissions this user has in the guild. + /// GuildPermissions GuildPermissions { get; } - /// Gets the guild for this user. + /// + /// Gets the guild for this user. + /// + /// + /// A guild object that this user belongs to. + /// IGuild Guild { get; } - /// Gets the id of the guild for this user. + /// + /// Gets the ID of the guild for this user. + /// + /// + /// An representing the snowflake identifier of the guild that this user belongs to. + /// 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. + /// + /// Gets a collection of IDs for the roles that this user currently possesses in the guild. + /// + /// + /// A read-only collection of , each representing a snowflake identifier for a role that + /// this user posesses. + /// 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. + /// + /// A structure representing the permissions that a user has in the + /// specified channel. + /// 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. + /// + /// A task that represents the asynchronous kick operation. + /// 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 delegate containing the properties to modify the user with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + /// Task ModifyAsync(Action func, RequestOptions options = null); - /// Adds a role to this user in this guild. + /// + /// Adds the specified to this user in this guild. + /// + /// The role to be added to the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role addition operation. + /// Task AddRoleAsync(IRole role, RequestOptions options = null); - /// Adds roles to this user in this guild. + /// + /// Adds the specified to this user in this guild. + /// + /// The roles to be added to the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role addition operation. + /// Task AddRolesAsync(IEnumerable roles, RequestOptions options = null); - /// Removes a role from this user in this guild. + /// + /// Removes the specified from this user in this guild. + /// + /// The role to be removed from the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role removal operation. + /// Task RemoveRoleAsync(IRole role, RequestOptions options = null); - /// Removes roles from this user in this guild. + /// + /// Removes the specified from this user in this guild. + /// + /// The roles to be removed from the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role removal operation. + /// 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..b300115f8 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 the user's presence status. This may include their online status and their activity. + /// 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..c1d7874eb 100644 --- a/src/Discord.Net.Core/Entities/Users/ISelfUser.cs +++ b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs @@ -3,15 +3,33 @@ 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. + /// + /// Indicates whether or not this user has their email verified. + /// + /// + /// true if this user's email has been verified; false if not. + /// bool IsVerified { get; } - /// Returns true if this user has enabled MFA on their account. + /// + /// Indicates whether or not this user has MFA enabled on their account. + /// + /// + /// true if this user has enabled multi-factor authentication on their account; false if not. + /// 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..ca9f092e0 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -2,26 +2,62 @@ 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 a value that indicates whether this user is a bot user. + /// + /// + /// true if the user is a bot; otherwise false. + /// bool IsBot { get; } - /// Returns true if this user is a webhook user. + /// + /// Gets a value that indicates whether this user is a webhook user. + /// + /// + /// true if the user is a webhook; otherwise false. + /// 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. + /// + /// A task that represents the asynchronous operation for getting or creating a 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..e5ee4d6aa 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 81cd10b49..6ebdbac6e 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)).OfType(); + /// 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)).OfType(); + /// 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..2d72dc985 100644 --- a/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs +++ b/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs @@ -2,26 +2,37 @@ 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. + /// The argument value is not between 0 to 255. 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. + /// The argument value is not between 0 to 1. 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/MessageExtensions.cs b/src/Discord.Net.Core/Extensions/MessageExtensions.cs index c53ef9053..90185cb6d 100644 --- a/src/Discord.Net.Core/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Core/Extensions/MessageExtensions.cs @@ -1,7 +1,17 @@ namespace Discord { + /// + /// Provides extension methods for . + /// public static class MessageExtensions { + /// + /// Gets a URL that jumps to the message. + /// + /// The message to jump to. + /// + /// A string that contains a URL for jumping to the message in chat. + /// public static string GetJumpUrl(this IMessage msg) { var channel = msg.Channel; diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index 951e8ca4b..f98bf7227 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -1,13 +1,36 @@ +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. /// + /// + /// This method attempts to send a direct-message to the user. + /// + /// + /// Please note that this method will throw an + /// if the user cannot receive DMs due to privacy reasons or if the user has the sender blocked. + /// + /// + /// You may want to consider catching for + /// 50007 when using this method. + /// + /// + /// + /// The user to send the DM to. + /// 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. + /// + /// A task that represents the asynchronous send operation. The task result contains the sent message. + /// public static async Task SendMessageAsync(this IUser user, string text = null, bool isTTS = false, @@ -18,8 +41,45 @@ namespace Discord } /// - /// Sends a file to the user via DM. + /// Sends a file to this message channel with an optional caption. /// + /// + /// The following example uploads a streamed image that will be called b1nzy.jpg embedded inside a + /// rich embed to the channel. + /// + /// await channel.SendFileAsync(b1nzyStream, "b1nzy.jpg", + /// embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); + /// + /// + /// + /// This method attempts to send an attachment as a direct-message to the user. + /// + /// + /// Please note that this method will throw an + /// if the user cannot receive DMs due to privacy reasons or if the user has the sender blocked. + /// + /// + /// You may want to consider catching for + /// 50007 when using this method. + /// + /// + /// + /// 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 + /// . See the example section for its usage. + /// + /// + /// The user to send the DM to. + /// 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. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// public static async Task SendFileAsync(this IUser user, Stream stream, string filename, @@ -33,8 +93,50 @@ namespace Discord } /// - /// Sends a file to the user via DM. + /// Sends a file via DM with an optional caption. /// + /// + /// The following example uploads a local file called wumpus.txt along with the text + /// good discord boi to the channel. + /// + /// await channel.SendFileAsync("wumpus.txt", "good discord boi"); + /// + /// + /// The following example uploads a local image called b1nzy.jpg embedded inside a rich embed to the + /// channel. + /// + /// await channel.SendFileAsync("b1nzy.jpg", + /// embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); + /// + /// + /// + /// This method attempts to send an attachment as a direct-message to the user. + /// + /// + /// Please note that this method will throw an + /// if the user cannot receive DMs due to privacy reasons or if the user has the sender blocked. + /// + /// + /// You may want to consider catching for + /// 50007 when using this method. + /// + /// + /// + /// 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 + /// . See the example section for its usage. + /// + /// + /// The user to send the DM to. + /// 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. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// public static async Task SendFileAsync(this IUser user, string filePath, string text = null, @@ -45,6 +147,17 @@ namespace Discord return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); } + /// + /// Bans the user from the 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. + /// + /// A task that represents the asynchronous operation for banning a user. + /// 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 414e00a29..0b16daeb1 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -1,5 +1,6 @@ namespace Discord { + /// A helper class for formatting characters. public static class Format { // Characters which need escaping diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index a383c37da..d6a863556 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -5,38 +5,274 @@ 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; } + /// + /// Starts the connection between Discord and the client.. + /// + /// + /// This method will initialize the connection between the client and Discord. + /// + /// This method will immediately return after it is called, as it will initialize the connection on + /// another thread. + /// + /// + /// + /// A task that represents the asynchronous start operation. + /// Task StartAsync(); + /// + /// Stops the connection between Discord and the client. + /// + /// + /// A task that represents the asynchronous stop operation. + /// 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. + /// + /// A task that represents the asynchronous get operation. The task result contains the application + /// information. + /// Task GetApplicationInfoAsync(RequestOptions options = null); + /// + /// Gets a generic channel. + /// + /// + /// + /// var channel = await _client.GetChannelAsync(381889909113225237); + /// if (channel != null && channel is IMessageChannel msgChannel) + /// { + /// await msgChannel.SendMessageAsync($"{msgChannel} is created at {msgChannel.CreatedAt}"); + /// } + /// + /// + /// The snowflake identifier of the channel (e.g. `381889909113225237`). + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the channel associated + /// with the snowflake identifier; null when the channel cannot be found. + /// Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a collection of private channels opened in this session. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// This method will retrieve all private channels (including direct-message, group channel and such) that + /// are currently opened in this session. + /// + /// This method will not return previously opened private channels outside of the current session! If + /// you have just started the client, this may return an empty collection. + /// + /// + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of private channels that the user currently partakes in. + /// Task> GetPrivateChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a collection of direct message channels opened in this session. + /// + /// + /// 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. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of direct-message channels that the user currently partakes in. + /// Task> GetDMChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a collection of group channels opened in this session. + /// + /// + /// This method returns a collection of currently opened group channels. + /// + /// This method will not return previously opened group 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. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of group channels that the user currently partakes in. + /// Task> GetGroupChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets the connections that the user has set up. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of connections. + /// Task> GetConnectionsAsync(RequestOptions options = null); + /// + /// Gets a guild. + /// + /// The guild snowflake identifier. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the guild associated + /// with the snowflake identifier; null when the guild cannot be found. + /// Task GetGuildAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a collection of guilds that the user is currently in. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of guilds that the current user is in. + /// Task> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created guild. + /// Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null); - + + /// + /// Gets an invite. + /// + /// The invitation identifier. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the invite information. + /// Task GetInviteAsync(string inviteId, RequestOptions options = null); + /// + /// Gets a user. + /// + /// + /// + /// var user = await _client.GetUserAsync(168693960628371456); + /// if (user != null) + /// Console.WriteLine($"{user} is created at {user.CreatedAt}."; + /// + /// + /// The snowflake identifier of the user (e.g. `168693960628371456`). + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the user associated with + /// the snowflake identifier; null if the user is not found. + /// Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a user. + /// + /// + /// + /// var user = await _client.GetUserAsync("Still", "2876"); + /// if (user != null) + /// Console.WriteLine($"{user} is created at {user.CreatedAt}."; + /// + /// + /// The name of the user (e.g. `Still`). + /// The discriminator value of the user (e.g. `2876`). + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the user associated with + /// the name and the discriminator; null if the user is not found. + /// Task GetUserAsync(string username, string discriminator, RequestOptions options = null); + /// + /// Gets a collection of the available voice regions. + /// + /// + /// The following example gets the most optimal voice region from the collection. + /// + /// var regions = await client.GetVoiceRegionsAsync(); + /// var optimalRegion = regions.FirstOrDefault(x => x.IsOptimal); + /// + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// with all of the available voice regions in this session. + /// Task> GetVoiceRegionsAsync(RequestOptions options = null); + /// + /// Gets a voice region. + /// + /// The identifier of the voice region (e.g. eu-central ). + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the voice region + /// associated with the identifier; null if the voice region is not found. + /// Task GetVoiceRegionAsync(string id, RequestOptions options = null); + /// + /// Gets a webhook available. + /// + /// The identifier of the webhook. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a webhook associated + /// with the identifier; null if the webhook is not found. + /// Task GetWebhookAsync(ulong id, RequestOptions options = null); + /// + /// Gets the recommended shard count as suggested by Discord. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains an + /// that represents the number of shards that should be used with this account. + /// Task GetRecommendedShardCountAsync(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..715cac677 100644 --- a/src/Discord.Net.Core/Logging/LogMessage.cs +++ b/src/Discord.Net.Core/Logging/LogMessage.cs @@ -1,15 +1,50 @@ -using System; +using System; using System.Text; namespace Discord { + /// + /// Provides a message object used for logging purposes. + /// public struct LogMessage { + /// + /// Gets the severity of the log entry. + /// + /// + /// A enum to indicate the severeness of the incident or event. + /// public LogSeverity Severity { get; } + /// + /// Gets the source of the log entry. + /// + /// + /// A string representing the source of the log entry. + /// public string Source { get; } + /// + /// Gets the message of this log entry. + /// + /// + /// A string containing the message of this log entry. + /// public string Message { get; } + /// + /// Gets the exception of this log entry. + /// + /// + /// An object associated with an incident; otherwise null. + /// 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 +52,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..d36bd66f9 100644 --- a/src/Discord.Net.Core/Net/HttpException.cs +++ b/src/Discord.Net.Core/Net/HttpException.cs @@ -3,13 +3,45 @@ using System.Net; namespace Discord.Net { + /// + /// The exception that is thrown if an error occurs while processing an Discord HTTP request. + /// 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. + /// + /// The HTTP status code returned. + /// The request that was sent prior to the exception. + /// The Discord status code returned. + /// The reason behind the exception. 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..c19487fad 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 { + /// + /// The exception that is thrown when 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/Rest/IRestClient.cs b/src/Discord.Net.Core/Net/Rest/IRestClient.cs index addfa9061..2e30d2ef4 100644 --- a/src/Discord.Net.Core/Net/Rest/IRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/IRestClient.cs @@ -4,11 +4,32 @@ using System.Threading.Tasks; namespace Discord.Net.Rest { + /// + /// Represents a generic REST-based client. + /// public interface IRestClient { + /// + /// Sets the HTTP header of this client for all requests. + /// + /// The field name of the header. + /// The value of the header. void SetHeader(string key, string value); + /// + /// Sets the cancellation token for this client. + /// + /// The cancellation token. void SetCancelToken(CancellationToken cancelToken); + /// + /// Sends a REST request. + /// + /// The method used to send this request (i.e. HTTP verb such as GET, POST). + /// The endpoint to send this request to. + /// The cancellation token used to cancel the task. + /// Indicates whether to send the header only. + /// The audit log reason. + /// Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly = false, string reason = null); Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly = false, string reason = null); Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly = false, string reason = null); diff --git a/src/Discord.Net.Core/Net/WebSocketClosedException.cs b/src/Discord.Net.Core/Net/WebSocketClosedException.cs index d647b6c8c..6e2564f6e 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 { + /// + /// The exception that is thrown when the WebSocket session is closed by Discord. + /// 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 40f0ebaa4..3af3ded6f 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -2,21 +2,47 @@ 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 sets the maximum time to wait for for this request to complete. /// + /// + /// Gets or set the max time, in milliseconds, to wait for 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. + /// + /// + /// A in milliseconds for when the request times out. + /// public int? Timeout { get; set; } + /// + /// Gets or sets the cancellation token for this request. + /// + /// + /// A for this request. + /// 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; } @@ -32,11 +58,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/RetryMode.cs b/src/Discord.Net.Core/RetryMode.cs index 65ae75fc3..1e09f4dd1 100644 --- a/src/Discord.Net.Core/RetryMode.cs +++ b/src/Discord.Net.Core/RetryMode.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord { @@ -12,7 +12,7 @@ namespace Discord RetryTimeouts = 0x1, // /// Retry if a request failed due to a network error. //RetryErrors = 0x2, - /// Retry if a request failed due to a ratelimit. + /// Retry if a request failed due to a rate-limit. RetryRatelimit = 0x4, /// Retry if a request failed due to an HTTP error 502. Retry502 = 0x8, 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..1857ae7a0 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,28 @@ 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. + /// + /// A task that represents the asynchronous download operation. The task result contains 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(); + /// + /// A task that represents the asynchronous operation that attempts to get the message via cache or to + /// download the message. The task result contains the downloaded entity. + /// + 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 e9edfb772..308f08460 100644 --- a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs +++ b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs @@ -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(paramName: nameof(collection)); InitializeFromCollection(collection); } + /// + /// or is null + /// public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer comparer) : this(concurrencyLevel, DefaultCapacity, false, comparer) { @@ -206,7 +210,7 @@ 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(paramName: "key"); @@ -230,6 +234,7 @@ namespace Discord return false; } + /// is null public bool TryAdd(T value) { if (value == null) throw new ArgumentNullException(paramName: "key"); @@ -279,6 +284,7 @@ namespace Discord } } + /// is null public bool TryRemove(T value) { if (value == null) throw new ArgumentNullException(paramName: "key"); diff --git a/src/Discord.Net.Core/Utils/DateTimeUtils.cs b/src/Discord.Net.Core/Utils/DateTimeUtils.cs index e2a8faa75..608476889 100644 --- a/src/Discord.Net.Core/Utils/DateTimeUtils.cs +++ b/src/Discord.Net.Core/Utils/DateTimeUtils.cs @@ -2,13 +2,12 @@ using System; namespace Discord { - //Source: https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/DateTimeOffset.cs + /// internal static class DateTimeUtils { public static DateTimeOffset FromTicks(long ticks) => new DateTimeOffset(ticks, TimeSpan.Zero); public static DateTimeOffset? FromTicks(long? ticks) => ticks != null ? new DateTimeOffset(ticks.Value, TimeSpan.Zero) : (DateTimeOffset?)null; - } } diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index a081b5a5a..6ffb7eee6 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -4,26 +4,52 @@ using System.Text; namespace Discord { + /// + /// Provides a series of helper methods for parsing mentions. + /// 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(message: "Invalid mention format", paramName: nameof(text)); + throw new ArgumentException(message: "Invalid mention format.", paramName: 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(message: "Invalid mention format", paramName: nameof(text)); + throw new ArgumentException(message: "Invalid mention format.", paramName: 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(message: "Invalid mention format", paramName: nameof(text)); + throw new ArgumentException(message: "Invalid mention format.", paramName: 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] == '>') 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/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs index 6e7125ab7..fd0fe091a 100644 --- a/src/Discord.Net.Core/Utils/Permissions.cs +++ b/src/Discord.Net.Core/Utils/Permissions.cs @@ -165,7 +165,7 @@ namespace Discord resolvedPermissions &= ~(ulong)ChannelPermission.AttachFiles; } } - resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) + resolvedPermissions &= mask; //Ensure we didn't get any permissions this channel doesn't support (from guildPerms, for example) } return resolvedPermissions; diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 6cbf4a173..d04d3be50 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -165,6 +165,7 @@ namespace Discord => new ArgumentException(message: msg ?? $"Value must be less than {value}", paramName: 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))); @@ -175,6 +176,7 @@ 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++) diff --git a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs index eecebfb24..dd8f8ca66 100644 --- a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs +++ b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs @@ -2,10 +2,27 @@ using System; namespace Discord { + /// + /// Provides a series of helper methods for handling snowflake identifiers. + /// public static class SnowflakeUtils { + /// + /// Resolves the time of which the snowflake is generated. + /// + /// The snowflake identifier to resolve. + /// + /// A representing the time for when the object is geenrated. + /// public static DateTimeOffset FromSnowflake(ulong value) => DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); + /// + /// Generates a pseudo-snowflake identifier with a . + /// + /// The time to be used in the new snowflake. + /// + /// A representing the newly generated snowflake identifier. + /// public static ulong ToSnowflake(DateTimeOffset value) => ((ulong)value.ToUnixTimeMilliseconds() - 1420070400000UL) << 22; } diff --git a/src/Discord.Net.Core/Utils/TokenUtils.cs b/src/Discord.Net.Core/Utils/TokenUtils.cs index 6decdc52f..bfc915252 100644 --- a/src/Discord.Net.Core/Utils/TokenUtils.cs +++ b/src/Discord.Net.Core/Utils/TokenUtils.cs @@ -1,11 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { + /// + /// Provides a series of helper methods for handling Discord login tokens. + /// public static class TokenUtils { /// @@ -13,8 +12,8 @@ namespace Discord /// /// The type of token to validate. /// The token value to validate. - /// Thrown when the supplied token string is null, empty, or contains only whitespace. - /// Thrown when the supplied TokenType or token value is invalid. + /// Thrown when the supplied token string is null, empty, or contains only whitespace. + /// Thrown when the supplied or token value is invalid. public static void ValidateToken(TokenType tokenType, string token) { // A Null or WhiteSpace token of any type is invalid. diff --git a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs index 1894a8906..afc01f87a 100644 --- a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs +++ b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs @@ -1,4 +1,4 @@ -using Discord.Net.WebSockets; +using Discord.Net.WebSockets; using System; using System.Collections.Generic; using System.Linq; @@ -151,7 +151,7 @@ namespace Discord.Net.Providers.WS4Net } private void OnBinaryMessage(object sender, DataReceivedEventArgs e) { - BinaryMessage(e.Data, 0, e.Data.Count()).GetAwaiter().GetResult(); + BinaryMessage(e.Data, 0, e.Data.Length).GetAwaiter().GetResult(); } private void OnConnected(object sender, object e) { diff --git a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs index 4381a9da3..d7f3ae68d 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs @@ -1,4 +1,3 @@ -using System; using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs index 3dd7020d9..cd08e7e26 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs @@ -1,4 +1,3 @@ -using System; using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/EmbedImage.cs b/src/Discord.Net.Rest/API/Common/EmbedImage.cs index c6b3562a3..e650d99f4 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedImage.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedImage.cs @@ -1,5 +1,4 @@ -#pragma warning disable CS1591 -using System; +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs index 1658eda1a..e01261483 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs @@ -1,5 +1,4 @@ -#pragma warning disable CS1591 -using System; +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs index 993beb72b..9c87ca46b 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs @@ -1,5 +1,4 @@ -#pragma warning disable CS1591 -using System; +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs index 610cf58a8..3a034d244 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs @@ -1,5 +1,4 @@ -#pragma warning disable CS1591 -using System; +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs index 9e909b50c..ae15aa5df 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs @@ -3,7 +3,6 @@ using Discord.Net.Converters; using Discord.Net.Rest; using Newtonsoft.Json; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Text; diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 8a3db3e6a..b584f5764 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -24,11 +24,18 @@ 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; } + /// 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; @@ -49,7 +56,6 @@ 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); @@ -84,7 +90,7 @@ namespace Discord.Rest catch (ArgumentException ex) { // log these ArgumentExceptions and allow for the client to attempt to log in anyways - await LogManager.WarningAsync("Discord", "A supplied token was invalid", ex).ConfigureAwait(false); + await LogManager.WarningAsync("Discord", "A supplied token was invalid.", ex).ConfigureAwait(false); } } @@ -102,8 +108,7 @@ namespace Discord.Rest } internal virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.Delay(0); - - /// + public async Task LogoutAsync() { await _stateLock.WaitAsync().ConfigureAwait(false); @@ -145,49 +150,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, 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 aa99fe005..a8f6b58ef 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); @@ -151,7 +153,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 1679579b2..36406bfea 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -47,6 +47,7 @@ namespace Discord.API internal JsonSerializer Serializer => _serializer; + /// Unknown OAuth token type. public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) { @@ -60,6 +61,8 @@ namespace Discord.API SetBaseUrl(DiscordConfig.APIUrl); } + + /// Unknown OAuth token type. internal void SetBaseUrl(string baseUrl) { RestClient = _restClientProvider(baseUrl); @@ -67,6 +70,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) @@ -78,7 +82,7 @@ namespace Discord.API case TokenType.Bearer: return $"Bearer {token}"; default: - throw new ArgumentException(message: "Unknown OAuth token type", paramName: nameof(tokenType)); + throw new ArgumentException(message: "Unknown OAuth token type.", paramName: nameof(tokenType)); } } internal virtual void Dispose(bool disposing) @@ -166,7 +170,7 @@ namespace Discord.API //Core internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, funcName), clientBucket, options); public async Task SendAsync(string method, string endpoint, string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { @@ -180,7 +184,7 @@ namespace Discord.API internal Task SendJsonAsync(string method, Expression> endpointExpr, object payload, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, funcName), clientBucket, options); public async Task SendJsonAsync(string method, string endpoint, object payload, string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { @@ -195,7 +199,7 @@ namespace Discord.API internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, funcName), clientBucket, options); public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { @@ -209,7 +213,7 @@ namespace Discord.API internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class - => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, funcName), clientBucket, options); public async Task SendAsync(string method, string endpoint, string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class { @@ -222,7 +226,7 @@ namespace Discord.API internal Task SendJsonAsync(string method, Expression> endpointExpr, object payload, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class - => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, funcName), clientBucket, options); public async Task SendJsonAsync(string method, string endpoint, object payload, string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class { @@ -236,8 +240,8 @@ namespace Discord.API internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); - public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, + => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, funcName), clientBucket, options); + public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { options = options ?? new RequestOptions(); @@ -405,7 +409,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) { @@ -416,7 +420,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 @@ -467,6 +471,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)); @@ -481,6 +486,8 @@ namespace Discord.API 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) @@ -497,6 +504,7 @@ namespace Discord.API 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)); @@ -511,6 +519,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) @@ -563,6 +574,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)); @@ -1098,7 +1110,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) @@ -1110,7 +1122,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) @@ -1121,7 +1133,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) @@ -1131,7 +1143,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 @@ -1251,7 +1263,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) { @@ -1307,6 +1319,7 @@ namespace Discord.API } //Helpers + /// Client is not logged in. protected void CheckState() { if (LoginState != LoginState.LoggedIn) @@ -1357,7 +1370,7 @@ namespace Discord.API { return endpointExpr.Compile()(); } - private static string GetBucketId(BucketIds ids, Expression> endpointExpr, TokenType tokenType, string callingMethod) + private static string GetBucketId(BucketIds ids, Expression> endpointExpr, string callingMethod) { return _bucketIdGenerators.GetOrAdd(callingMethod, x => CreateBucketId(endpointExpr))(ids); } @@ -1419,7 +1432,7 @@ namespace Discord.API } catch (Exception ex) { - throw new InvalidOperationException("Failed to generate the bucket id for this operation", ex); + throw new InvalidOperationException("Failed to generate the bucket id for this operation.", ex); } } diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index d4dee9f0a..e36353855 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -5,13 +5,24 @@ using System.Threading.Tasks; namespace Discord.Rest { + /// + /// Provides a client to send REST-based requests to Discord. + /// public class DiscordRestClient : BaseDiscordClient, IDiscordClient { private RestApplication _applicationInfo; + /// + /// Gets the logged-in user. + /// public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; + /// public DiscordRestClient() : this(new DiscordRestConfig()) { } + /// + /// Initializes a new with the provided configuration. + /// + /// The configuration to be used with the client. public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { } private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) @@ -22,83 +33,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, RequestOptions options = null) => ClientHelper.GetInviteAsync(this, inviteId, 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 +105,7 @@ namespace Discord.Rest else return null; } + /// async Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -113,6 +113,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -120,6 +121,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -128,12 +130,14 @@ namespace Discord.Rest return ImmutableArray.Create(); } + /// async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync(options).ConfigureAwait(false); async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) => await GetInviteAsync(inviteId, options).ConfigureAwait(false); + /// async Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -141,6 +145,7 @@ namespace Discord.Rest else return null; } + /// async Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -148,9 +153,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 +166,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..7bf7440ce 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..fc807cac0 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 { + /// + /// Contains 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 user 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 51e72c414..51a19a0de 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 { + /// + /// Contains 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) @@ -42,9 +44,34 @@ namespace Discord.Rest return new ChannelCreateAuditLogData(entry.TargetId.Value, name, type, overwrites.ToReadOnlyCollection()); } + /// + /// Gets the snowflake ID of the created channel. + /// + /// + /// A 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 , containing the permission overwrites that were + /// assigned to the created channel. + /// 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 7af5ca10c..d09b658cf 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 { + /// + /// Contains 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. + /// + /// + /// A 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..6382e8250 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 bit-rate of this channel if applicable. + /// + /// + /// An representing the bit-rate 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 36fe82084..f37404906 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 { + /// + /// Contains a piece of audit log data related to a channel update. + /// public class ChannelUpdateAuditLogData : IAuditLogData { private ChannelUpdateAuditLogData(ulong id, ChannelInfo before, ChannelInfo after) @@ -38,8 +41,26 @@ namespace Discord.Rest return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after); } + /// + /// Gets the snowflake ID of the updated channel. + /// + /// + /// A representing the snowflake identifier for the updated channel. + /// public ulong ChannelId { get; } + /// + /// Gets the channel information before the changes. + /// + /// + /// An information object containing the original channel information before the changes were made. + /// public ChannelInfo Before { get; } + /// + /// Gets the channel information after the changes. + /// + /// + /// An information object containing the channel information after the changes were made. + /// 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 dac2d90ef..92e92574f 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 { + /// + /// Contains 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. + /// + /// + /// A 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 73cb31af9..fd307d5a9 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 { + /// + /// Contains 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. + /// + /// + /// A 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 84898013d..96e791d81 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 { + /// + /// Contains 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. + /// + /// + /// A 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..df63251d8 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. + /// + /// + /// A 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 user 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 09c1eda18..16f027a7d 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 { + /// + /// Contains 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 1d7f48e93..215a3c164 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 { + /// + /// Contains 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,56 @@ 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; } + /// + /// Gets a value that determines whether the invite is a temporary one. + /// + /// + /// 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 user that created this invite. + /// public IUser Creator { get; } + /// + /// Gets the ID of the channel this invite is linked to. + /// + /// + /// A 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 was 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 091285532..5e49bb641 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 { + /// + /// Contains 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,56 @@ 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; } + /// + /// Gets a value that indicates whether the invite is a temporary one. + /// + /// + /// 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 user that created this invite. + /// public IUser Creator { get; } + /// + /// Gets the ID of the channel this invite is linked to. + /// + /// + /// A 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..aaad362da 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,44 @@ 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; } + /// + /// Gets a value that indicates whether the invite is a temporary one. + /// + /// + /// 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. + /// + /// + /// A 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/InviteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs index 35088be98..95bfb845a 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs @@ -5,6 +5,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data relating to an invite update. + /// public class InviteUpdateAuditLogData : IAuditLogData { private InviteUpdateAuditLogData(InviteInfo before, InviteInfo after) @@ -40,7 +43,19 @@ namespace Discord.Rest return new InviteUpdateAuditLogData(before, after); } + /// + /// Gets the invite information before the changes. + /// + /// + /// An information object containing the original invite information before the changes were made. + /// public InviteInfo Before { get; } + /// + /// Gets the invite information after the changes. + /// + /// + /// An information object containing the invite information after the changes were made. + /// public InviteInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs index 41b5526b8..dceb73d0a 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs @@ -5,6 +5,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to a kick. + /// public class KickAuditLogData : IAuditLogData { private KickAuditLogData(RestUser user) @@ -18,6 +21,12 @@ namespace Discord.Rest return new KickAuditLogData(RestUser.Create(discord, userInfo)); } + /// + /// Gets the user that was kicked. + /// + /// + /// A user object representing the kicked user. + /// public IUser Target { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs index 48adb1833..763c90c68 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; @@ -7,6 +6,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to a change in a guild member's roles. + /// public class MemberRoleAuditLogData : IAuditLogData { private MemberRoleAuditLogData(IReadOnlyCollection roles, IUser target) @@ -30,7 +32,20 @@ namespace Discord.Rest return new MemberRoleAuditLogData(roleInfos.ToReadOnlyCollection(), user); } + /// + /// Gets a collection of role changes that were performed on the member. + /// + /// + /// A read-only collection of , containing the roles that were changed on + /// the member. + /// public IReadOnlyCollection Roles { get; } + /// + /// Gets the user that the roles changes were performed on. + /// + /// + /// A user object representing the user that the role changes were performed on. + /// public IUser Target { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleEditInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleEditInfo.cs index 4838b75c9..b0abf2d95 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleEditInfo.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleEditInfo.cs @@ -1,5 +1,8 @@ namespace Discord.Rest { + /// + /// An information object representing a change in one of a guild member's roles. + /// public struct MemberRoleEditInfo { internal MemberRoleEditInfo(string name, ulong roleId, bool added) @@ -9,8 +12,26 @@ namespace Discord.Rest Added = added; } + /// + /// Gets the name of the role that was changed. + /// + /// + /// A string containing the name of the role that was changed. + /// public string Name { get; } + /// + /// Gets the ID of the role that was changed. + /// + /// + /// A representing the snowflake identifier of the role that was changed. + /// public ulong RoleId { get; } + /// + /// Gets a value that indicates whether the role was added to the user. + /// + /// + /// true if the role was added to the user; otherwise false. + /// public bool Added { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs index 96d34610e..238c79843 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs @@ -1,11 +1,13 @@ -using System.Linq; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; -using ChangeModel = Discord.API.AuditLogChange; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to a change in a guild member. + /// public class MemberUpdateAuditLogData : IAuditLogData { private MemberUpdateAuditLogData(IUser target, MemberInfo before, MemberInfo after) @@ -42,6 +44,12 @@ namespace Discord.Rest return new MemberUpdateAuditLogData(user, before, after); } + /// + /// Gets the user that the changes were performed on. + /// + /// + /// A user object representing the user who the changes were performed on. + /// public IUser Target { get; } public MemberInfo Before { get; } public MemberInfo After { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs index 3949cdd68..317d47648 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs @@ -3,6 +3,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to message deletion(s). + /// public class MessageDeleteAuditLogData : IAuditLogData { private MessageDeleteAuditLogData(ulong channelId, int count) @@ -16,7 +19,20 @@ namespace Discord.Rest return new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, entry.Options.MessageDeleteCount.Value); } + /// + /// Gets the number of messages that were deleted. + /// + /// + /// An representing the number of messages that were deleted from the channel. + /// public int MessageCount { get; } + /// + /// Gets the ID of the channel that the messages were deleted from. + /// + /// + /// A representing the snowflake identifier for the channel that the messages were + /// deleted from. + /// public ulong ChannelId { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs index b13f4b8fd..a9ccd4db3 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs @@ -5,6 +5,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data for a permissions overwrite creation. + /// public class OverwriteCreateAuditLogData : IAuditLogData { private OverwriteCreateAuditLogData(Overwrite overwrite) @@ -30,6 +33,12 @@ namespace Discord.Rest return new OverwriteCreateAuditLogData(new Overwrite(id, type, permissions)); } + /// + /// Gets the permission overwrite object that was created. + /// + /// + /// An object representing the overwrite that was created. + /// public Overwrite Overwrite { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs index 5d5177d9a..1e51fa5e3 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs @@ -1,16 +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; -using ChangeModel = Discord.API.AuditLogChange; -using OptionModel = Discord.API.AuditLogOptions; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to the deletion of a permission overwrite. + /// public class OverwriteDeleteAuditLogData : IAuditLogData { private OverwriteDeleteAuditLogData(Overwrite deletedOverwrite) @@ -35,6 +32,12 @@ namespace Discord.Rest return new OverwriteDeleteAuditLogData(new Overwrite(id, type, new OverwritePermissions(allow, deny))); } + /// + /// Gets the permission overwrite object that was deleted. + /// + /// + /// An object representing the overwrite that was deleted. + /// public Overwrite Overwrite { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs index d05e1feff..ac67c85cf 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs @@ -5,6 +5,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to the update of a permission overwrite. + /// public class OverwriteUpdateAuditLogData : IAuditLogData { private OverwriteUpdateAuditLogData(OverwritePermissions before, OverwritePermissions after, ulong targetId, PermissionTarget targetType) @@ -35,10 +38,35 @@ namespace Discord.Rest return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, type); } + /// + /// Gets the overwrite permissions before the changes. + /// + /// + /// An overwrite permissions object representing the overwrite permissions that the overwrite had before + /// the changes were made. + /// public OverwritePermissions OldPermissions { get; } + /// + /// Gets the overwrite permissions after the changes. + /// + /// + /// An overwrite permissions object representing the overwrite permissions that the overwrite had after the + /// changes. + /// public OverwritePermissions NewPermissions { get; } - + /// + /// Gets the ID of the overwrite that was updated. + /// + /// + /// A representing the snowflake identifier of the overwrite that was updated. + /// public ulong OverwriteTargetId { get; } + /// + /// Gets the target of the updated permission overwrite. + /// + /// + /// The target of the updated permission overwrite. + /// public PermissionTarget OverwriteType { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs index 0005e304d..c32d12b3f 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs @@ -3,6 +3,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to a guild prune. + /// public class PruneAuditLogData : IAuditLogData { private PruneAuditLogData(int pruneDays, int membersRemoved) @@ -16,7 +19,22 @@ namespace Discord.Rest return new PruneAuditLogData(entry.Options.PruneDeleteMemberDays.Value, entry.Options.PruneMembersRemoved.Value); } + /// + /// Gets the threshold for a guild member to not be kicked. + /// + /// + /// An representing the amount of days that a member must have been seen in the server, + /// to avoid being kicked. (i.e. If a user has not been seen for more than , they will be + /// kicked from the server) + /// public int PruneDays { get; } + /// + /// Gets the number of members that were kicked during the purge. + /// + /// + /// An representing the number of members that were removed from this guild for having + /// not been seen within . + /// public int MembersRemoved { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs index 69e72fdb0..cee255fb0 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs @@ -5,6 +5,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to a role creation. + /// public class RoleCreateAuditLogData : IAuditLogData { private RoleCreateAuditLogData(ulong id, RoleEditInfo props) @@ -41,7 +44,19 @@ namespace Discord.Rest new RoleEditInfo(color, mentionable, hoist, name, permissions)); } + /// + /// Gets the ID of the role that was created. + /// + /// + /// A representing the snowflake identifier to the role that was created. + /// public ulong RoleId { get; } + /// + /// Gets the role information that was created. + /// + /// + /// An information object representing the properties of the role that was created. + /// public RoleEditInfo Properties { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs index f812567cb..78b5efc87 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs @@ -5,6 +5,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data relating to a role deletion. + /// public class RoleDeleteAuditLogData : IAuditLogData { private RoleDeleteAuditLogData(ulong id, RoleEditInfo props) @@ -41,7 +44,19 @@ namespace Discord.Rest new RoleEditInfo(color, mentionable, hoist, name, permissions)); } + /// + /// Gets the ID of the role that was deleted. + /// + /// + /// A representing the snowflake identifier to the role that was deleted. + /// public ulong RoleId { get; } + /// + /// Gets the role information that was deleted. + /// + /// + /// An information object representing the properties of the role that was deleted. + /// public RoleEditInfo Properties { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleEditInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleEditInfo.cs index 186ea8d11..6f3d8d387 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleEditInfo.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleEditInfo.cs @@ -1,5 +1,8 @@ namespace Discord.Rest { + /// + /// Represents information for a role edit. + /// public struct RoleEditInfo { internal RoleEditInfo(Color? color, bool? mentionable, bool? hoist, string name, @@ -12,10 +15,45 @@ namespace Discord.Rest Permissions = permissions; } + /// + /// Gets the color of this role. + /// + /// + /// A color object representing the color assigned to this role; null if this role does not have a + /// color. + /// public Color? Color { get; } + /// + /// Gets a value that indicates whether this role is mentionable. + /// + /// + /// true if other members can mention this role in a text channel; otherwise false; + /// null if this is not mentioned in this entry. + /// public bool? Mentionable { get; } + /// + /// Gets a value that indicates whether this role is hoisted (i.e. its members will appear in a separate + /// section on the user list). + /// + /// + /// true if this role's members will appear in a separate section in the user list; otherwise + /// false; null if this is not mentioned in this entry. + /// public bool? Hoist { get; } + /// + /// Gets the name of this role. + /// + /// + /// A string containing the name of this role. + /// public string Name { get; } + /// + /// Gets the permissions assigned to this role. + /// + /// + /// A guild permissions object representing the permissions that have been assigned to this role; null + /// if no permissions have been assigned. + /// public GuildPermissions? Permissions { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs index 5cea865f1..f98fc3e23 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs @@ -5,6 +5,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to a role update. + /// public class RoleUpdateAuditLogData : IAuditLogData { private RoleUpdateAuditLogData(ulong id, RoleEditInfo oldProps, RoleEditInfo newProps) @@ -55,8 +58,26 @@ namespace Discord.Rest return new RoleUpdateAuditLogData(entry.TargetId.Value, oldProps, newProps); } + /// + /// Gets the ID of the role that was changed. + /// + /// + /// A representing the snowflake identifier of the role that was changed. + /// public ulong RoleId { get; } + /// + /// Gets the role information before the changes. + /// + /// + /// A role information object containing the role information before the changes were made. + /// public RoleEditInfo Before { get; } + /// + /// Gets the role information after the changes. + /// + /// + /// A role information object containing the role information after the changes were made. + /// public RoleEditInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs index c94f18271..bc7e7fd4f 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs @@ -5,6 +5,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to an unban. + /// public class UnbanAuditLogData : IAuditLogData { private UnbanAuditLogData(IUser user) @@ -18,6 +21,12 @@ namespace Discord.Rest return new UnbanAuditLogData(RestUser.Create(discord, userInfo)); } + /// + /// Gets the user that was unbanned. + /// + /// + /// A user object representing the user that was unbanned. + /// public IUser Target { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs index 06932bfc4..21388f985 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs @@ -5,6 +5,9 @@ using EntryModel = Discord.API.AuditLogEntry; namespace Discord.Rest { + /// + /// Contains a piece of audit log data related to a webhook creation. + /// public class WebhookCreateAuditLogData : IAuditLogData { private WebhookCreateAuditLogData(IWebhook webhook, WebhookType type, string name, ulong channelId) @@ -33,12 +36,40 @@ namespace Discord.Rest return new WebhookCreateAuditLogData(webhook, type, name, channelId); } - //Corresponds to the *current* data + // Doc Note: Corresponds to the *current* data + + /// + /// Gets the webhook that was created. + /// + /// + /// A webhook object representing the webhook that was created. + /// public IWebhook Webhook { get; } - //Corresponds to the *audit log* data + // Doc Note: Corresponds to the *audit log* data + + /// + /// Gets the type of webhook that was created. + /// + /// + /// The type of webhook that was created. + /// public WebhookType Type { get; } + + /// + /// Gets the name of the webhook. + /// + /// + /// A string containing the name of the webhook. + /// public string Name { get; } + /// + /// Gets the ID of the channel that the webhook could send to. + /// + /// + /// A representing the snowflake identifier of the channel that the webhook could send + /// to. + /// public ulong ChannelId { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs index 8fc4da578..308020c95 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.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 { + /// + /// Contains a piece of audit log data related to a webhook deletion. + /// public class WebhookDeleteAuditLogData : IAuditLogData { private WebhookDeleteAuditLogData(ulong id, ulong channel, WebhookType type, string name, string avatar) @@ -37,10 +36,41 @@ namespace Discord.Rest return new WebhookDeleteAuditLogData(entry.TargetId.Value, channelId, type, name, avatarHash); } + /// + /// Gets the ID of the webhook that was deleted. + /// + /// + /// A representing the snowflake identifier of the webhook that was deleted. + /// public ulong WebhookId { get; } + /// + /// Gets the ID of the channel that the webhook could send to. + /// + /// + /// A representing the snowflake identifier of the channel that the webhook could send + /// to. + /// public ulong ChannelId { get; } + /// + /// Gets the type of the webhook that was deleted. + /// + /// + /// The type of webhook that was deleted. + /// public WebhookType Type { get; } + /// + /// Gets the name of the webhook that was deleted. + /// + /// + /// A string containing the name of the webhook that was deleted. + /// public string Name { get; } + /// + /// Gets the hash value of the webhook's avatar. + /// + /// + /// A string containing the hash of the webhook's avatar. + /// public string Avatar { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs index 26975cc7c..e60157b1a 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs @@ -1,5 +1,8 @@ namespace Discord.Rest { + /// + /// Represents information for a webhook. + /// public struct WebhookInfo { internal WebhookInfo(string name, ulong? channelId, string avatar) @@ -9,8 +12,27 @@ namespace Discord.Rest Avatar = avatar; } + /// + /// Gets the name of this webhook. + /// + /// + /// A string containing the name of this webhook. + /// public string Name { get; } + /// + /// Gets the ID of the channel that this webhook sends to. + /// + /// + /// A representing the snowflake identifier of the channel that this webhook can send + /// to. + /// public ulong? ChannelId { get; } + /// + /// Gets the hash value of this webhook's avatar. + /// + /// + /// A string containing the hash of this webhook's avatar. + /// public string Avatar { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs index ad7db53e2..18fe865df 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.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 { + /// + /// Contains a piece of audit log data related to a webhook update. + /// public class WebhookUpdateAuditLogData : IAuditLogData { private WebhookUpdateAuditLogData(IWebhook webhook, WebhookInfo before, WebhookInfo after) @@ -42,11 +41,28 @@ namespace Discord.Rest return new WebhookUpdateAuditLogData(webhook, before, after); } - //Again, the *current* data + /// + /// Gets the webhook that was updated. + /// + /// + /// A webhook object representing the webhook that was updated. + /// public IWebhook Webhook { get; } - //And the *audit log* data + /// + /// Gets the webhook information before the changes. + /// + /// + /// A webhook information object representing the webhook before the changes were made. + /// public WebhookInfo Before { get; } + + /// + /// Gets the webhook information after the changes. + /// + /// + /// A webhook information object representing the webhook after the changes were made. + /// public WebhookInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs index d01e964ae..d604077f4 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -6,6 +6,9 @@ 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 74ce7870f..716f3beaf 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 { @@ -78,14 +77,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); } @@ -161,6 +154,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) { @@ -169,6 +163,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) { @@ -177,6 +195,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) { @@ -241,6 +260,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) { @@ -253,6 +273,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) { @@ -292,7 +313,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/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index e0095c7b1..42ca20f7c 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -4,25 +4,193 @@ 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. + /// Determines whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// 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. + /// + /// + /// This method sends a file as if you are uploading an attachment directly from your Discord client. + /// + /// 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 + /// . + /// + /// + /// 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. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// 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. + /// + /// + /// This method sends a file as if you are uploading an attachment directly from your Discord client. + /// + /// 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 + /// . + /// + /// + /// 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. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// 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. + /// + /// The snowflake identifier of the message. + /// The options to be used when sending the request. + /// + /// A task that represents an asynchronous get operation for retrieving the message. The task result contains + /// the retrieved message; null if no message is found with the specified identifier. + /// 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 returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under . The + /// library will attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example downloads 300 messages and gets messages that belong to the user + /// 53905483156684800. + /// + /// var messages = await messageChannel.GetMessagesAsync(300).FlattenAsync(); + /// var userMessages = messages.Where(x => x.Author.Id == 53905483156684800); + /// + /// + /// The numbers of message to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of 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 returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under around + /// the message depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example gets 5 message prior to the message identifier 442012544660537354. + /// + /// var messages = await channel.GetMessagesAsync(442012544660537354, Direction.Before, 5).FlattenAsync(); + /// + /// + /// 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. + /// 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 returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under around + /// the message depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example gets 5 message prior to a specific message, oldMessage. + /// + /// var messages = await channel.GetMessagesAsync(oldMessage, Direction.Before, 5).FlattenAsync(); + /// + /// + /// 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. + /// 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. + /// + /// A task that represents the asynchronous get operation for retrieving pinned messages in this channel. + /// The task result contains a collection of messages found in the pinned 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 20a76aaf0..bee096273 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -9,12 +9,25 @@ 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 { - public RestUser CurrentUser { get; private set; } - public RestUser Recipient { get; private set; } - + /// + /// Gets the current logged-in user. + /// + public RestUser CurrentUser { get; } + + /// + /// Gets the recipient of the channel. + /// + public RestUser Recipient { get; } + + /// + /// Gets a collection that is the current logged-in user and the recipient. + /// public IReadOnlyCollection Users => ImmutableArray.Create(CurrentUser, Recipient); internal RestDMChannel(BaseDiscordClient discord, ulong id, ulong recipientId) @@ -34,14 +47,24 @@ 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); + + /// + /// Gets a user in this channel from the provided . + /// + /// The snowflake identifier of the user. + /// + /// A object that is a recipient of this channel; otherwise null. + /// public RestUser GetUser(ulong id) { if (id == Recipient.Id) @@ -52,49 +75,96 @@ 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) => ChannelHelper.EnterTypingState(this, Discord, options); + /// + /// Gets a string that represents the Username#Discriminator of the recipient. + /// + /// + /// A string that resolves to the Recipient of this channel. + /// 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 +172,7 @@ namespace Discord.Rest else return null; } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -109,6 +180,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 +188,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,24 +196,27 @@ 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); //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 c6b5d6ad0..39991d411 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -10,12 +10,16 @@ using Model = Discord.API.Channel; namespace Discord.Rest { + /// + /// Represents a REST-based group-message channel. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] 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 +53,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,33 +70,70 @@ 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) => ChannelHelper.EnterTypingState(this, Discord, options); @@ -143,10 +185,10 @@ namespace Discord.Rest => 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 + /// + /// Connecting to a group channel is not supported. Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 1dbcf7e9a..380a64c8c 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 { + /// + /// 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,19 +56,29 @@ 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); + /// + /// Gets the permission overwrite for a specific user. + /// + /// The user to get the overwrite from. + /// + /// An overwrite object for the targeted user; null if none is set. + /// public OverwritePermissions? GetPermissionOverwrite(IUser user) { for (int i = 0; i < _overwrites.Length; i++) @@ -71,6 +88,14 @@ namespace Discord.Rest } return null; } + + /// + /// Gets the permission overwrite for a specific role. + /// + /// The role to get the overwrite from. + /// + /// An overwrite object for the targeted role; null if none is set. + /// public OverwritePermissions? GetPermissionOverwrite(IRole role) { for (int i = 0; i < _overwrites.Length; i++) @@ -80,16 +105,44 @@ namespace Discord.Rest } return null; } - public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, RequestOptions options = null) + + /// + /// 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. + /// + /// A task representing the asynchronous permission operation for adding the specified permissions to the channel. + /// + public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) { - await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms, options).ConfigureAwait(false); - _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(perms.AllowValue, perms.DenyValue))); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, permissions, options).ConfigureAwait(false); + _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue))); } - public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, RequestOptions options = null) + /// + /// 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. + /// + /// A task representing the asynchronous permission operation for adding the specified permissions to the channel. + /// + public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) { - await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms, options).ConfigureAwait(false); - _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(perms.AllowValue, perms.DenyValue))); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, permissions, options).ConfigureAwait(false); + _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue))); } + + /// + /// 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. + /// + /// A task representing the asynchronous operation for removing the specified permissions from the channel. + /// public async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) { await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false); @@ -103,6 +156,14 @@ namespace Discord.Rest } } } + /// + /// 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. + /// + /// A task representing the asynchronous operation for removing the specified permissions from the channel. + /// public async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) { await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false); @@ -117,14 +178,42 @@ namespace Discord.Rest } } + /// + /// Gets a collection of all invites to this channel. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of invite metadata that are created for this channel. + /// public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); + + /// + /// 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, the 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. + /// + /// A task that represents the asynchronous invite creation operation. The task result contains an invite + /// metadata object containing information for the created invite. + /// public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); + /// + /// Gets the name of this channel. + /// + /// + /// A string that is the name of this channel. + /// public override string ToString() => Name; //IGuildChannel + /// IGuild IGuildChannel.Guild { get @@ -135,32 +224,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 12437c969..4ccd35a3a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -8,17 +8,22 @@ 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 int SlowModeInterval { get; private set; } + /// public ulong? CategoryId { get; private set; } + /// public string Mention => MentionUtils.MentionChannel(Id); - - private bool _nsfw; - public bool IsNsfw => _nsfw; + /// + public bool IsNsfw { get; private set; } internal RestTextChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, guild, id) @@ -30,82 +35,188 @@ namespace Discord.Rest entity.Update(model); return entity; } + /// internal override void Update(Model model) { base.Update(model); CategoryId = model.CategoryId; Topic = model.Topic.Value; SlowModeInterval = model.SlowMode.Value; - _nsfw = model.Nsfw.GetValueOrDefault(); + IsNsfw = model.Nsfw.GetValueOrDefault(); } + /// public async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } + /// + /// Gets a user in this channel. + /// + /// The snowflake identifier of the user. + /// The options to be used when sending the request. + /// + /// Resolving permissions requires the parent guild to be downloaded. + /// + /// + /// A task representing the asynchronous get operation. The task result contains a guild user object that + /// represents the user; null if none is found. + /// public Task GetUserAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetUserAsync(this, Guild, Discord, id, options); + + /// + /// Gets a collection of users that are able to view the channel. + /// + /// The options to be used when sending the request. + /// + /// Resolving permissions requires the parent guild to be downloaded. + /// + /// + /// A paged collection containing a collection of guild users that can access this channel. Flattening the + /// paginated response into a collection of users with + /// is required if you wish to access the users. + /// 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); + /// + /// 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// webhook. + /// public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); + /// + /// Gets a webhook available in this text channel. + /// + /// The identifier of the webhook. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a webhook associated + /// with the identifier; null if the webhook is not found. + /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetWebhookAsync(this, Discord, id, options); + /// + /// Gets the webhooks available in this text channel. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of webhooks that is available in this channel. + /// public Task> GetWebhooksAsync(RequestOptions options = null) => ChannelHelper.GetWebhooksAsync(this, Discord, options); + /// + /// Gets the parent (category) channel of this channel. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the category channel + /// representing the parent of this channel; null if none is set. + /// public Task GetCategoryAsync(RequestOptions options = null) => ChannelHelper.GetCategoryAsync(this, Discord, options); 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) @@ -113,6 +224,7 @@ namespace Discord.Rest else return null; } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -121,6 +233,7 @@ namespace Discord.Rest return AsyncEnumerable.Empty>(); } + /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -128,6 +241,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) @@ -135,20 +249,23 @@ 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) @@ -156,6 +273,7 @@ namespace Discord.Rest else return null; } + /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -165,6 +283,7 @@ namespace Discord.Rest } //IChannel + /// async Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -172,6 +291,7 @@ namespace Discord.Rest else return null; } + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -181,6 +301,7 @@ namespace Discord.Rest } // INestedChannel + /// async Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) { if (CategoryId.HasValue && mode == CacheMode.AllowDownload) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 13f3b5efa..7f0295c18 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -8,11 +8,17 @@ 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; } internal RestVoiceChannel(BaseDiscordClient discord, IGuild guild, ulong id) @@ -25,6 +31,7 @@ namespace Discord.Rest entity.Update(model); return entity; } + /// internal override void Update(Model model) { base.Update(model); @@ -33,24 +40,37 @@ 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); Update(model); } + /// + /// Gets the parent (category) channel of this channel. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the category channel + /// representing the parent of this channel; null if none is set. + /// public Task GetCategoryAsync(RequestOptions options = null) => ChannelHelper.GetCategoryAsync(this, Discord, options); private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; //IAudioChannel + /// + /// Connecting to a REST-based channel is not supported. Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } Task IAudioChannel.DisconnectAsync() { 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..ce37af6b4 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -41,13 +41,17 @@ namespace Discord.Rest => 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) => ChannelHelper.EnterTypingState(this, Discord, options); @@ -57,7 +61,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 +87,24 @@ 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); - IDisposable IMessageChannel.EnterTypingState(RequestOptions options) - => EnterTypingState(options); + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); //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 2b7900154..823d41a05 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) { @@ -220,6 +224,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) { @@ -266,7 +271,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) => @@ -338,7 +343,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, @@ -350,11 +355,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) { @@ -368,9 +374,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 e6819e8a4..fda9b609d 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,57 @@ 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); + /// + /// Gets the built-in role containing all users in this guild. + /// public RestRole EveryoneRole => GetRole(Id); + + /// + /// Gets a collection of all roles in this guild. + /// public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); + /// public IReadOnlyCollection Emotes => _emotes; + /// public IReadOnlyCollection Features => _features; internal RestGuild(BaseDiscordClient client, ulong id) @@ -103,89 +131,206 @@ 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); //Bans + //Bans + /// + /// Gets a collection of all users banned in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// ban objects that this guild currently possesses, with each object containing the user banned and reason + /// behind the ban. + /// public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); + /// + /// Gets a ban object for a banned user. + /// + /// The banned user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a ban object, which + /// contains the user information and the reason for the ban; null if the ban entry cannot be found. + /// public Task GetBanAsync(IUser user, RequestOptions options = null) => GuildHelper.GetBanAsync(this, Discord, user.Id, options); + /// + /// Gets a ban object for a banned user. + /// + /// The snowflake identifier for the banned user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a ban object, which + /// contains the user information and the reason for the ban; null if the ban entry cannot be found. + /// 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 + /// + /// Gets a collection of all channels in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// generic channels found within this guild. + /// public Task> GetChannelsAsync(RequestOptions options = null) => GuildHelper.GetChannelsAsync(this, Discord, options); + + /// + /// Gets a channel in this guild. + /// + /// The snowflake identifier for the channel. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the generic channel + /// associated with the specified ; null if none is found. + /// public Task GetChannelAsync(ulong id, RequestOptions options = null) => GuildHelper.GetChannelAsync(this, Discord, id, options); + + /// + /// Gets a text channel in this guild. + /// + /// The snowflake identifier for the text channel. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the text channel + /// associated with the specified ; null if none is found. + /// public async Task GetTextChannelAsync(ulong id, RequestOptions options = null) { var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false); return channel as RestTextChannel; } + + /// + /// 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. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// message channels found within this guild. + /// public async Task> GetTextChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); return channels.OfType().ToImmutableArray(); } + + /// + /// Gets a voice channel in this guild. + /// + /// The snowflake identifier for the voice channel. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the voice channel associated + /// with the specified ; null if none is found. + /// public async Task GetVoiceChannelAsync(ulong id, RequestOptions options = null) { var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false); return channel as RestVoiceChannel; } + + /// + /// Gets a collection of all voice channels in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// voice channels found within this guild. + /// public async Task> GetVoiceChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); return channels.OfType().ToImmutableArray(); } + + /// + /// Gets a collection of all category channels in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// category channels found within this guild. + /// public async Task> GetCategoryChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); return channels.OfType().ToImmutableArray(); } + /// + /// Gets the AFK voice channel in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the voice channel that the + /// AFK users will be moved to after they have idled for too long; null if none is set. + /// public async Task GetAFKChannelAsync(RequestOptions options = null) { var afkId = AFKChannelId; @@ -196,6 +341,15 @@ namespace Discord.Rest } return null; } + + /// + /// Gets the first viewable text channel in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the first viewable text + /// channel in this guild; null if none is found. + /// public async Task GetDefaultChannelAsync(RequestOptions options = null) { var channels = await GetTextChannelsAsync(options).ConfigureAwait(false); @@ -205,6 +359,15 @@ namespace Discord.Rest .OrderBy(c => c.Position) .FirstOrDefault(); } + + /// + /// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the embed channel set + /// within the server's widget settings; null if none is set. + /// public async Task GetEmbedChannelAsync(RequestOptions options = null) { var embedId = EmbedChannelId; @@ -212,6 +375,15 @@ namespace Discord.Rest return await GuildHelper.GetChannelAsync(this, Discord, embedId.Value, options).ConfigureAwait(false); return null; } + + /// + /// Gets the first viewable text channel in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the first viewable text + /// channel in this guild; null if none is found. + /// public async Task GetSystemChannelAsync(RequestOptions options = null) { var systemId = SystemChannelId; @@ -222,10 +394,52 @@ namespace Discord.Rest } return null; } + /// + /// Creates a new text channel in this guild. + /// + /// + /// The following example creates a new text channel under an existing category named Wumpus with a set topic. + /// + /// var categories = await guild.GetCategoriesAsync(); + /// var targetCategory = categories.FirstOrDefault(x => x.Name == "wumpus"); + /// if (targetCategory == null) return; + /// await Context.Guild.CreateTextChannelAsync(name, x => + /// { + /// x.CategoryId = targetCategory.Id; + /// x.Topic = $"This channel was created at {DateTimeOffset.UtcNow} by {user}."; + /// }); + /// + /// + /// 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly 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 delegate containing the properties to be applied to the channel upon its creation. + /// 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); @@ -236,6 +450,14 @@ namespace Discord.Rest => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); //Invites + /// + /// Gets a collection of all invites in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// invite metadata, each representing information for an invite found within this guild. + /// public Task> GetInvitesAsync(RequestOptions options = null) => GuildHelper.GetInvitesAsync(this, Discord, options); /// @@ -249,6 +471,13 @@ namespace Discord.Rest => GuildHelper.GetVanityInviteAsync(this, Discord, options); //Roles + /// + /// Gets a role in this guild. + /// + /// The snowflake identifier for the role. + /// + /// A role that is associated with the specified ; null if none is found. + /// public RestRole GetRole(ulong id) { if (_roles.TryGetValue(id, out RestRole value)) @@ -256,6 +485,18 @@ namespace Discord.Rest return null; } + /// + /// 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// role. + /// public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false, RequestOptions options = null) { @@ -265,47 +506,151 @@ namespace Discord.Rest } //Users + /// + /// Gets a collection of all users in this guild. + /// + /// + /// This method retrieves all users found within this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a collection of guild + /// users found within this guild. + /// public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) => GuildHelper.GetUsersAsync(this, Discord, null, null, options); + + /// + /// Gets a user from this guild. + /// + /// + /// This method retrieves a user found within this guild. + /// + /// The snowflake identifier of the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the guild user + /// associated with the specified ; null if none is found. + /// public Task GetUserAsync(ulong id, RequestOptions options = null) => GuildHelper.GetUserAsync(this, Discord, id, options); + + /// + /// Gets the current user for this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the currently logged-in + /// user within this guild. + /// public Task GetCurrentUserAsync(RequestOptions options = null) => GuildHelper.GetUserAsync(this, Discord, Discord.CurrentUser.Id, options); + + /// + /// Gets the owner of this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the owner of this guild. + /// public Task GetOwnerAsync(RequestOptions options = null) => GuildHelper.GetUserAsync(this, Discord, OwnerId, options); + /// + /// + /// Prunes inactive users. + /// + /// + /// + /// This method removes all users that have not logged on in the provided number of . + /// + /// + /// If is true, this method will only return the number of users that + /// would be removed without kicking the users. + /// + /// + /// 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. + /// + /// A task that represents the asynchronous prune operation. The task result contains the number of users to + /// be or has been removed from this guild. + /// public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); //Audit logs + /// + /// Gets the specified number of audit log entries for this guild. + /// + /// The number of audit log entries to fetch. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of the requested audit log entries. + /// public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); //Webhooks + /// + /// Gets a webhook found within this guild. + /// + /// The identifier for the webhook. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the webhook with the + /// specified ; null if none is found. + /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => GuildHelper.GetWebhookAsync(this, Discord, id, options); + + /// + /// Gets a collection of all webhook from this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of webhooks found within the guild. + /// 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); /// @@ -315,6 +660,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) @@ -322,6 +668,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -329,6 +676,7 @@ namespace Discord.Rest else return null; } + /// async Task> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -336,6 +684,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -343,6 +692,7 @@ namespace Discord.Rest else return null; } + /// async Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -350,6 +700,7 @@ namespace Discord.Rest else return ImmutableArray.Create(); } + /// async Task> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -357,6 +708,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -364,6 +716,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -371,6 +724,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -378,6 +732,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -385,6 +740,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -392,29 +748,38 @@ 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); /// async Task IGuild.GetVanityInviteAsync(RequestOptions options) => await GetVanityInviteAsync(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) @@ -422,6 +787,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -429,6 +795,7 @@ namespace Discord.Rest else return null; } + /// async Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -436,6 +803,7 @@ namespace Discord.Rest else return null; } + /// async Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -443,7 +811,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.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options) { @@ -453,9 +824,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 de5a5f7d9..b75d6288e 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -9,12 +9,17 @@ namespace Discord.Rest 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/Guilds/RestVoiceRegion.cs b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs index 4e0c3c1ee..7a03b68d4 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs @@ -4,6 +4,9 @@ using Model = Discord.API.VoiceRegion; namespace Discord { + /// + /// Represents a REST-based voice region. + /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class RestVoiceRegion : RestEntity, IVoiceRegion { diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 050f117fb..153eb6c41 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 public class RestInvite : RestEntity, IInvite, IUpdateable { public ChannelType ChannelType { get; private set; } + /// 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; } internal IGuild Guild { get; } + /// public string Code => Id; + /// public string Url => $"{DiscordConfig.InviteUrl}{Code}"; internal RestInvite(BaseDiscordClient discord, IGuild guild, IChannel channel, string id) @@ -43,18 +51,27 @@ namespace Discord.Rest PresenceCount = model.PresenceCount.IsSpecified ? model.PresenceCount.Value : null; ChannelType = (ChannelType)model.Channel.Type; } - + + /// public async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetInviteAsync(Code, options).ConfigureAwait(false); Update(model); } + /// public Task DeleteAsync(RequestOptions options = null) => InviteHelper.DeleteAsync(this, Discord, options); + /// + /// Gets the URL of the invite. + /// + /// + /// A string that resolves to the Url of the invite. + /// public override string ToString() => Url; private string DebuggerDisplay => $"{Url} ({GuildName} / {ChannelName})"; - + + /// IGuild IInvite.Guild { get @@ -66,6 +83,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 c7236be58..55acd5f45 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -3,17 +3,27 @@ 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.IsSpecified ? model.CreatedAt.Value.UtcTicks : (long?)null; } + /// 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..a2c7b9cf7 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 30ee75c47..b963fd532 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..a4ecf3647 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; @@ -7,27 +7,53 @@ using Model = Discord.API.Message; namespace Discord.Rest { + /// + /// Represents a REST-based message. + /// public abstract class RestMessage : RestEntity, IMessage, IUpdateable { private long _timestampTicks; + /// public IMessageChannel Channel { get; } + /// + /// Gets the Author of the message. + /// 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; + /// + /// Gets a collection of the 's on the message. + /// public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); + /// + /// Gets a collection of the 's on the message. + /// public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); + /// public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); + /// public virtual IReadOnlyCollection MentionedRoleIds => ImmutableArray.Create(); + /// + /// Gets a collection of the mentioned users in the message. + /// 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 +79,32 @@ 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); + /// + /// Gets the of the message. + /// + /// + /// A string that is the of the message. + /// 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/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index b9dda08ae..89a651eb7 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -1,11 +1,15 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.Message; namespace Discord.Rest { + /// + /// Represents a REST-based system message. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestSystemMessage : RestMessage, ISystemMessage { + /// public MessageType Type { get; private set; } internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 7354cc4af..5ec908fde 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -8,6 +8,9 @@ using Model = Discord.API.Message; namespace Discord.Rest { + /// + /// Represents a REST-based message sent by a user. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestUserMessage : RestMessage, IUserMessage { @@ -17,23 +20,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 +137,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..7c1a3aaa2 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -1,24 +1,39 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Role; namespace Discord.Rest { + /// + /// Represents a REST-based role. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] 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); + /// + /// Gets if this role is the @everyone role of the guild or not. + /// public bool IsEveryone => Id == Guild.Id; + /// public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) @@ -43,20 +58,30 @@ 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); + /// + /// Gets the name of the role. + /// + /// + /// A string that is the name of the 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..fbd76f609 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs @@ -1,8 +1,11 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.User; namespace Discord.Rest { + /// + /// Represents a REST-based group user. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestGroupUser : RestUser, IGroupUser { @@ -16,14 +19,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 3c47587cb..e59d92407 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -8,18 +8,28 @@ using Model = Discord.API.GuildMember; namespace Discord.Rest { + /// + /// Represents a REST-based guild user. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] 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 +39,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 +79,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 +100,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 +116,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 +125,7 @@ namespace Discord.Rest } //IGuildUser + /// IGuild IGuildUser.Guild { get @@ -119,10 +137,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..7f3a3faa8 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -1,15 +1,21 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; namespace Discord.Rest { + /// + /// Represents the logged-in REST-based user. + /// [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 +28,7 @@ namespace Discord.Rest entity.Update(model); return entity; } + /// internal override void Update(Model model) { base.Update(model); @@ -34,6 +41,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 +51,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..6af5b5c95 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -5,19 +5,32 @@ using Model = Discord.API.User; namespace Discord.Rest { + /// + /// Represents a REST-based user. + /// [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,26 +61,43 @@ 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); Update(model); } + /// + /// 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. + /// + /// A task that represents the asynchronous get operation. The task result contains a rest DM channel where the user is the recipient. + /// 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); + /// + /// Gets the Username#Discriminator of the user. + /// + /// + /// A string that resolves to Username#Discriminator of the user. + /// public override string ToString() => $"{Username}#{Discriminator}"; 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 54fe3b681..05a568198 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 40b91e98e..1b4830da2 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -117,7 +117,7 @@ namespace Discord.Net.Queue if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) _buckets.TryRemove(bucket.Id, out _); } - 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 d7f9779a4..d2f77cc39 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/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 9f197b5c5..800e04933 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -420,7 +420,6 @@ namespace Discord.Audio } private async Task RunKeepaliveAsync(int intervalMillis, CancellationToken cancelToken) { - var packet = new byte[8]; try { await _audioLogger.DebugAsync("Keepalive Started").ConfigureAwait(false); 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/OpusEncodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs index f5883ad4b..035b92b30 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs @@ -25,7 +25,7 @@ namespace Discord.Audio.Streams public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { - //Assume threadsafe + //Assume thread-safe while (count > 0) { if (_partialFramePos == 0 && count >= OpusConverter.FrameBytes) 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/SodiumDecryptStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs index 9ed849a5e..2b1a97f04 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs @@ -4,7 +4,9 @@ using System.Threading.Tasks; namespace Discord.Audio.Streams { - /// Decrypts an RTP frame using libsodium + /// + /// Decrypts an RTP frame using libsodium. + /// public class SodiumDecryptStream : AudioOutStream { private readonly AudioClient _client; diff --git a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs index bacc9be47..8b3f0e302 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs @@ -4,7 +4,9 @@ using System.Threading.Tasks; namespace Discord.Audio.Streams { - /// Encrypts an RTP frame using libsodium + /// + /// Encrypts an RTP frame using libsodium. + /// public class SodiumEncryptStream : AudioOutStream { private readonly AudioClient _client; @@ -20,21 +22,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..b1b7d17b5 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 + /// and accept a as its parameter. + /// + /// + /// 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 + /// and accept a as its parameter. + /// + /// + /// 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 + /// and accept 2 as its parameters. + /// + /// + /// 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,18 +62,72 @@ 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 + /// and accept a as its parameter. + /// + /// + /// 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 classes 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); } } internal readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); /// Fired when a message is updated. + /// + /// + /// This event is fired when a message is updated. The event handler must return a + /// and accept a , , + /// and as its parameters. + /// + /// + /// If caching is enabled via , the + /// entity will contain the original message; otherwise, in event + /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the + /// . + /// + /// + /// The updated message will be passed into the parameter. + /// + /// + /// The source channel of the updated message will be passed into the + /// parameter. + /// + /// public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated { add { _messageUpdatedEvent.Add(value); } remove { _messageUpdatedEvent.Remove(value); } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index a7d590b42..abaf5989e 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -6,88 +6,288 @@ using Discord.Rest; namespace Discord.WebSocket { + /// + /// Represents the base of a WebSocket-based Discord client. + /// 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. + /// + /// + /// An that represents the round-trip latency to the WebSocket server. Please + /// note that this value does not represent a "true" latency for operations such as sending a message. + /// public abstract int Latency { get; protected set; } - public abstract UserStatus Status { get; protected set; } + /// + /// Gets the status for the logged-in user. + /// + /// + /// A status object that represents the user's online presence status. + /// + public abstract UserStatus Status { get; protected set; } + /// + /// Gets the activity for the logged-in user. + /// + /// + /// An activity object that represents the user's current activity. + /// 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 user is currently in. + /// + /// + /// A read-only collection of guilds that the current user is in. + /// public abstract IReadOnlyCollection Guilds { get; } + /// + /// Gets a collection of private channels opened in this session. + /// + /// + /// This method will retrieve all private channels (including direct-message, group channel and such) that + /// are currently opened in this session. + /// + /// This method will not return previously opened private channels outside of the current session! If + /// you have just started the client, this may return an empty collection. + /// + /// + /// + /// A read-only collection of private channels that the user currently partakes in. + /// public abstract IReadOnlyCollection PrivateChannels { get; } + /// + /// Gets a collection of available voice regions. + /// + /// + /// A read-only collection of voice regions that the user has access to. + /// 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. + /// + /// A task that represents the asynchronous get operation. The task result contains the application + /// information. + /// public abstract Task GetApplicationInfoAsync(RequestOptions options = null); - /// + /// + /// Gets a generic 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 generic WebSocket-based 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 generic WebSocket-based user; null when the user cannot be found. + /// public abstract SocketUser GetUser(string username, string discriminator); - /// + /// + /// Gets a channel. + /// + /// The snowflake identifier of the channel (e.g. `381889909113225237`). + /// + /// A generic WebSocket-based channel object (voice, text, category, etc.) associated with the identifier; + /// null when the channel cannot be found. + /// public abstract SocketChannel GetChannel(ulong id); - /// + /// + /// Gets a guild. + /// + /// The guild snowflake identifier. + /// + /// A WebSocket-based guild associated with the snowflake identifier; null when the guild cannot be + /// found. + /// public abstract SocketGuild GetGuild(ulong id); - /// + /// + /// Gets a voice region. + /// + /// The identifier of the voice region (e.g. eu-central ). + /// + /// A REST-based voice region associated with the identifier; null if the voice region is not + /// found. + /// public abstract RestVoiceRegion GetVoiceRegion(string id); /// public abstract Task StartAsync(); /// public abstract Task StopAsync(); + /// + /// Sets the current status of the user (e.g. Online, Do not Disturb). + /// + /// The new status to be set. + /// + /// A task that represents the asynchronous set operation. + /// public abstract Task SetStatusAsync(UserStatus status); + /// + /// Sets the game of the user. + /// + /// The name of the game. + /// If streaming, the URL of the stream. Must be a valid Twitch URL. + /// The type of the game. + /// + /// A task that represents the asynchronous set operation. + /// 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 activity to be set. + /// + /// A task that represents the asynchronous set operation. + /// 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. + /// + /// A task that represents the asynchronous download operation. + /// + 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains the 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 user has set up. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of connections. + /// public Task> GetConnectionsAsync(RequestOptions options = null) => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); - /// + /// + /// Gets an invite. + /// + /// The invitation identifier. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the invite information. + /// public Task GetInviteAsync(string inviteId, RequestOptions options = null) => ClientHelper.GetInviteAsync(this, inviteId, 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, RequestOptions options) => await GetInviteAsync(inviteId, 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 a119e5e28..dad185d66 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -74,7 +74,7 @@ namespace Discord.WebSocket case SocketDMChannel dmChannel: _dmChannels.TryRemove(dmChannel.Recipient.Id, out _); 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 881ce3909..4e497346f 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -19,24 +19,29 @@ namespace Discord.WebSocket private DiscordSocketClient[] _shards; private int _totalShards; - /// 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 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) @@ -90,13 +95,13 @@ namespace Discord.WebSocket } } - //Assume threadsafe: already in a connection lock + //Assume thread safe: already in a connection lock for (int i = 0; i < _shards.Length; i++) - await _shards[i].LoginAsync(tokenType, token, false); + await _shards[i].LoginAsync(tokenType, token); } internal override async Task OnLogoutAsync() { - //Assume threadsafe: already in a connection lock + //Assume thread safe: already in a connection lock if (_shards != null) { for (int i = 0; i < _shards.Length; i++) @@ -213,9 +218,11 @@ 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. + /// + /// is public override async Task DownloadUsersAsync(IEnumerable guilds) { + if (guilds == null) throw new ArgumentNullException(nameof(guilds)); for (int i = 0; i < _shards.Length; i++) { int id = _shardIds[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++) @@ -317,34 +327,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, RequestOptions options) => await GetInviteAsync(inviteId, 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 d42bd7132..bc579793d 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 using Discord.API.Gateway; -using Discord.API.Rest; using Discord.Net.Queue; using Discord.Net.Rest; using Discord.Net.WebSockets; @@ -120,12 +119,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 +177,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.Events.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs index 1222b270e..51dea5f9f 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Threading.Tasks; +using Discord.API; namespace Discord.WebSocket -{ +{ public partial class DiscordSocketClient { //General @@ -34,5 +35,9 @@ namespace Discord.WebSocket remove { _latencyUpdatedEvent.Remove(value); } } private readonly AsyncEvent> _latencyUpdatedEvent = new AsyncEvent>(); + + internal DiscordSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) : base(config, client) + { + } } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 45d60b764..81538e6e7 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -19,6 +19,9 @@ using GameModel = Discord.API.Game; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based Discord client. + /// public partial class DiscordSocketClient : BaseSocketClient, IDiscordClient { private readonly ConcurrentQueue _largeGuilds; @@ -46,7 +49,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 +63,53 @@ 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; + /// + /// Gets a collection of direct message channels opened in this session. + /// + /// + /// 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. + /// + /// + /// + /// An collection of DM channels that have been opened in this session. + /// public IReadOnlyCollection DMChannels => State.PrivateChannels.OfType().ToImmutableArray(); + /// + /// Gets a collection of group channels opened in this session. + /// + /// + /// This method returns a collection of currently opened group channels. + /// + /// This method will not return previously opened group channels outside of the current session! If you + /// have just started the client, this may return an empty collection. + /// + /// + /// + /// An collection of group channels that have been opened in this session. + /// public IReadOnlyCollection GroupChannels => State.PrivateChannels.OfType().ToImmutableArray(); + /// public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); - /// Creates a new REST/WebSocket discord client. + /// + /// Initializes a new REST/WebSocket-based Discord client. + /// public DiscordSocketClient() : this(new DiscordSocketConfig()) { } - /// Creates a new REST/WebSocket discord client. + /// + /// Initializes a new REST/WebSocket-based Discord client with the provided configuration. + /// + /// The configuration to be used with the 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 +167,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 +177,7 @@ namespace Discord.WebSocket } } + /// internal override async Task OnLoginAsync(TokenType tokenType, string token) { if (_parentClient == null) @@ -147,6 +188,7 @@ namespace Discord.WebSocket else _voiceRegions = _parentClient._voiceRegions; } + /// internal override async Task OnLogoutAsync() { await StopAsync().ConfigureAwait(false); @@ -154,9 +196,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 +321,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 +360,13 @@ namespace Discord.WebSocket } } + /// + /// + /// The following example sets the status of the current user to Do Not Disturb. + /// + /// await client.SetStatusAsync(UserStatus.DoNotDisturb); + /// + /// public override async Task SetStatusAsync(UserStatus status) { Status = status; @@ -325,6 +376,21 @@ namespace Discord.WebSocket _statusSince = null; await SendStatusAsync().ConfigureAwait(false); } + /// + /// + /// + /// The following example sets the activity of the current user to the specified game name. + /// + /// await client.SetGameAsync("A Strange Game"); + /// + /// + /// + /// The following example sets the activity of the current user to a streaming status. + /// + /// await client.SetGameAsync("Great Stream 10/10", "https://twitch.tv/MyAmazingStream1337", ActivityType.Streaming); + /// + /// + /// public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) { if (!string.IsNullOrEmpty(streamUrl)) @@ -335,6 +401,7 @@ namespace Discord.WebSocket Activity = null; await SendStatusAsync().ConfigureAwait(false); } + /// public override async Task SetActivityAsync(IActivity activity) { Activity = activity; @@ -352,7 +419,7 @@ 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) - throw new NotSupportedException("Outgoing Rich Presences are not supported"); + throw new NotSupportedException("Outgoing Rich Presences are not supported via WebSocket."); if (Activity != null) { @@ -1148,7 +1215,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 +1242,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 +1262,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 +1286,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 +1310,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 +1341,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); } } @@ -1634,6 +1701,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); @@ -1806,43 +1874,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, RequestOptions options) => await GetInviteAsync(inviteId, 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..3ac2a81a0 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..9f04239ad 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -5,27 +5,120 @@ 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 read-only 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. + /// Determines whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// 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. + /// + /// + /// This method sends a file as if you are uploading an attachment directly from your Discord client. + /// + /// 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 + /// . + /// + /// + /// 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. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// 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. + /// + /// + /// This method sends a file as if you are uploading an attachment directly from your Discord client. + /// + /// 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 + /// . + /// + /// + /// 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. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// 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 read-only 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 read-only 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 read-only 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 task that represents the asynchronous get operation for retrieving pinned messages in this channel. + /// The task result contains a collection of messages found in the pinned 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..0ae40820c 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs @@ -3,22 +3,31 @@ 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)), ChannelPermission.ViewChannel)).ToImmutableArray(); + /// + /// Gets the child channels of this category. + /// + /// + /// A read-only collection of whose + /// matches the snowflake identifier of this category + /// channel. + /// public IReadOnlyCollection Channels => Guild.Channels.Where(x => x is INestedChannel nestedChannel && nestedChannel.CategoryId == Id).ToImmutableArray(); @@ -34,6 +43,7 @@ namespace Discord.WebSocket } //Users + /// public override SocketGuildUser GetUser(ulong id) { var user = Guild.GetUser(id); @@ -51,18 +61,26 @@ namespace Discord.WebSocket internal new SocketCategoryChannel Clone() => MemberwiseClone() as SocketCategoryChannel; // IGuildChannel + /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => 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(); //IChannel + /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 502e61d15..13c0c9b4f 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,28 @@ namespace Discord.WebSocket internal abstract void Update(ClientState state, Model model); //User + /// + /// Gets a generic user from this channel. + /// + /// The snowflake identifier of the user. + /// + /// A generic WebSocket-based user associated with the snowflake identifier. + /// 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 9a8be9703..6b6577167 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -10,14 +10,25 @@ 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; } + /// + /// Gets the recipient of the channel. + /// + public SocketUser Recipient { get; } + /// public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); + + /// + /// Gets a collection that is the current logged-in user and the recipient. + /// public new IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketGlobalUser recipient) @@ -26,7 +37,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,12 +50,22 @@ 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); + /// + /// Gets the message associated with the given . + /// + /// TThe 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. + /// public async Task GetMessageAsync(ulong id, RequestOptions options = null) { IMessage msg = _messages?.Get(id); @@ -52,37 +73,150 @@ namespace Discord.WebSocket msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); return msg; } + + /// + /// Gets the last N messages from this message channel. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under . The + /// library will attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example downloads 300 messages and gets messages that belong to the user + /// 53905483156684800. + /// + /// var messages = await messageChannel.GetMessagesAsync(300).FlattenAsync(); + /// var userMessages = messages.Where(x => x.Author.Id == 53905483156684800); + /// + /// + /// The numbers of message to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. + /// public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); + /// + /// Gets a collection of messages in this channel. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under around + /// the message depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example gets 5 message prior to the message identifier 442012544660537354. + /// + /// var messages = await channel.GetMessagesAsync(442012544660537354, Direction.Before, 5).FlattenAsync(); + /// + /// + /// 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. + /// public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); + /// + /// Gets a collection of messages in this channel. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under around + /// the message depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example gets 5 message prior to a specific message, oldMessage. + /// + /// var messages = await channel.GetMessagesAsync(oldMessage, Direction.Before, 5).FlattenAsync(); + /// + /// + /// 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. + /// 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); - + /// 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) => ChannelHelper.EnterTypingState(this, Discord, options); @@ -92,6 +226,13 @@ namespace Discord.WebSocket => _messages?.Remove(id); //Users + /// + /// Gets a user in this channel from the provided . + /// + /// The snowflake identifier of the user. + /// + /// A object that is a recipient of this channel; otherwise null. + /// public new SocketUser GetUser(ulong id) { if (id == Recipient.Id) @@ -102,24 +243,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 +277,36 @@ 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 74a1e3725..1fc289f41 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 { @@ -23,8 +26,10 @@ namespace Discord.WebSocket private string _iconId; private ConcurrentDictionary _users; + /// 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,31 +94,42 @@ 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); + /// 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) => ChannelHelper.EnterTypingState(this, Discord, options); @@ -120,6 +139,13 @@ namespace Discord.WebSocket => _messages?.Remove(id); //Users + /// + /// Gets a user from this group. + /// + /// The snowflake identifier of the user. + /// + /// A WebSocket-based group user associated with the snowflake identifier. + /// public new SocketGroupUser GetUser(ulong id) { if (_users.TryGetValue(id, out SocketGroupUser user)) @@ -169,21 +195,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,32 +225,40 @@ 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 + /// + /// Connecting to a group channel is not supported. Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } Task IAudioChannel.DisconnectAsync() { 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..bf1b2260b 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 object 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 read-only 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) @@ -41,6 +59,7 @@ namespace Discord.WebSocket return new SocketGuildChannel(guild.Discord, model.Id, guild); } } + /// internal override void Update(ClientState state, Model model) { Name = model.Name.Value; @@ -53,11 +72,20 @@ 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); + /// + /// Gets the permission overwrite for a specific user. + /// + /// The user to get the overwrite from. + /// + /// An overwrite object for the targeted user; null if none is set. + /// public OverwritePermissions? GetPermissionOverwrite(IUser user) { for (int i = 0; i < _overwrites.Length; i++) @@ -67,6 +95,13 @@ namespace Discord.WebSocket } return null; } + /// + /// Gets the permission overwrite for a specific role. + /// + /// The role to get the overwrite from. + /// + /// An overwrite object for the targeted role; null if none is set. + /// public OverwritePermissions? GetPermissionOverwrite(IRole role) { for (int i = 0; i < _overwrites.Length; i++) @@ -76,16 +111,44 @@ namespace Discord.WebSocket } return null; } - public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, RequestOptions options = null) + + /// + /// 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. + /// + /// A task representing the asynchronous permission operation for adding the specified permissions to the channel. + /// + public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) { - await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms, options).ConfigureAwait(false); - _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(perms.AllowValue, perms.DenyValue))); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, permissions, options).ConfigureAwait(false); + _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue))); } - public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, RequestOptions options = null) + + /// + /// 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. + /// + /// A task representing the asynchronous permission operation for adding the specified permissions to the channel. + /// + public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) { - await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms, options).ConfigureAwait(false); - _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(perms.AllowValue, perms.DenyValue))); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, permissions, options).ConfigureAwait(false); + _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue))); } + /// + /// 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. + /// + /// A task representing the asynchronous operation for removing the specified permissions from the channel. + /// public async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) { await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false); @@ -99,6 +162,14 @@ namespace Discord.WebSocket } } } + /// + /// 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. + /// + /// A task representing the asynchronous operation for removing the specified permissions from the channel. + /// public async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) { await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false); @@ -113,50 +184,93 @@ namespace Discord.WebSocket } } + /// + /// Returns a collection of all invites to this channel. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of invite metadata that are created for this channel. + /// public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); + /// + /// 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, the 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. + /// + /// A task that represents the asynchronous invite creation operation. The task result contains an invite + /// metadata object containing information for the created invite. + /// public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); public new virtual SocketGuildUser GetUser(ulong id) => null; + /// + /// Gets the name of the channel. + /// + /// + /// A string that resolves to . + /// public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}, Guild)"; internal new SocketGuildChannel Clone() => MemberwiseClone() as SocketGuildChannel; //SocketChannel + /// internal override IReadOnlyCollection GetUsersInternal() => Users; + /// 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 2e9cd90be..8faf1c6eb 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -10,22 +10,37 @@ 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 int SlowModeInterval { get; private set; } + /// public ulong? CategoryId { get; private set; } + /// + /// Gets the parent (category) of this channel in the guild's channel list. + /// + /// + /// An representing the parent of this channel; null if none is set. + /// public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; private bool _nsfw; + /// public bool IsNsfw => _nsfw; + /// 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)), @@ -35,7 +50,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) { @@ -52,12 +67,23 @@ 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); + /// + /// Gets a message from this message channel. + /// + /// The snowflake identifier of the message. + /// The options to be used when sending the request. + /// + /// A task that represents an asynchronous get operation for retrieving the message. The task result contains + /// the retrieved message; null if no message is found with the specified identifier. + /// public async Task GetMessageAsync(ulong id, RequestOptions options = null) { IMessage msg = _messages?.Get(id); @@ -65,42 +91,159 @@ namespace Discord.WebSocket msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); return msg; } + /// + /// Gets the last N messages from this message channel. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under . The + /// library will attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example downloads 300 messages and gets messages that belong to the user + /// 53905483156684800. + /// + /// var messages = await messageChannel.GetMessagesAsync(300).FlattenAsync(); + /// var userMessages = messages.Where(x => x.Author.Id == 53905483156684800); + /// + /// + /// The numbers of message to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. + /// + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); + /// + /// Gets a collection of messages in this channel. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under around + /// the message depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example gets 5 message prior to the message identifier 442012544660537354. + /// + /// var messages = await channel.GetMessagesAsync(442012544660537354, Direction.Before, 5).FlattenAsync(); + /// + /// + /// 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. + /// public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); + /// + /// Gets a collection of messages in this channel. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// + /// Do not fetch too many messages at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of messages specified under around + /// the message depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 messages, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// + /// The following example gets 5 message prior to a specific message, oldMessage. + /// + /// var messages = await channel.GetMessagesAsync(oldMessage, Direction.Before, 5).FlattenAsync(); + /// + /// + /// 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. + /// 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); + /// 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 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) => ChannelHelper.EnterTypingState(this, Discord, options); @@ -110,6 +253,7 @@ namespace Discord.WebSocket => _messages?.Remove(id); //Users + /// public override SocketGuildUser GetUser(ulong id) { var user = Guild.GetUser(id); @@ -124,10 +268,37 @@ 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// webhook. + /// public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); + /// + /// Gets a webhook available in this text channel. + /// + /// The identifier of the webhook. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a webhook associated + /// with the identifier; null if the webhook is not found. + /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetWebhookAsync(this, Discord, id, options); + /// + /// Gets the webhooks available in this text channel. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of webhooks that is available in this channel. + /// public Task> GetWebhooksAsync(RequestOptions options = null) => ChannelHelper.GetWebhooksAsync(this, Discord, options); @@ -135,20 +306,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) @@ -156,26 +333,31 @@ 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); // INestedChannel + /// Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Category); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 8e6e09a9f..07977e5e0 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -10,15 +10,28 @@ 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; } + /// + /// Gets the parent (category) channel of this channel. + /// + /// + /// A category channel representing the parent of this channel; null if none is 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(); @@ -32,6 +45,7 @@ namespace Discord.WebSocket entity.Update(state, model); return entity; } + /// internal override void Update(ClientState state, Model model) { base.Update(state, model); @@ -40,17 +54,21 @@ 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(bool selfDeaf = false, bool selfMute = false, bool external = false) { return await Guild.ConnectAudioAsync(Id, selfDeaf, selfMute, external).ConfigureAwait(false); } + /// public async Task DisconnectAsync() => await Guild.DisconnectAudioAsync(); + /// public override SocketGuildUser GetUser(ulong id) { var user = Guild.GetUser(id); @@ -63,12 +81,15 @@ 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(); // INestedChannel + /// Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Category); } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 78ea4004a..06b048872 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,96 @@ 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. + /// + /// + /// This property retrieves the number of members returned by Discord. + /// + /// + /// Due to how this property is returned by Discord instead of relying on the WebSocket cache, the + /// number here is the most accurate in terms of counting the number of users within this guild. + /// + /// + /// Use this instead of enumerating the count of the + /// collection, as you may see discrepancy + /// between that and this property. + /// + /// + /// 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 default channel in this guild. + /// + /// + /// This property retrieves the first viewable text channel for this guild. + /// + /// This channel does not guarantee the user can send message to it, as it only looks for the first viewable + /// text channel. + /// + /// + /// + /// A representing the first viewable channel that the user has access to. + /// public SocketTextChannel DefaultChannel => TextChannels .Where(c => CurrentUser.GetPermissions(c).ViewChannel) .OrderBy(c => c.Position) .FirstOrDefault(); + /// + /// Gets the AFK voice channel in this guild. + /// + /// + /// A that the AFK users will be moved to after they have idled for too + /// long; null if none is set. + /// public SocketVoiceChannel AFKChannel { get @@ -74,6 +136,12 @@ namespace Discord.WebSocket return id.HasValue ? GetVoiceChannel(id.Value) : null; } } + /// + /// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. + /// + /// + /// A channel set within the server's widget settings; null if none is set. + /// public SocketGuildChannel EmbedChannel { get @@ -82,6 +150,12 @@ namespace Discord.WebSocket return id.HasValue ? GetChannel(id.Value) : null; } } + /// + /// Gets the system channel where randomized welcome messages are sent in this guild. + /// + /// + /// A text channel where randomized welcome messages will be sent to; null if none is set. + /// public SocketTextChannel SystemChannel { get @@ -90,14 +164,47 @@ namespace Discord.WebSocket return id.HasValue ? GetTextChannel(id.Value) : null; } } + /// + /// Gets a collection of all text channels in this guild. + /// + /// + /// A read-only collection of message channels found within this guild. + /// public IReadOnlyCollection TextChannels => Channels.OfType().ToImmutableArray(); + /// + /// Gets a collection of all voice channels in this guild. + /// + /// + /// A read-only collection of voice channels found within this guild. + /// public IReadOnlyCollection VoiceChannels => Channels.OfType().ToImmutableArray(); + /// + /// Gets a collection of all category channels in this guild. + /// + /// + /// A read-only collection of category channels found within this guild. + /// public IReadOnlyCollection CategoryChannels => Channels.OfType().ToImmutableArray(); + /// + /// Gets the current logged-in user. + /// public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; + /// + /// Gets the built-in role containing all users in this guild. + /// + /// + /// A role object that represents an @everyone role in this guild. + /// public SocketRole EveryoneRole => GetRole(Id); + /// + /// Gets a collection of all channels in this guild. + /// + /// + /// A read-only collection of generic channels found within this guild. + /// public IReadOnlyCollection Channels { get @@ -107,9 +214,38 @@ namespace Discord.WebSocket return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); } } + /// public IReadOnlyCollection Emotes => _emotes; + /// public IReadOnlyCollection Features => _features; + /// + /// Gets a collection of users in this guild. + /// + /// + /// This property retrieves all users found within this guild. + /// + /// + /// This property may not always return all the members for large guilds (i.e. guilds containing + /// 100+ users). If you are simply looking to get the number of users present in this guild, + /// consider using instead. + /// + /// + /// Otherwise, you may need to enable to fetch + /// the full user list upon startup, or use to manually download + /// the users. + /// + /// + /// + /// + /// A collection of guild users found within this guild. + /// public IReadOnlyCollection Users => _members.ToReadOnlyCollection(); + /// + /// Gets a collection of all roles in this guild. + /// + /// + /// A read-only collection of roles found within this guild. + /// public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); internal SocketGuild(DiscordSocketClient client, ulong id) @@ -270,40 +406,87 @@ 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 + /// + /// Gets a collection of all users banned in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// ban objects that this guild currently possesses, with each object containing the user banned and reason + /// behind the ban. + /// public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); + /// + /// Gets a ban object for a banned user. + /// + /// The banned user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a ban object, which + /// contains the user information and the reason for the ban; null if the ban entry cannot be found. + /// public Task GetBanAsync(IUser user, RequestOptions options = null) => GuildHelper.GetBanAsync(this, Discord, user.Id, options); + /// + /// Gets a ban object for a banned user. + /// + /// The snowflake identifier for the banned user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a ban object, which + /// contains the user information and the reason for the ban; null if the ban entry cannot be found. + /// 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 + /// + /// Gets a channel in this guild. + /// + /// The snowflake identifier for the channel. + /// + /// A generic channel associated with the specified ; null if none is found. + /// public SocketGuildChannel GetChannel(ulong id) { var channel = Discord.State.GetChannel(id) as SocketGuildChannel; @@ -311,14 +494,73 @@ namespace Discord.WebSocket return channel; return null; } + /// + /// Gets a text channel in this guild. + /// + /// The snowflake identifier for the text channel. + /// + /// A text channel associated with the specified ; null if none is found. + /// public SocketTextChannel GetTextChannel(ulong id) => GetChannel(id) as SocketTextChannel; + /// + /// Gets a voice channel in this guild. + /// + /// The snowflake identifier for the voice channel. + /// + /// A voice channel associated with the specified ; null if none is found. + /// public SocketVoiceChannel GetVoiceChannel(ulong id) => GetChannel(id) as SocketVoiceChannel; + + /// + /// Creates a new text channel in this guild. + /// + /// + /// The following example creates a new text channel under an existing category named Wumpus with a set topic. + /// + /// var categories = await guild.GetCategoriesAsync(); + /// var targetCategory = categories.FirstOrDefault(x => x.Name == "wumpus"); + /// if (targetCategory == null) return; + /// await Context.Guild.CreateTextChannelAsync(name, x => + /// { + /// x.CategoryId = targetCategory.Id; + /// x.Topic = $"This channel was created at {DateTimeOffset.UtcNow} by {user}."; + /// }); + /// + /// + /// 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. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// text channel. + /// public Task CreateTextChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateTextChannelAsync(this, Discord, name, options, func); + /// + /// Creates a new voice channel in this guild. + /// + /// 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. + /// is null. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// voice channel. + /// public Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); + /// + /// Creates a new channel category in this guild. + /// + /// The new name for the category. + /// The options to be used when sending the request. + /// is null. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// category channel. + /// public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); @@ -343,6 +585,14 @@ namespace Discord.WebSocket => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); //Invites + /// + /// Gets a collection of all invites in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// invite metadata, each representing information for an invite found within this guild. + /// public Task> GetInvitesAsync(RequestOptions options = null) => GuildHelper.GetInvitesAsync(this, Discord, options); /// @@ -350,18 +600,40 @@ namespace Discord.WebSocket /// /// The options to be used when sending the request. /// - /// A partial metadata of the vanity invite found within this guild. + /// A task that represents the asynchronous get operation. The task result contains the partial metadata of + /// the vanity invite found within this guild; null if none is found. /// public Task GetVanityInviteAsync(RequestOptions options = null) => GuildHelper.GetVanityInviteAsync(this, Discord, options); //Roles + /// + /// Gets a role in this guild. + /// + /// The snowflake identifier for the role. + /// + /// A role that is associated with the specified ; null if none is found. + /// public SocketRole GetRole(ulong id) { if (_roles.TryGetValue(id, out SocketRole value)) return value; return null; } + + /// + /// 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. + /// is null. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly 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); @@ -379,12 +651,27 @@ namespace Discord.WebSocket } //Users + /// + /// Gets a user from this guild. + /// + /// + /// This method retrieves a user found within this guild. + /// + /// This may return null in the WebSocket implementation due to incomplete user collection in + /// large guilds. + /// + /// + /// The snowflake identifier of the user. + /// + /// A guild user associated with the specified ; null if none is found. + /// 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); @@ -438,6 +725,7 @@ namespace Discord.WebSocket return null; } + /// public async Task DownloadUsersAsync() { await Discord.DownloadUsersAsync(new[] { this }).ConfigureAwait(false); @@ -448,22 +736,53 @@ namespace Discord.WebSocket } //Audit logs + /// + /// Gets the specified number of audit log entries for this guild. + /// + /// The number of audit log entries to fetch. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of the requested audit log entries. + /// public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); //Webhooks + /// + /// Gets a webhook found within this guild. + /// + /// The identifier for the webhook. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the webhook with the + /// specified ; null if none is found. + /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => GuildHelper.GetWebhookAsync(this, Discord, id, options); + /// + /// Gets a collection of all webhook from this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of webhooks found within the guild. + /// 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); @@ -576,7 +895,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); } @@ -650,20 +969,35 @@ namespace Discord.WebSocket } } + /// + /// Gets the name of the guild. + /// + /// + /// A string that resolves to . + /// 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); /// @@ -673,60 +1007,84 @@ 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); /// async Task IGuild.GetVanityInviteAsync(RequestOptions options) => await GetVanityInviteAsync(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); + /// async Task> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options) { if (cacheMode == CacheMode.AllowDownload) @@ -735,9 +1093,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 6ebd0fa1c..24e46df46 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -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)); @@ -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..9c3a5f32b 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs @@ -1,14 +1,50 @@ -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 identifier associated with the user. + /// public ulong UserId { get; } + /// + /// Gets the user who added the reaction if possible. + /// + /// + /// A user object where possible; a value is not always returned. + /// + /// public Optional User { get; } + /// + /// Gets the ID of the message that has been reacted to. + /// + /// + /// A message snowflake identifier associated with the message. + /// public ulong MessageId { get; } + /// + /// Gets the message that has been reacted to if possible. + /// + /// + /// A WebSocket-based message where possible; a 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 +66,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 +78,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 bf817e008..97754da56 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 { @@ -19,22 +22,32 @@ namespace Discord.WebSocket private ImmutableArray _embeds; private ImmutableArray _tags; + /// 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..b5e26ad78 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; @@ -8,21 +8,45 @@ using Model = Discord.API.Role; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based role to be given to a guild user. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketRole : SocketEntity, IRole { + /// + /// Gets the guild that owns this role. + /// + /// + /// A representing the parent guild of this role. + /// 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); + /// + /// Returns a value that determines if the role is an @everyone role. + /// + /// + /// true if the role is @everyone; otherwise false. + /// 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,18 +73,28 @@ 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); + /// + /// Gets the name of the role. + /// + /// + /// A string that resolves to . + /// public override string ToString() => Name; 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/SocketEntity.cs b/src/Discord.Net.WebSocket/Entities/SocketEntity.cs index c8e14fb6c..f76694e6f 100644 --- a/src/Discord.Net.WebSocket/Entities/SocketEntity.cs +++ b/src/Discord.Net.WebSocket/Entities/SocketEntity.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord.WebSocket { @@ -6,6 +6,7 @@ namespace Discord.WebSocket where T : IEquatable { internal DiscordSocketClient Discord { get; } + /// public T Id { get; } internal SocketEntity(DiscordSocketClient discord, T id) 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..f560aadbb 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 @@ -6,15 +6,28 @@ namespace Discord.WebSocket [DebuggerDisplay("{DebuggerDisplay,nq}")] public class SocketGroupUser : SocketUser, IGroupUser { + /// + /// Gets the group channel of the user. + /// + /// + /// A representing the channel of which the user belongs to. + /// 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 +43,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..659a2eeea 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,67 @@ 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 ?? ""; + /// + /// Gets the voice connection status of the user if any. + /// + /// + /// A representing the user's voice status; null if the user is not + /// connected to a voice channel. + /// 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 +156,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 +176,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..4112fd273 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -1,13 +1,17 @@ -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.Presence; namespace Discord.WebSocket { - //TODO: C#7 Candidate for record type + /// + /// Represents the WebSocket user's presence status. This may include their online status and their activity. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct SocketPresence : IPresence { + /// public UserStatus Status { get; } + /// public IActivity Activity { get; } internal SocketPresence(UserStatus status, IActivity activity) @@ -20,6 +24,12 @@ namespace Discord.WebSocket return new SocketPresence(model.Status, model.Game?.ToEntity()); } + /// + /// Gets the status of the user. + /// + /// + /// A string that resolves to . + /// public override string ToString() => Status.ToString(); private string DebuggerDisplay => $"{Status}{(Activity != null ? $", {Activity.Name}": "")}"; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index b7c02c2db..ae705109c 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; @@ -6,20 +6,32 @@ using Model = Discord.API.User; namespace Discord.WebSocket { + /// + /// Represents the logged-in WebSocket-based user. + /// [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 +65,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..e6ce0137e 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs @@ -1,21 +1,35 @@ -using System; +using System; using System.Diagnostics; using Model = Discord.API.User; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based user that is yet to be recognized by the client. + /// + /// + /// A user may not be recognized due to the user missing from the cache or failed to be recognized properly. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketUnknownUser : SocketUser { + /// 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 => false; internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } - internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } + /// + /// This field is not supported for an unknown user. + internal override SocketGlobalUser GlobalUser => + throw new NotSupportedException(); internal SocketUnknownUser(DiscordSocketClient discord, ulong id) : base(discord, id) @@ -28,6 +42,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..3e5d1c3b7 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs @@ -1,13 +1,18 @@ -using System; +using System; using System.Diagnostics; using Model = Discord.API.VoiceState; namespace Discord.WebSocket { - //TODO: C#7 Candidate for record type + /// + /// Represents a WebSocket user's voice connection status. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct SocketVoiceState : IVoiceState { + /// + /// Initializes a default with everything set to null or false. + /// public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, false, false, false, false, false); [Flags] @@ -22,14 +27,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,10 +69,17 @@ namespace Discord.WebSocket return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress); } + /// + /// Gets the name of this voice channel. + /// + /// + /// A string that resolves to name of this voice channel; otherwise "Unknown". + /// public override string ToString() => VoiceChannel?.Name ?? "Unknown"; private string DebuggerDisplay => $"{VoiceChannel?.Name ?? "Unknown"} ({_voiceStates})"; internal SocketVoiceState Clone() => this; + /// IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index dd80648d2..3169be682 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; @@ -7,21 +7,32 @@ using Model = Discord.API.User; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based webhook user. + /// [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 +47,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, diff --git a/test/Discord.Net.Tests/AnalyzerTests/GuildAccessTests.cs b/test/Discord.Net.Tests/AnalyzerTests/GuildAccessTests.cs index 073cc1de7..4cb5cefcb 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/GuildAccessTests.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/GuildAccessTests.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; -using System.Threading.Tasks; +using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Discord.Analyzers; diff --git a/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticVerifier.Helper.cs b/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticVerifier.Helper.cs index 7a8eb2e9c..99654f12c 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticVerifier.Helper.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticVerifier.Helper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; -using Discord; using Discord.Commands; namespace TestHelper diff --git a/test/Discord.Net.Tests/AnalyzerTests/Verifiers/DiagnosticVerifier.cs b/test/Discord.Net.Tests/AnalyzerTests/Verifiers/DiagnosticVerifier.cs index 498e5ef27..3564093f8 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/Verifiers/DiagnosticVerifier.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/Verifiers/DiagnosticVerifier.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; //using Microsoft.VisualStudio.TestTools.UnitTesting; using Xunit; @@ -104,7 +103,7 @@ namespace TestHelper /// Diagnostic Results that should have appeared in the code private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) { - int expectedCount = expectedResults.Count(); + int expectedCount = expectedResults.Length; int actualCount = actualResults.Count(); if (expectedCount != actualCount) diff --git a/test/Discord.Net.Tests/Net/HttpMixin.cs b/test/Discord.Net.Tests/Net/HttpMixin.cs index c4a78ce0b..6cde2776b 100644 --- a/test/Discord.Net.Tests/Net/HttpMixin.cs +++ b/test/Discord.Net.Tests/Net/HttpMixin.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Net; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -142,4 +141,4 @@ namespace Discord.Net return stream.WriteAsync(data, start, length).ToObservable(); } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/Tests.Channels.cs b/test/Discord.Net.Tests/Tests.Channels.cs index 46e28b9da..890cacbf1 100644 --- a/test/Discord.Net.Tests/Tests.Channels.cs +++ b/test/Discord.Net.Tests/Tests.Channels.cs @@ -1,5 +1,4 @@ using Discord.Rest; -using System; using System.Linq; using System.Threading.Tasks; using Xunit; @@ -171,15 +170,15 @@ namespace Discord // 2 categories Assert.Equal(2, categories.Length); - var cat1 = categories.Where(x => x.Name == "cat1").FirstOrDefault(); - var cat2 = categories.Where(x => x.Name == "cat2").FirstOrDefault(); + var cat1 = categories.FirstOrDefault(x => x.Name == "cat1"); + var cat2 = categories.FirstOrDefault(x => x.Name == "cat2"); Assert.NotNull(cat1); Assert.NotNull(cat2); // get text1, text2, ensure they have category id == cat1 - var text1 = allChannels.Where(x => x.Name == "text1").FirstOrDefault() as RestTextChannel; - var text2 = allChannels.Where(x => x.Name == "text2").FirstOrDefault() as RestTextChannel; + var text1 = allChannels.FirstOrDefault(x => x.Name == "text1") as RestTextChannel; + var text2 = allChannels.FirstOrDefault(x => x.Name == "text2") as RestTextChannel; Assert.NotNull(text1); Assert.NotNull(text2); @@ -197,8 +196,8 @@ namespace Discord Assert.Equal(text2Cat.Name, cat1.Name); // do the same for the voice channels - var voice1 = allChannels.Where(x => x.Name == "voice1").FirstOrDefault() as RestVoiceChannel; - var voice3 = allChannels.Where(x => x.Name == "voice3").FirstOrDefault() as RestVoiceChannel; + var voice1 = allChannels.FirstOrDefault(x => x.Name == "voice1") as RestVoiceChannel; + var voice3 = allChannels.FirstOrDefault(x => x.Name == "voice3") as RestVoiceChannel; Assert.NotNull(voice1); Assert.NotNull(voice3); diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs index bbd6621b5..f49f431b5 100644 --- a/test/Discord.Net.Tests/Tests.GuildPermissions.cs +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -31,11 +31,11 @@ namespace Discord copy = GuildPermissions.None.Modify(); Assert.Equal(GuildPermissions.None.RawValue, copy.RawValue); - // test modify with no paramters on all permissions + // test modify with no parameters on all permissions copy = GuildPermissions.All.Modify(); Assert.Equal(GuildPermissions.All.RawValue, copy.RawValue); - // test modify with no paramters on webhook permissions + // test modify with no parameters on webhook permissions copy = GuildPermissions.Webhook.Modify(); Assert.Equal(GuildPermissions.Webhook.RawValue, copy.RawValue); diff --git a/test/Discord.Net.Tests/Tests.Migrations.cs b/test/Discord.Net.Tests/Tests.Migrations.cs index 23e55a737..2bd36220a 100644 --- a/test/Discord.Net.Tests/Tests.Migrations.cs +++ b/test/Discord.Net.Tests/Tests.Migrations.cs @@ -19,7 +19,7 @@ namespace Discord if (client == null) { client = new DiscordRestClient(); - await client.LoginAsync(TokenType.Bot, _config.Token, false).ConfigureAwait(false); + await client.LoginAsync(TokenType.Bot, _config.Token).ConfigureAwait(false); guild = await client.GetGuildAsync(_config.GuildId); } diff --git a/test/Discord.Net.Tests/Tests.Permissions.cs b/test/Discord.Net.Tests/Tests.Permissions.cs index 2f72f272d..239d0862e 100644 --- a/test/Discord.Net.Tests/Tests.Permissions.cs +++ b/test/Discord.Net.Tests/Tests.Permissions.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Xunit;