diff --git a/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md b/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md index 8117b2c3f..75b9f93a5 100644 --- a/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md +++ b/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md @@ -1,7 +1,5 @@ --- uid: Discord.Commands.PreconditionAttribute -seealso: - - linkId: Discord.Commands.ParameterPreconditionAttribute remarks: *content --- @@ -12,8 +10,6 @@ method-level for a command. --- uid: Discord.Commands.ParameterPreconditionAttribute -seealso: - - linkId: Discord.Commands.PreconditionAttribute remarks: *content --- @@ -31,11 +27,11 @@ The following example creates a precondition to see if the user has sufficient role required to access the command. ```cs -public class RequireRoleAtribute : PreconditionAttribute +public class RequireRoleAttribute : PreconditionAttribute { private readonly ulong _roleId; - public RequireRoleAtribute(ulong roleId) + public RequireRoleAttribute(ulong roleId) { _roleId = roleId; } 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/_template/lastmodified/plugins/LastModifiedPostProcessor.dll b/docs/_template/lastmodified/plugins/LastModifiedPostProcessor.dll new file mode 100644 index 000000000..c527ec698 Binary files /dev/null and b/docs/_template/lastmodified/plugins/LastModifiedPostProcessor.dll differ diff --git a/docs/docfx.json b/docs/docfx.json index ccd271999..cb6a36360 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -35,9 +35,10 @@ "dest": "_site", "template": [ "default", - "_template/light-dark-theme" + "_template/light-dark-theme", + "_template/lastmodified" ], - "postProcessors": [ "ExtractSearchIndex" ], + "postProcessors": [ "ExtractSearchIndex", "LastModifiedPostProcessor" ], "overwrite": "_overwrites/**/**.md", "globalMetadata": { "_appTitle": "Discord.Net Documentation", diff --git a/docs/faq/basics/basic-operations.md b/docs/faq/basics/basic-operations.md index 518a7426d..aef28a683 100644 --- a/docs/faq/basics/basic-operations.md +++ b/docs/faq/basics/basic-operations.md @@ -8,7 +8,7 @@ title: Questions about Basic Operations ## How should I safely check a type? > [!WARNING] -> Direct casting (e.g. `(Type)type`) is **the least recommended** +> 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. > @@ -28,9 +28,9 @@ A good and safe casting example: ## How do I send a message? > [!TIP] -> The [GetChannel] method by default returns an [IChannel]. -> This means channels such as [IVoiceChannel], [ICategoryChannel] -> can be returned. This is why that you cannot send message +> 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] diff --git a/docs/faq/basics/client-basics.md b/docs/faq/basics/client-basics.md index 376667ca0..48386206c 100644 --- a/docs/faq/basics/client-basics.md +++ b/docs/faq/basics/client-basics.md @@ -8,11 +8,12 @@ title: Basic Questions about Client ## My client keeps returning 401 upon logging in! > [!WARNING] -> Userbot/selfbot (logging in with a user token) is not -> officially supported with this library. +> 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. > -> 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. @@ -20,20 +21,23 @@ There are few possible reasons why this may occur. 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 tokens is different from a *client secret*. + 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 in order for you to get access to any server +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's best to wait for all +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. @@ -42,7 +46,7 @@ event is triggered, then you can proceed to do whatever you like. ## 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, +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). diff --git a/docs/faq/basics/getting-started.md b/docs/faq/basics/getting-started.md index 08972ba2e..6e39aeed0 100644 --- a/docs/faq/basics/getting-started.md +++ b/docs/faq/basics/getting-started.md @@ -8,25 +8,27 @@ title: Beginner Questions / How to Get Started ## How do I add my bot to my server/guild? You can do so by using the [permission calculator] provided -by FiniteReality. -This tool allows you to set the permissions that the bot will be -added with, and invite the bot into your guild. With this method, -bots will also be assigned their own special roles that normal users -cannot use; this is what we call a `Managed` role, and this is a much -safer method of permission management than to create a role that any -users can be assigned to. - +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 applies to both user and bot -accounts. That also means that you should never ever 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. +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? diff --git a/docs/faq/commands/Commands.md b/docs/faq/commands/commands.md similarity index 55% rename from docs/faq/commands/Commands.md rename to docs/faq/commands/commands.md index 4811b02be..2c905eaad 100644 --- a/docs/faq/commands/Commands.md +++ b/docs/faq/commands/commands.md @@ -5,37 +5,35 @@ title: Questions about Commands # Command-related Questions -## How can I restrict some of my commands so only certain users can execute them? +## 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` etc.*). +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 own custom precondition or use +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 own +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 -## I'm getting an error about `Assembly#GetEntryAssembly`. +## Why am I getting an error about `Assembly.GetEntryAssembly`? -You may be confusing [CommandService#AddModulesAsync] with -[CommandService#AddModuleAsync]. The former is used to add modules -via the assembly, while the latter is used to add a single module. - -[CommandService#AddModulesAsync]: xref:Discord.Commands.CommandService.AddModulesAsync* -[CommandService#AddModuleAsync]: xref:Discord.Commands.CommandService.AddModuleAsync* +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 -don't have to add quotes around the text for the text to be +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. @@ -47,13 +45,14 @@ the last parameter. ## 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. This means that they are spawned -every time when a request is received, and are killed from memory -when the execution finishes. This is why you cannot store persistent -data inside a module. To workaround this, consider using a service. - -Service is often used to hold data externally, so that they will -persist throughout execution. Think of it like a chest that holds +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, @@ -66,25 +65,47 @@ A brief example of service and dependency injection can be seen below. [Dependency Injection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection [video]: https://www.youtube.com/watch?v=QtDTfn8YxXg -## I have a long-running Task in my command, and Discord.Net keeps saying that a `MessageReceived` handler is blocking the gateway. What gives? +## 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. -By default, all commands are executed on the same thread as the -gateway task, which is responsible for keeping the connection from -your client to Discord alive. When you execute a command, -this blocks the gateway from communicating for as long as the command -task is being executed. The library will warn you about any long -running event handler (in this case, the command handler) that -persists for **more than 3 seconds**. +> [!NOTE] +> In rare cases, runtime errors can also cause blockage, usually +> associated with Mono, which is not supported by this library. -To resolve this, the library has designed a flag called [RunMode]. +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` (default) +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 `CommandAttribute` or by setting the global default with the [DefaultRunMode] flag under `CommandServiceConfig`. # [CommandAttribute](#tab/cmdattrib) @@ -99,15 +120,6 @@ the [DefaultRunMode] flag under `CommandServiceConfig`. *** -> [!IMPORTANT] -> While specifying `RunMode.Async` allows the command to be spun off -> to a different thread instead of the gateway thread, -> keep in mind that there will be **potential consequences** -> by doing so. Before applying this flag, please -> consider whether it is necessary to do so. -> -> Further details regarding `RunMode.Async` can be found below. - [RunMode]: xref:Discord.Commands.RunMode [CommandAttribute]: xref:Discord.Commands.CommandAttribute [DefaultRunMode]: xref:Discord.Commands.CommandServiceConfig.DefaultRunMode @@ -115,16 +127,15 @@ the [DefaultRunMode] flag under `CommandServiceConfig`. ## 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 `ExecuteAsyncInternalAsync`, the task -that is used to invoke the command task, to be finished on a -different thread. This means that [ExecuteAsync] will be forced to -return a successful [ExecuteResult] regardless of the actual -execution result. +[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 race condition. -2. Unnecessary overhead caused by [async state machine]. +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). @@ -137,7 +148,7 @@ For #3, in Discord.Net 2.0, the library introduces a new event called **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 +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 @@ -146,6 +157,6 @@ For #4, exceptions are caught in [CommandService#Log] event under [ExecuteResult]: xref:Discord.Commands.ExecuteResult [RuntimeResult]: xref:Discord.Commands.RuntimeResult [CommandExecuted]: xref:Discord.Commands.CommandService.CommandExecuted -[CommandService#Log]: xref:Discord.Commands.CommandService.Log +[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/Remainder.cs b/docs/faq/commands/samples/Remainder.cs index a28c782e0..337fb6e45 100644 --- a/docs/faq/commands/samples/Remainder.cs +++ b/docs/faq/commands/samples/Remainder.cs @@ -16,5 +16,5 @@ public Task EchoAsync(string text) => ReplyAsync(text); // 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. +// e.g., // !echo "Coffee Cake" \ No newline at end of file diff --git a/docs/faq/misc/Glossary.md b/docs/faq/misc/glossary.md similarity index 100% rename from docs/faq/misc/Glossary.md rename to docs/faq/misc/glossary.md diff --git a/docs/faq/misc/Legacy.md b/docs/faq/misc/legacy.md similarity index 83% rename from docs/faq/misc/Legacy.md rename to docs/faq/misc/legacy.md index ef4caa1cd..fec6ba24d 100644 --- a/docs/faq/misc/Legacy.md +++ b/docs/faq/misc/legacy.md @@ -7,8 +7,8 @@ title: Questions about Legacy Versions ## X, Y, Z does not work! It doesn't return a valid value anymore -If you're currently using an older version in stable branch, please -upgrade to the latest pre-release version to ensure maximum +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. diff --git a/docs/guides/commands/intro.md b/docs/guides/commands/intro.md index 8036ed09e..e2fad73e8 100644 --- a/docs/guides/commands/intro.md +++ b/docs/guides/commands/intro.md @@ -107,7 +107,7 @@ be found in @Guides.Commands.TypeReaders. #### Optional Parameters Parameters, by default, are always required. To make a parameter -optional, give it a default value (i.e. `int num = 0`). +optional, give it a default value (i.e., `int num = 0`). #### Parameters with Spaces diff --git a/docs/guides/commands/post-execution.md b/docs/guides/commands/post-execution.md index 5f19147cb..267f84b8f 100644 --- a/docs/guides/commands/post-execution.md +++ b/docs/guides/commands/post-execution.md @@ -6,18 +6,18 @@ title: Post-command Execution Handling # Preface When developing commands, you may want to consider building a -post-execution handling system so you can have a finer control +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've shown the following +If you recall, in the [Command Guide], we have shown the following example for executing and handling commands, [!code[Command Handler](samples/command_handler.cs)] You may notice that after we perform [ExecuteAsync], we store the -result and print it to the chat. This is essentially the most -basic post-execution handling. +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, @@ -25,8 +25,8 @@ With this in mind, we could start doing things like the following, However, this may not always be preferred, because you are creating your post-execution logic *with* the essential command -handler. This could lead to messy code and could potentially be a -violation of the SRP (Single Responsibility Principle). +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 @@ -37,8 +37,8 @@ about the impact in the [FAQ](xref:FAQ.Commands). 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 **without -any parsing or precondition failure**. This means this event can be +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. @@ -52,7 +52,7 @@ next? We can take this further by using [RuntimeResult]. ### RuntimeResult -`RuntimeResult` was originally introduced in 1.0 to allow +`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. @@ -62,7 +62,7 @@ However, it wasn't widely adopted due to the aforementioned 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 own version of +The best way to make use of it is to create your version of `RuntimeResult`. You can achieve this by inheriting the `RuntimeResult` class. @@ -71,16 +71,16 @@ of `RuntimeResult`, [!code[Base Use](samples/customresult_base.cs)] -The sky's the limit from here. You can add any additional information -you'd like regarding the execution result. +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 own result type or other +For example, you may want to add your result type or other helpful information regarding the execution, or something simple like static methods to help you create return types easily. [!code[Extended Use](samples/customresult_extended.cs)] -After you're done creating your own [RuntimeResult], you can +After you're done creating your [RuntimeResult], you can implement it in your command by marking the command return type to `Task`. @@ -100,12 +100,12 @@ And now we can check for it in our [CommandExecuted] handler: ## CommandService.Log Event We have so far covered the handling of various result types, but we -haven't talked about what to do if the command enters a catastrophic -failure (i.e. exceptions). To resolve this, we can make use of the +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 will be caught and -be sent to the Log event under the [LogMessage.Exception] property +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. diff --git a/docs/guides/concepts/connections.md b/docs/guides/concepts/connections.md index 324b67566..99ea45756 100644 --- a/docs/guides/concepts/connections.md +++ b/docs/guides/concepts/connections.md @@ -11,14 +11,14 @@ stopped. To start a connection, invoke the `StartAsync` method on a client that supports a WebSocket connection; to end a connection, invoke the -`StopAsync` method. This will gracefully close any open WebSocket or +`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.** +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. @@ -29,7 +29,7 @@ ready to be used. 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 @@ -41,8 +41,8 @@ sync, and has a complete guild cache. > [!TIP] > 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. +> 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 @@ -50,6 +50,6 @@ and resume a connection. Don't worry about trying to maintain your own connections, the connection manager is designed to be bulletproof and never fail - if -your client doesn't manage to reconnect, you've found a bug! +your client does not manage to reconnect, you have found a bug! [events]: xref:Guides.Concepts.Events diff --git a/docs/guides/concepts/deployment.md b/docs/guides/concepts/deployment.md index d26abee34..eea747817 100644 --- a/docs/guides/concepts/deployment.md +++ b/docs/guides/concepts/deployment.md @@ -68,7 +68,7 @@ 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` +* `dotnet publish -c Release` Additionally, you may want to target a specific platform when publishing the application so you may use the application without @@ -80,7 +80,7 @@ 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` +* `dotnet publish -c Release -r win10-x64` [.NET Core application deployment]: https://docs.microsoft.com/en-us/dotnet/core/deploying/ [Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog \ No newline at end of file diff --git a/docs/guides/concepts/entities.md b/docs/guides/concepts/entities.md index 7c66c7a57..7415f043a 100644 --- a/docs/guides/concepts/entities.md +++ b/docs/guides/concepts/entities.md @@ -56,7 +56,7 @@ DiscordSocketClient. > [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 +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. diff --git a/docs/guides/concepts/events.md b/docs/guides/concepts/events.md index d8e586681..293b5dc72 100644 --- a/docs/guides/concepts/events.md +++ b/docs/guides/concepts/events.md @@ -74,7 +74,7 @@ object. [Cacheable]: xref:Discord.Cacheable`2 > [!NOTE] -> Many events relating to a Message entity (i.e. `MessageUpdated` and +> 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. diff --git a/docs/guides/concepts/logging.md b/docs/guides/concepts/logging.md index dba78006f..b92d2bd53 100644 --- a/docs/guides/concepts/logging.md +++ b/docs/guides/concepts/logging.md @@ -16,7 +16,7 @@ section. > [!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 +> 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. diff --git a/docs/guides/getting_started/first-bot.md b/docs/guides/getting_started/first-bot.md index 622709eca..a5a83aa16 100644 --- a/docs/guides/getting_started/first-bot.md +++ b/docs/guides/getting_started/first-bot.md @@ -27,8 +27,8 @@ Discord Applications Portal first. ![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!** +7. (Optional) If this bot will be public, check "Public Bot." + * **Do not tick any other options!** [Discord Applications Portal]: https://discordapp.com/developers/applications/me @@ -38,15 +38,18 @@ 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 application's **Client ID**. +2. Navigate to `OAuth2 URL Generator` and click on `Generate OAuth2 URL`. - ![Step 2](images/intro-client-id.png) + ![Step 2](images/intro-generate-oauth.png) -3. Create an OAuth2 authorization URL +3. Select the permissions that you wish to assign your bot with. - - `https://discordapp.com/oauth2/authorize?client_id=&scope=bot` + > [!NOTE] + > This will assign the bot with a special "managed" role that no + > one else can use. The permissions can be changed later in the + > roles settings if you ever change your mind! -4. Open the authorization URL in your browser. +4. Open the generated authorization URL in your browser. 5. Select a server. 6. Click on authorize. diff --git a/docs/guides/getting_started/images/install-vs-nuget.png b/docs/guides/getting_started/images/install-vs-nuget.png index 64da79a9f..ecf627d11 100644 Binary files a/docs/guides/getting_started/images/install-vs-nuget.png and b/docs/guides/getting_started/images/install-vs-nuget.png differ diff --git a/docs/guides/getting_started/images/intro-client-id.png b/docs/guides/getting_started/images/intro-client-id.png deleted file mode 100644 index e370aa2ec..000000000 Binary files a/docs/guides/getting_started/images/intro-client-id.png and /dev/null differ diff --git a/docs/guides/getting_started/images/intro-copy-oauth.png b/docs/guides/getting_started/images/intro-copy-oauth.png new file mode 100644 index 000000000..31dc6f743 Binary files /dev/null and b/docs/guides/getting_started/images/intro-copy-oauth.png differ diff --git a/docs/guides/getting_started/images/intro-create-app.png b/docs/guides/getting_started/images/intro-create-app.png index 7aceb84b4..94f229a73 100644 Binary files a/docs/guides/getting_started/images/intro-create-app.png and b/docs/guides/getting_started/images/intro-create-app.png differ diff --git a/docs/guides/getting_started/images/intro-create-bot.png b/docs/guides/getting_started/images/intro-create-bot.png index 0522358cf..cc4e55ec3 100644 Binary files a/docs/guides/getting_started/images/intro-create-bot.png and b/docs/guides/getting_started/images/intro-create-bot.png differ diff --git a/docs/guides/getting_started/images/intro-generate-oauth.png b/docs/guides/getting_started/images/intro-generate-oauth.png new file mode 100644 index 000000000..5d7100ad2 Binary files /dev/null and b/docs/guides/getting_started/images/intro-generate-oauth.png differ diff --git a/docs/guides/getting_started/installing.md b/docs/guides/getting_started/installing.md index c0980101e..b174a3829 100644 --- a/docs/guides/getting_started/installing.md +++ b/docs/guides/getting_started/installing.md @@ -40,7 +40,7 @@ Release builds of Discord.Net will be published to the Development builds of Discord.Net, as well as add-ons, will be published to our [MyGet feed]. -Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` +* Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` Not sure how to add a direct feed? See how [with Visual Studio] or [without Visual Studio]. diff --git a/docs/guides/getting_started/terminology.md b/docs/guides/getting_started/terminology.md index a03dc8fbf..61a226dcf 100644 --- a/docs/guides/getting_started/terminology.md +++ b/docs/guides/getting_started/terminology.md @@ -28,13 +28,13 @@ 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`). +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`). +with `Rpc` (e.g., `RpcChannel`). `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 +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 index 165a7949a..c22edd1f7 100644 --- a/docs/guides/introduction/intro.md +++ b/docs/guides/introduction/intro.md @@ -17,7 +17,7 @@ understand these topics to some extent before proceeding. Here are some examples: -1. [Official quick start guide] +1. [Official samples] 2. [Official template] > [!NOTE] @@ -26,7 +26,7 @@ Here are some examples: > It is not meant to be something that will work out of the box. [Official template]: https://github.com/foxbot/DiscordBotBase/tree/csharp/src/DiscordBot -[Official quick start guide]: https://github.com/RogueException/Discord.Net/blob/dev/docs/guides/getting_started/samples/first-bot/structure.cs +[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/ diff --git a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs index efdb2c5b2..9b750809b 100644 --- a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs @@ -6,6 +6,7 @@ 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 { diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index 58d9c8ba4..316b2729e 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -3,20 +3,29 @@ using System.Threading.Tasks; namespace Discord.Commands { - /// Requires the module or class to pass the specified precondition before execution can begin. + /// + /// 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. + /// 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 = or not at all will + /// 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/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index 072f10e0f..f2bd717e4 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -54,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) @@ -68,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/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 39cae845e..24db6e9b5 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -129,7 +129,7 @@ namespace Discord.Commands /// The type of module. /// /// The for your dependency injection solution, if using one - otherwise, pass - /// . + /// null. /// /// /// A built module. @@ -144,7 +144,7 @@ namespace Discord.Commands /// The type of module. /// /// The for your dependency injection solution, if using one - otherwise, pass - /// . + /// null. /// /// /// A built module. @@ -183,7 +183,7 @@ namespace Discord.Commands /// The containing command modules. /// /// An for your dependency injection solution, if using one - otherwise, pass - /// . + /// null. /// /// /// A collection of built modules. diff --git a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs index cd7a9d744..6cb9ca6e6 100644 --- a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs @@ -6,19 +6,23 @@ using System.Threading.Tasks; namespace Discord.Commands { + /// + /// A for parsing objects implementing . + /// + /// The type to be checked; must implement . public class ChannelTypeReader : TypeReader where T : class, IChannel { + /// public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { if (context.Guild != null) { var results = new Dictionary(); var channels = await context.Guild.GetChannelsAsync(CacheMode.CacheOnly).ConfigureAwait(false); - ulong id; //By Mention (1.0) - if (MentionUtils.TryParseChannel(input, out id)) + if (MentionUtils.TryParseChannel(input, out ulong id)) AddResult(results, await context.Guild.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); //By Id (0.9) diff --git a/src/Discord.Net.Commands/Readers/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/RoleTypeReader.cs b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs index c199033fa..4c9aaf4d8 100644 --- a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs @@ -6,9 +6,14 @@ 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) { if (context.Guild != null) diff --git a/src/Discord.Net.Commands/Readers/TypeReader.cs b/src/Discord.Net.Commands/Readers/TypeReader.cs index af45a0aac..037213ae9 100644 --- a/src/Discord.Net.Commands/Readers/TypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TypeReader.cs @@ -1,10 +1,22 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.Commands { + /// + /// Defines a reader class that parses user input into a specified type. + /// public abstract class TypeReader { + /// + /// Attempts to parse the into the desired type. + /// + /// The context of the command. + /// The raw input of the command. + /// The service collection used for dependency injection. + /// + /// An awaitable Task containing the result of the type reading process. + /// public abstract Task ReadAsync(ICommandContext context, string input, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index 7feab50ad..6d9f1dd8c 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -7,9 +7,14 @@ 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(); @@ -72,8 +77,8 @@ namespace Discord.Commands .ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f)) .ConfigureAwait(false); - foreach (var guildUser in guildUsers.Where(x => string.Equals(input, (x as IGuildUser).Nickname, StringComparison.OrdinalIgnoreCase))) - AddResult(results, guildUser as T, (guildUser as IGuildUser).Nickname == input ? 0.60f : 0.50f); + foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase))) + AddResult(results, guildUser as T, guildUser.Nickname == input ? 0.60f : 0.50f); } if (results.Count > 0) diff --git a/src/Discord.Net.Commands/Results/RuntimeResult.cs b/src/Discord.Net.Commands/Results/RuntimeResult.cs index a7febd68e..e4c86fc23 100644 --- a/src/Discord.Net.Commands/Results/RuntimeResult.cs +++ b/src/Discord.Net.Commands/Results/RuntimeResult.cs @@ -8,7 +8,7 @@ namespace Discord.Commands /// /// Initializes a new class with the type of error and reason. /// - /// The type of failure, or if none. + /// The type of failure, or null if none. /// The reason of failure. protected RuntimeResult(CommandError? error, string reason) { diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index 1b75d0633..0fefc9a0d 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -8,13 +8,26 @@ namespace Discord public static class CDN { /// - /// Returns the Discord developer application icon. + /// 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 the user avatar URL based on the and . + /// 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) @@ -26,34 +39,64 @@ namespace Discord /// 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 the icon URL associated with the given guild ID. + /// 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 the guild splash URL associated with the given guild and splash ID. + /// 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 the channel icon URL associated with the given guild and icon ID. + /// 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 the emoji URL based on the emoji ID. + /// 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 the rich presence asset URL based on the asset ID and . + /// 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, ""); @@ -61,10 +104,21 @@ namespace Discord } /// - /// Returns the Spotify album URL based on the album art ID. + /// 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/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index b3cdddfa5..0e46f2960 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -8,68 +8,116 @@ namespace Discord public class DiscordConfig { /// - /// Returns the gateway version Discord.Net uses. + /// Returns the API version Discord.Net uses. /// + /// + /// A 32-bit integer representing the API version that Discord.Net uses to communicate with Discord. + /// A list of available API version can be seen on the official + /// Discord API documentation + /// . + /// public const int APIVersion = 6; /// /// Gets the Discord.Net version, including the build number. /// + /// + /// A string containing the detailed version information, including its build number; Unknown when + /// the version fails to be fetched. + /// public static string Version { get; } = typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion ?? - typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? + 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. + /// 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; + public const int MaxAuditLogEntriesPerBatch = 100; /// /// 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. /// + /// + /// The currently set for logging level. + /// public LogSeverity LogLevel { get; set; } = LogSeverity.Info; /// /// 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/GameAsset.cs b/src/Discord.Net.Core/Entities/Activities/GameAsset.cs index b0c8ea975..467b2885f 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameAsset.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameAsset.cs @@ -19,7 +19,7 @@ namespace Discord public string ImageId { get; internal set; } /// - /// Returns the image URL of the asset, or when the application ID does not exist. + /// Returns the image URL of the asset, or null when the application ID does not exist. /// public string GetImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => ApplicationId.HasValue ? CDN.GetRichAssetUrl(ApplicationId.Value, ImageId, size, format) : null; diff --git a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs index beec4eebc..23f88687d 100644 --- a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs @@ -13,36 +13,65 @@ namespace Discord /// /// 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() { } @@ -50,6 +79,10 @@ namespace Discord /// /// 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/AuditLogs/ActionType.cs b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs new file mode 100644 index 000000000..e5a4ff30a --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// The action type within a + /// + public enum ActionType + { + GuildUpdated = 1, + + ChannelCreated = 10, + ChannelUpdated = 11, + ChannelDeleted = 12, + + OverwriteCreated = 13, + OverwriteUpdated = 14, + OverwriteDeleted = 15, + + Kick = 20, + Prune = 21, + Ban = 22, + Unban = 23, + + MemberUpdated = 24, + MemberRoleUpdated = 25, + + RoleCreated = 30, + RoleUpdated = 31, + RoleDeleted = 32, + + InviteCreated = 40, + InviteUpdated = 41, + InviteDeleted = 42, + + WebhookCreated = 50, + WebhookUpdated = 51, + WebhookDeleted = 52, + + EmojiCreated = 60, + EmojiUpdated = 61, + EmojiDeleted = 62, + + MessageDeleted = 72 + } +} diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs new file mode 100644 index 000000000..47aaffb26 --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// 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 new file mode 100644 index 000000000..b85730a1d --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents an entry in an audit log + /// + public interface IAuditLogEntry : IEntity + { + /// + /// The action which occured to create this entry + /// + ActionType Action { get; } + + /// + /// The data for this entry. May be if no data was available. + /// + IAuditLogData Data { get; } + + /// + /// The user responsible for causing the changes + /// + IUser User { get; } + + /// + /// The reason behind the change. May be if no reason was provided. + /// + string Reason { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs index fdbd0447c..a0edfc796 100644 --- a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs @@ -3,14 +3,7 @@ namespace Discord /// /// 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 { /// diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs index fdbe77653..3d11e2c6f 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -24,7 +24,7 @@ namespace Discord /// Gets the parent ID (category) of this channel in the guild's channel list. /// /// - /// The parent category ID associated with this channel, or if none is set. + /// The parent category ID associated with this channel, or null if none is set. /// ulong? CategoryId { get; } /// @@ -57,16 +57,16 @@ namespace Discord /// Creates a new invite to this channel. /// /// - /// The time (in seconds) until the invite expires. Set to to never expire. + /// 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 to have unlimited uses. + /// The max amount of times this invite may be used. Set to null to have unlimited uses. /// /// - /// If , a user accepting this invite will be kicked from the guild after closing their client. + /// If true, a user accepting this invite will be kicked from the guild after closing their client. /// /// - /// If , don't try to reuse a similar invite (useful for creating many unique one time use invites). + /// 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. @@ -86,12 +86,12 @@ namespace Discord Task ModifyAsync(Action func, RequestOptions options = null); /// - /// Gets the permission overwrite for a specific role, or if one does not exist. + /// Gets the permission overwrite for a specific role, or null if one does not exist. /// /// The role to get the overwrite from. OverwritePermissions? GetPermissionOverwrite(IRole role); /// - /// Gets the permission overwrite for a specific user, or if one does not exist. + /// Gets the permission overwrite for a specific user, or null if one does not exist. /// /// The user to get the overwrite from. OverwritePermissions? GetPermissionOverwrite(IUser user); diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index 9837a3048..837e90604 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -17,10 +17,13 @@ namespace Discord /// Whether the message should be read aloud by Discord or not. /// The to be sent. /// The options to be used when sending the request. + /// + /// An awaitable Task containing the message sent to the channel. + /// Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); #if FILESYSTEM /// - /// Sends a file to this message channel, with an optional caption. + /// Sends a file to this message channel with an optional caption. /// /// The file path of the file. /// The message to be sent. @@ -32,10 +35,13 @@ namespace Discord /// upload the file and refer to the file with "attachment://filename.ext" in the /// . /// + /// + /// An awaitable Task containing the message sent to the channel. + /// Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); #endif /// - /// Sends a file to this message channel, with an optional caption. + /// Sends a file to this message channel with an optional caption. /// /// The of the file to be sent. /// The name of the attachment. @@ -48,16 +54,19 @@ namespace Discord /// upload the file and refer to the file with "attachment://filename.ext" in the /// . /// + /// + /// An awaitable Task containing the message sent to the channel. + /// Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); /// - /// Gets a message from this message channel with the given id, or if not found. + /// Gets a message from this message channel with the given id, or null if not found. /// /// The ID of the message. /// The that determines whether the object should be fetched from cache. /// The options to be used when sending the request. /// - /// The message gotten from either the cache or the download, or if none is found. + /// The message gotten from either the cache or the download, or null if none is found. /// Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); @@ -108,7 +117,7 @@ namespace Discord /// /// The options to be used when sending the request. /// - /// A collection of messages. + /// An awaitable Task containing a collection of messages. /// Task> GetPinnedMessagesAsync(RequestOptions options = null); diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index d1b2465ad..29c67bf6b 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -14,7 +14,7 @@ namespace Discord /// Determines whether the channel is NSFW. /// /// - /// if the channel has the NSFW flag enabled; otherwise, . + /// true if the channel has the NSFW flag enabled; otherwise, false. /// bool IsNsfw { get; } @@ -22,19 +22,29 @@ namespace Discord /// Gets the current topic for this text channel. /// /// - /// The topic set in the channel, or if none is set. + /// The topic set in the channel, or null if none is set. /// string Topic { get; } /// /// Bulk-deletes multiple messages. /// + /// + /// + /// This method can only remove messages that are posted within 14 days! + /// + /// /// The messages to be bulk-deleted. /// The options to be used when sending the request. Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); /// /// Bulk-deletes multiple messages. /// + /// + /// + /// This method can only remove messages that are posted within 14 days! + /// + /// /// The IDs of the messages to be bulk-deleted. /// The options to be used when sending the request. Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null); @@ -62,7 +72,7 @@ namespace Discord /// The ID of the webhook. /// The options to be used when sending the request. /// - /// A webhook associated with the , or if not found. + /// A webhook associated with the , or null if not found. /// Task GetWebhookAsync(ulong id, RequestOptions options = null); /// diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs index f69e6e22e..e1efb1a86 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -14,7 +14,7 @@ namespace Discord int Bitrate { get; } /// /// Gets the max amount of users allowed to be connected to this channel at one time, or - /// if none is set. + /// null if none is set. /// int? UserLimit { get; } diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs index b68c416b7..03b56b26c 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -1,8 +1,11 @@ +using System; + namespace Discord { /// /// Properties that are used to modify an with the specified changes. /// + /// public class TextChannelProperties : GuildChannelProperties { /// diff --git a/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs index c285560df..46e8f8550 100644 --- a/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs @@ -10,7 +10,7 @@ namespace Discord /// public Optional Bitrate { get; set; } /// - /// Gets or sets the maximum number of users that can be present in a channel, or if none. + /// 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/EmoteProperties.cs b/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs index 721345afe..de457a0dc 100644 --- a/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs +++ b/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs @@ -5,6 +5,7 @@ namespace Discord /// /// Properties that are used to modify an with the specified changes. /// + /// public class EmoteProperties { /// diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs index 68925b103..2977cd10c 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs @@ -10,11 +10,11 @@ namespace Discord /// public Optional Enabled { get; set; } /// - /// Sets the channel that the invite should place its users in, if not . + /// Sets the channel that the invite should place its users in, if not null. /// public Optional Channel { get; set; } /// - /// Sets the channel the invite should place its users in, if not . + /// 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/GuildProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs index 3c136b579..eccd852dd 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs @@ -3,28 +3,19 @@ namespace Discord /// /// Properties that are used to modify an with the specified changes. /// - /// - /// - /// await Context.Guild.ModifyAsync(async x => - /// { - /// x.Name = "aaaaaah"; - /// }); - /// - /// - /// + /// public class GuildProperties { - public Optional Username { get; set; } /// - /// Gets or sets the name of the Guild. + /// Gets or sets the name of the guild. Must be within 100 characters. /// public Optional Name { get; set; } /// - /// Gets or sets the region for the Guild's voice connections. + /// Gets or sets the region for the guild's voice connections. /// public Optional Region { get; set; } /// - /// Gets or sets 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; } /// diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 057b94788..581ca551e 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -13,17 +13,23 @@ namespace Discord /// /// 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. + /// automatically moved to the AFK voice channel. /// + /// + /// The amount of time in seconds for a user to be marked as inactive and moved into the AFK voice channel. + /// int AFKTimeout { get; } /// /// Determines if this guild is embeddable (i.e. can use widget). /// /// - /// if this guild can be embedded via widgets; otherwise . + /// true if this guild can be embedded via widgets; otherwise false. /// bool IsEmbeddable { get; } /// @@ -34,38 +40,61 @@ namespace Discord /// 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. /// + /// + /// The level of requirements. + /// VerificationLevel VerificationLevel { get; } /// - /// Returns the ID of this guild's icon, or if none is 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 of this guild's icon, or if none is 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 if none is 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 of this guild's splash image, or if none is 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; } /// /// 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. + /// /// - /// Returns if this guild is currently connected and ready to be used. Only applies - /// to the WebSocket client. + /// 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, or if none is set. + /// Gets the ID of the AFK voice channel for this guild, or null if none is set. /// ulong? AFKChannelId { get; } /// @@ -73,11 +102,11 @@ namespace Discord /// ulong DefaultChannelId { get; } /// - /// Gets the ID of the embed channel set in the widget settings of this guild, or if none is set. + /// Gets the ID of the embed channel set in the widget settings of this guild, or null if none is set. /// ulong? EmbedChannelId { get; } /// - /// Gets the ID of the channel where randomized welcome messages are sent, or if none is set. + /// Gets the ID of the channel where randomized welcome messages are sent, or null if none is set. /// ulong? SystemChannelId { get; } /// @@ -143,7 +172,7 @@ namespace Discord /// Task ModifyEmbedAsync(Action func, RequestOptions options = null); /// - /// Bulk modifies the order of channels in 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. @@ -152,17 +181,21 @@ namespace Discord /// Task ReorderChannelsAsync(IEnumerable args, RequestOptions options = null); /// - /// Bulk modifies the order of roles in 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. - Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null); /// /// An awaitable . /// + Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null); /// - /// Leaves this guild. If you are the owner, use instead. + /// Leaves this guild. /// + /// + /// This method will make the currently logged-in user leave the guild. If the user is the owner, use + /// instead. + /// /// The options to be used when sending the request. /// /// An awaitable . @@ -178,7 +211,25 @@ namespace Discord /// Task> GetBansAsync(RequestOptions options = null); /// - /// Bans the provided user from this guild and optionally prunes their recent messages. + /// Gets a ban object for a banned user. + /// + /// The banned user. + /// + /// An awaitable containing the 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. + /// + /// An awaitable containing the 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 user from this guild and optionally prunes their recent messages. /// /// The user to ban. /// @@ -192,9 +243,9 @@ namespace Discord /// 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. + /// Bans the user from this guild and optionally prunes their recent messages. /// - /// The ID of the user to ban. + /// The snowflake ID of the user to ban. /// /// The number of days to remove messages from this user for - must be between [0, 7]. /// @@ -245,7 +296,7 @@ namespace Discord /// The options to be used when sending the request. /// /// An awaitable containing the generic channel with the specified ID, or - /// if none is found. + /// null if none is found. /// Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// @@ -260,7 +311,7 @@ namespace Discord /// Task> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// - /// Gets a text channel in this guild with the provided ID, or if not found. + /// Gets a text channel in this guild with the provided ID, or null if not found. /// /// The text channel ID. /// @@ -269,7 +320,7 @@ namespace Discord /// The options to be used when sending the request. /// /// An awaitable containing the text channel with the specified ID, or - /// if none is found. + /// null if none is found. /// Task GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// @@ -304,7 +355,7 @@ namespace Discord /// The options to be used when sending the request. /// /// An awaitable containing the voice channel with the specified ID, or - /// if none is found. + /// null if none is found. /// Task GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// @@ -316,7 +367,7 @@ namespace Discord /// The options to be used when sending the request. /// /// An awaitable containing the AFK voice channel set within this guild, or - /// if none is set. + /// null if none is set. /// Task GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// @@ -328,7 +379,7 @@ namespace Discord /// The options to be used when sending the request. /// /// An awaitable containing the system channel within this guild, or - /// if none is set. + /// null if none is set. /// Task GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// @@ -340,7 +391,7 @@ namespace Discord /// The options to be used when sending the request. /// /// An awaitable containing the first viewable text channel in this guild, or - /// if none is found. + /// null if none is found. /// Task GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// @@ -352,7 +403,7 @@ namespace Discord /// The options to be used when sending the request. /// /// An awaitable containing the embed channel set within the server's widget settings, or - /// if none is set. + /// null if none is set. /// Task GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// @@ -389,12 +440,19 @@ namespace Discord /// /// Gets a collection of all invites to this guild. /// + /// The options to be used when sending the request. + /// + /// An awaitable containing a collection of invites found within this guild. + /// Task> GetInvitesAsync(RequestOptions options = null); /// - /// Gets the role in this guild with the provided ID, or if not found. + /// Gets a role in this guild. /// /// The role ID. + /// + /// A role that matches the provided snowflake identifier; null if none is found. + /// IRole GetRole(ulong id); /// /// Creates a new role with the provided name. @@ -412,25 +470,30 @@ namespace Discord /// /// Gets a collection of all users in this guild. /// - /// The that determines whether the object should be fetched from - /// cache. + /// + /// This method retrieves all users found within this guild. + /// + /// This may return an incomplete list on the WebSocket implementation. + /// + /// + /// + /// The that determines whether the object should be fetched from cache. + /// /// The options to be used when sending the request. /// /// An awaitable containing a collection of users found within this guild. /// - /// - /// This may return an incomplete list on the WebSocket implementation because Discord only sends offline - /// users on large guilds. - /// Task> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// - /// Gets the user in this guild with the provided ID, or if not found. + /// Gets a user found in this guild. /// - /// The user ID. - /// The that determines whether the object should be fetched from cache. + /// The user ID to search for. + /// The that determines whether the object should be fetched from + /// cache. /// The options to be used when sending the request. /// - /// An awaitable containing the guild user with the specified ID, otherwise . + /// An awaitable containing the guild user with the specified ID; null if none is + /// found. /// Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// @@ -459,25 +522,33 @@ namespace Discord /// Task DownloadUsersAsync(); /// - /// Removes all users from this guild if they have not logged on in a provided number of - /// or, if is , returns the - /// number of users that would be removed. + /// Prunes inactive users. /// + /// + /// This method removes all users that have not logged on in the provided number of days or, if + /// is true, returns the number of users that would be removed. + /// /// The number of days required for the users to be kicked. /// Whether this prune action is a simulation. /// The options to be used when sending the request. /// - /// An awaitable containing the number of users to be or has been removed from this guild. + /// An awaitable containing the number of users to be or has been removed from this + /// guild. /// Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); + /// Gets the specified number of audit log entries for this guild. + Task> GetAuditLogAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, + CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// - /// Gets the webhook in this guild with the provided ID, or if not found. + /// Gets a webhook found within this guild. /// /// The webhook ID. /// The options to be used when sending the request. /// - /// An awaitable containing the webhook with the specified ID, otherwise . + /// An awaitable containing the webhook with the specified ID; null if none is + /// found. /// Task GetWebhookAsync(ulong id, RequestOptions options = null); /// @@ -495,7 +566,8 @@ namespace Discord /// The guild emote ID. /// The options to be used when sending the request. /// - /// An awaitable containing the emote found with the specified ID, or if not found. + /// An awaitable containing the emote found with the specified ID; null if none is + /// found. /// Task GetEmoteAsync(ulong id, RequestOptions options = null); /// diff --git a/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs index 5da2ce5da..b6685edf6 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs @@ -7,11 +7,11 @@ namespace Discord /// string Name { get; } /// - /// Gets the icon URL associated with this guild, or if one is not set. + /// Gets the icon URL associated with this guild, or null if one is not set. /// string IconUrl { get; } /// - /// Returns if the current user owns this guild. + /// Returns true if the current user owns this guild. /// bool IsOwner { get; } /// diff --git a/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs index eef208905..8516036f1 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs @@ -14,19 +14,19 @@ namespace Discord /// string Name { get; } /// - /// Returns if this voice region is exclusive to VIP accounts. + /// Returns true if this voice region is exclusive to VIP accounts. /// bool IsVip { get; } /// - /// Returns if this voice region is the closest to your machine. + /// Returns true if this voice region is the closest to your machine. /// bool IsOptimal { get; } /// - /// Returns if this is a deprecated voice region (avoid switching to these). + /// Returns true if this is a deprecated voice region (avoid switching to these). /// bool IsDeprecated { get; } /// - /// Returns if this is a custom voice region (used for events/etc). + /// Returns true if this is a custom voice region (used for events/etc). /// bool IsCustom { get; } } diff --git a/src/Discord.Net.Core/Entities/Image.cs b/src/Discord.Net.Core/Entities/Image.cs index dd77ec6ae..5453027ac 100644 --- a/src/Discord.Net.Core/Entities/Image.cs +++ b/src/Discord.Net.Core/Entities/Image.cs @@ -35,7 +35,7 @@ namespace Discord /// is a zero-length string, contains only white space, or contains one or more invalid /// characters as defined by . /// - /// is . + /// 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 diff --git a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs index dcd3de997..1c6b1dd79 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs @@ -10,20 +10,20 @@ namespace Discord /// IUser Inviter { get; } /// - /// Returns if this invite was revoked. + /// Returns true if this invite was revoked. /// bool IsRevoked { get; } /// - /// Returns if users accepting this invite will be removed from the guild when they + /// Returns true if users accepting this invite will be removed from the guild when they /// log off. /// bool IsTemporary { get; } /// - /// Gets the time (in seconds) until the invite expires, or if it never expires. + /// Gets the time (in seconds) until the invite expires, or null if it never expires. /// int? MaxAge { get; } /// - /// Gets the max amount of times this invite may be used, or if there is no limit. + /// Gets the max amount of times this invite may be used, or null if there is no limit. /// int? MaxUses { get; } /// diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs index ab1360ce3..e596c0707 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs @@ -3,7 +3,7 @@ using System.Diagnostics; namespace Discord { /// - /// Represents a author field of an . + /// A author field of an . /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedAuthor diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 087b30993..034d7eb73 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -106,7 +106,7 @@ namespace Discord /// 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 . @@ -125,28 +125,28 @@ namespace Discord /// Gets or sets the timestamp of an . /// /// - /// The timestamp of the embed, or if none is set. + /// 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 if none is set. + /// 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 if none is set. + /// 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 if none is set. + /// The footer field builder of the embed, or null if none is set. /// public EmbedFooterBuilder Footer { get; set; } @@ -438,7 +438,6 @@ namespace Discord { private string _name; private string _value; - private EmbedField _field; /// /// Gets the maximum field length for name allowed by Discord. /// @@ -452,7 +451,7 @@ namespace Discord /// Gets or sets the field name. /// /// - /// Field name is , empty or entirely whitespace. + /// Field name is null, empty or entirely whitespace. /// - or - /// Field name length exceeds . /// @@ -474,7 +473,7 @@ namespace Discord /// Gets or sets the field value. /// /// - /// Field value is , empty or entirely whitespace. + /// Field value is null, empty or entirely whitespace. /// - or - /// Field value length exceeds . /// @@ -540,7 +539,7 @@ namespace Discord /// The current builder. /// /// - /// or is , empty or entirely whitespace. + /// or is null, empty or entirely whitespace. /// - or - /// or exceeds the maximum length allowed by Discord. /// diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedField.cs b/src/Discord.Net.Core/Entities/Messages/EmbedField.cs index 3ae000022..5d8fd3c6b 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedField.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedField.cs @@ -3,7 +3,7 @@ using System.Diagnostics; namespace Discord { /// - /// Represents a field for an . + /// A field for an . /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedField diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index 4368f74a4..d02b2cdc3 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -13,11 +13,11 @@ namespace Discord /// public string Url { get; } /// - /// Gets the height of the video, or if none. + /// Gets the height of the video, or null if none. /// public int? Height { get; } /// - /// Gets the weight of the video, or if none. + /// Gets the weight of the video, or null if none. /// public int? Width { get; } diff --git a/src/Discord.Net.Core/Entities/Messages/IAttachment.cs b/src/Discord.Net.Core/Entities/Messages/IAttachment.cs index f01876186..5d4d32cfa 100644 --- a/src/Discord.Net.Core/Entities/Messages/IAttachment.cs +++ b/src/Discord.Net.Core/Entities/Messages/IAttachment.cs @@ -6,33 +6,54 @@ namespace Discord public interface IAttachment { /// - /// Gets the snowflake ID of the attachment. + /// Gets the ID of this attachment. /// + /// + /// A snowflake ID associated with this attachment. + /// ulong Id { get; } /// - /// Gets the filename of the attachment. + /// 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 the attachment. + /// Gets the URL of this attachment. /// + /// + /// A string containing the URL of this attachment. + /// string Url { get; } /// - /// Gets the proxied URL of the attachment. + /// Gets a proxied URL of this attachment. /// + /// + /// A string containing the proxied URL of this attachment. + /// string ProxyUrl { get; } /// - /// Gets the file size of the attachment. + /// Gets the file size of this attachment. /// + /// + /// The size of this attachment in bytes. + /// int Size { get; } /// - /// Gets the height of the attachment if it is an image, or return when it is not. + /// 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 the attachment if it is an image, or return when it is not. + /// 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 473a61ed5..4c1029a10 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -9,56 +9,96 @@ namespace Discord public interface IEmbed { /// - /// Gets the title URL of the embed. + /// 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 the embed. + /// Gets the title of this embed. /// + /// + /// The title of the embed. + /// string Title { get; } /// - /// Gets the description of the embed. + /// Gets the description of this embed. /// + /// + /// The description field of the embed. + /// string Description { get; } /// - /// Gets the type of the embed. + /// Gets the type of this embed. /// + /// + /// The type of the embed. + /// EmbedType Type { get; } /// - /// Gets the timestamp of the embed, or if none is set. + /// 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 sidebar color of the embed, or if none is set. + /// 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 the embed, or if none is set. + /// Gets the image of this embed. /// + /// + /// The image of the embed, or null if none is set. + /// EmbedImage? Image { get; } /// - /// Gets the video of the embed, or if none is set. + /// 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 the embed, or if none is set. + /// 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 the embed, or if none is set. + /// 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 the embed, or if none is set. + /// 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 the embed, or if none is set. + /// 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 d66a6b883..edbe4f4b6 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -17,11 +17,11 @@ namespace Discord /// MessageSource Source { get; } /// - /// Returns if this message was sent as a text-to-speech message. + /// Returns true if this message was sent as a text-to-speech message. /// bool IsTTS { get; } /// - /// Returns if this message was added to its channel's pinned messages. + /// Returns true if this message was added to its channel's pinned messages. /// bool IsPinned { get; } /// @@ -31,14 +31,20 @@ namespace Discord /// /// 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, or if none is set. + /// 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; } /// @@ -49,10 +55,16 @@ namespace Discord /// /// Returns all attachments included in this message. /// + /// + /// Collection of attachments. + /// IReadOnlyCollection Attachments { get; } /// /// Returns all embeds included in this message. /// + /// + /// Collection of embed objects. + /// IReadOnlyCollection Embeds { get; } /// /// Returns all tags included in this message's content. @@ -61,14 +73,23 @@ namespace Discord /// /// Returns the IDs of channels mentioned in this message. /// + /// + /// Collection of channel IDs. + /// IReadOnlyCollection MentionedChannelIds { get; } /// /// Returns the IDs of roles mentioned in this message. /// + /// + /// Collection of role IDs. + /// IReadOnlyCollection MentionedRoleIds { get; } /// /// Returns the IDs of users mentioned in this message. /// + /// + /// Collection of user IDs. + /// IReadOnlyCollection MentionedUserIds { get; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs b/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs index 0f5a171d1..89cd17a35 100644 --- a/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs @@ -1,7 +1,7 @@ namespace Discord { /// - /// Represents a message sent by the system. + /// 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 1afb3a3b2..18ef93266 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace Discord { /// - /// Represents a Discord message object. + /// Represents a generic message sent by a user. /// public interface IUserMessage : IMessage { diff --git a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs index c2892117a..2cc0eab8e 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs @@ -4,23 +4,9 @@ namespace Discord /// Properties that are used to modify an with the specified changes. /// /// - /// The content of a message can be cleared with if and only if an 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.") - /// .Build(); - /// }); - /// - /// + /// public class MessageProperties { /// diff --git a/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs b/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs index 8ef11bc47..8f2678cd9 100644 --- a/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs +++ b/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs @@ -11,7 +11,7 @@ namespace Discord public int ReactionCount { get; internal set; } /// - /// Returns if the current user has used this reaction. + /// Returns true if the current user has used this reaction. /// public bool IsMe { get; internal set; } } diff --git a/src/Discord.Net.Core/Entities/Messages/TagHandling.cs b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs index 667f7241b..eaadd6400 100644 --- a/src/Discord.Net.Core/Entities/Messages/TagHandling.cs +++ b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs @@ -1,21 +1,39 @@ namespace Discord { - /// Specifies the handling type the tag should use. + /// + /// Specifies the handling type the tag should use. + /// + /// + /// public enum TagHandling { - /// Tag handling is ignored. - Ignore = 0, //<@53905483156684800> -> <@53905483156684800> - /// Removes the tag entirely. - Remove, //<@53905483156684800> -> - /// Resolves to username (e.g. @User). - Name, //<@53905483156684800> -> @Voltana - /// Resolves to username without mention prefix (e.g. User). - NameNoPrefix, //<@53905483156684800> -> Voltana - /// Resolves to username with discriminator value. (e.g. @User#0001). - FullName, //<@53905483156684800> -> @Voltana#8252 - /// Resolves to username with discriminator value without mention prefix. (e.g. User#0001). - FullNameNoPrefix, //<@53905483156684800> -> Voltana#8252 - /// Sanitizes the tag. - 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/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 99134bb90..758bc5758 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -12,7 +12,7 @@ namespace Discord /// Gets a that grants all permissions for text channels. public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); /// Gets a that grants all permissions for voice channels. - public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001); + public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000010000_010001); /// Gets a that grants all permissions for category channels. public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); /// Gets a that grants all permissions for direct message channels. @@ -37,52 +37,52 @@ namespace Discord /// Gets a packed value representing all the permissions in this . public ulong RawValue { get; } - /// If , a user may create invites. + /// If true, a user may create invites. public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); - /// If , 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 , a user may add reactions. + /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); - /// If , a user may join channels. + /// If true, a user may join channels. [Obsolete("Use ViewChannel instead.")] public bool ReadMessages => ViewChannel; - /// If , a user may view channels. + /// If true, a user may view channels. public bool ViewChannel => Permissions.GetValue(RawValue, ChannelPermission.ViewChannel); - /// If , a user may send messages. + /// If true, a user may send messages. public bool SendMessages => Permissions.GetValue(RawValue, ChannelPermission.SendMessages); - /// If , 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 , a user may delete messages. + /// If true, a user may delete messages. public bool ManageMessages => Permissions.GetValue(RawValue, ChannelPermission.ManageMessages); - /// If , 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 , a user may send files. + /// If true, a user may send files. public bool AttachFiles => Permissions.GetValue(RawValue, ChannelPermission.AttachFiles); - /// If , a user may read previous messages. + /// If true, a user may read previous messages. public bool ReadMessageHistory => Permissions.GetValue(RawValue, ChannelPermission.ReadMessageHistory); - /// If , a user may mention @everyone. + /// If true, a user may mention @everyone. public bool MentionEveryone => Permissions.GetValue(RawValue, ChannelPermission.MentionEveryone); - /// If , 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 , 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 , 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 , a user may mute users. + /// If true, a user may mute users. public bool MuteMembers => Permissions.GetValue(RawValue, ChannelPermission.MuteMembers); - /// If , a user may deafen users. + /// If true, a user may deafen users. public bool DeafenMembers => Permissions.GetValue(RawValue, ChannelPermission.DeafenMembers); - /// If , 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 , 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 , 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 , 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 with the provided packed value. diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index e1dbb08fd..9a0cb2919 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -16,65 +16,65 @@ namespace Discord /// Gets a packed value representing all the permissions in this . public ulong RawValue { get; } - /// If , a user may create invites. + /// If true, a user may create invites. public bool CreateInstantInvite => Permissions.GetValue(RawValue, GuildPermission.CreateInstantInvite); - /// If , 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 , 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 , 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 , 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 , a user may adjust guild properties. + /// If true, a user may adjust guild properties. public bool ManageGuild => Permissions.GetValue(RawValue, GuildPermission.ManageGuild); - /// If , a user may add reactions. + /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); - /// If , 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 , a user may join channels. + /// If true, a user may join channels. public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); - /// If , a user may send messages. + /// If true, a user may send messages. public bool SendMessages => Permissions.GetValue(RawValue, GuildPermission.SendMessages); - /// If , 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 , a user may delete messages. + /// If true, a user may delete messages. public bool ManageMessages => Permissions.GetValue(RawValue, GuildPermission.ManageMessages); - /// If , 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 , a user may send files. + /// If true, a user may send files. public bool AttachFiles => Permissions.GetValue(RawValue, GuildPermission.AttachFiles); - /// If , a user may read previous messages. + /// If true, a user may read previous messages. public bool ReadMessageHistory => Permissions.GetValue(RawValue, GuildPermission.ReadMessageHistory); - /// If , a user may mention @everyone. + /// If true, a user may mention @everyone. public bool MentionEveryone => Permissions.GetValue(RawValue, GuildPermission.MentionEveryone); - /// If , 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 , 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 , 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 , a user may mute users. + /// If true, a user may mute users. public bool MuteMembers => Permissions.GetValue(RawValue, GuildPermission.MuteMembers); - /// If , a user may deafen users. + /// If true, a user may deafen users. public bool DeafenMembers => Permissions.GetValue(RawValue, GuildPermission.DeafenMembers); - /// If , 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 , 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 , 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 , 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 , a user may adjust roles. + /// If true, a user may adjust roles. public bool ManageRoles => Permissions.GetValue(RawValue, GuildPermission.ManageRoles); - /// If , 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 , 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 with the provided packed value. diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index cdee5284d..fa7624a57 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -4,7 +4,7 @@ using System.Diagnostics; namespace Discord { /// - /// Represents a Discord color. + /// Represents a color used in Discord. /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Color @@ -65,7 +65,7 @@ namespace Discord /// /// Initializes a struct with the given raw value. /// - /// A raw value for the color (e.g. 0x607D8B). + /// The raw value of the color (e.g. 0x607D8B). public Color(uint rawValue) { RawValue = rawValue; @@ -73,9 +73,9 @@ namespace Discord /// /// Initializes a struct with the given RGB bytes. /// - /// The that represents the red color. - /// The that represents the green color. - /// The that represents the blue color. + /// 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 = @@ -126,8 +126,11 @@ namespace Discord } /// - /// Gets the hexadecimal representation of the color (e.g. #000ccc). + /// 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 f4cb4c64d..c0f4e9942 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -21,24 +21,24 @@ namespace Discord /// Determines whether the role can be separated in the user list. /// /// - /// Returns if users of this role are separated in the user list; otherwise, returns - /// . + /// Returns true if users of this role are separated in the user list; otherwise, returns + /// false. /// bool IsHoisted { get; } /// /// Determines whether the role is managed by Discord. /// /// - /// Returns if this role is automatically managed by Discord; otherwise, returns - /// . + /// Returns true if this role is automatically managed by Discord; otherwise, returns + /// false. /// bool IsManaged { get; } /// /// Determines whether the role is mentionable. /// /// - /// Returns if this role may be mentioned in messages; otherwise, returns - /// . + /// Returns true if this role may be mentioned in messages; otherwise, returns + /// false. /// bool IsMentionable { get; } /// diff --git a/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs b/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs index 79372b86d..54fa27bfd 100644 --- a/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs +++ b/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs @@ -3,16 +3,7 @@ namespace Discord /// /// 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 { /// diff --git a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs index beb2c392f..27a8be351 100644 --- a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs @@ -5,36 +5,28 @@ namespace Discord /// /// Properties that are used to modify an with the following parameters. /// - /// - /// - /// await guildUser.ModifyAsync(x => - /// { - /// x.Nickname = $"festive {guildUser.Username}"; - /// }); - /// - /// - /// + /// public class GuildUserProperties { /// /// Gets or sets whether the user should be muted in a voice channel. /// /// - /// If this value is set to , 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; } /// /// Gets or sets whether the user should be deafened in a voice channel. /// /// - /// If this value is set to , 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; } /// /// 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; } diff --git a/src/Discord.Net.Core/Entities/Users/ISelfUser.cs b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs index 4a97c86ef..1ead0cbba 100644 --- a/src/Discord.Net.Core/Entities/Users/ISelfUser.cs +++ b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs @@ -13,11 +13,11 @@ namespace Discord /// string Email { get; } /// - /// Returns if this user's email has been verified. + /// Returns true if this user's email has been verified. /// bool IsVerified { get; } /// - /// Returns if this user has enabled MFA on their account. + /// Returns true if this user has enabled MFA on their account. /// bool IsMfaEnabled { get; } diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index f651a23f3..96b9ae7ee 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -12,11 +12,18 @@ namespace Discord /// 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(); /// @@ -28,11 +35,11 @@ namespace Discord /// ushort DiscriminatorValue { get; } /// - /// Returns if this user is a bot user. + /// Gets true if this user is a bot user. /// bool IsBot { get; } /// - /// Returns if this user is a webhook user. + /// Gets true if this user is a webhook user. /// bool IsWebhook { get; } /// @@ -43,6 +50,10 @@ namespace Discord /// /// Returns a direct message channel to this user, or create one if it does not already exist. /// + /// The options to be used when sending the request. + /// + /// An awaitable Task containing the DM channel. + /// Task GetOrCreateDMChannelAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs index 725ef2870..abe06c3b3 100644 --- a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs +++ b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs @@ -6,27 +6,27 @@ namespace Discord public interface IVoiceState { /// - /// Returns if the guild has deafened this user. + /// Returns true if the guild has deafened this user. /// bool IsDeafened { get; } /// - /// Returns if the guild has muted this user. + /// Returns true if the guild has muted this user. /// bool IsMuted { get; } /// - /// Returns if this user has marked themselves as deafened. + /// Returns true if this user has marked themselves as deafened. /// bool IsSelfDeafened { get; } /// - /// Returns if this user has marked themselves as muted. + /// Returns true if this user has marked themselves as muted. /// bool IsSelfMuted { get; } /// - /// Returns 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, or if none. + /// Gets the voice channel this user is currently in, or null if none. /// IVoiceChannel VoiceChannel { get; } /// diff --git a/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs b/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs index d79da0265..e2ae12ba4 100644 --- a/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs @@ -3,15 +3,7 @@ namespace Discord /// /// 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 { /// diff --git a/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs b/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs index 387ee6106..00af9f9ef 100644 --- a/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs +++ b/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs @@ -3,16 +3,7 @@ namespace Discord /// /// 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 { /// diff --git a/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs b/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs new file mode 100644 index 000000000..0ddfa393d --- /dev/null +++ b/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs @@ -0,0 +1,14 @@ +namespace Discord +{ + /// + /// Represents the type of a webhook. + /// + /// + /// This type is currently unused, and is only returned in audit log responses. + /// + public enum WebhookType + { + /// An incoming webhook + Incoming = 1 + } +} diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index ad00296f7..41e84c3cb 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using System.IO; @@ -6,7 +7,16 @@ namespace Discord /// An extension class for various Discord user objects. public static class UserExtensions { - /// Sends a message to the user via DM. + /// + /// Sends a message via DM. + /// + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// An awaitable Task containing the message sent to the channel. + /// public static async Task SendMessageAsync(this IUser user, string text, bool isTTS = false, @@ -16,7 +26,23 @@ namespace Discord return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); } - /// Sends a file to the user via DM. + /// + /// Sends a file to this message channel with an optional caption. + /// + /// The of the file to be sent. + /// The name of the attachment. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// public static async Task SendFileAsync(this IUser user, Stream stream, string filename, @@ -30,7 +56,22 @@ namespace Discord } #if FILESYSTEM - /// Sends a file to the user via DM. + /// + /// Sends a file via DM with an optional caption. + /// + /// The file path of the file. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// public static async Task SendFileAsync(this IUser user, string filePath, string text = null, @@ -41,10 +82,15 @@ namespace Discord return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); } #endif - /// Bans the provided user from the guild and optionally prunes their recent messages. - /// The user to ban. - /// The number of days to remove messages from this user for - must be between [0, 7] - /// The reason of the ban to be written in the audit log. + /// + /// Bans the provided user from the guild and optionally prunes their recent messages. + /// + /// The user to ban. + /// + /// The number of days to remove messages from this user for - must be between [0, 7] + /// + /// The reason of the ban to be written in the audit log. + /// is not between 0 to 7. public static Task BanAsync(this IGuildUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) => user.Guild.AddBanAsync(user, pruneDays, reason, options); } diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index 92aee2b08..f5bbd0a28 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -10,16 +10,33 @@ namespace Discord /// public interface IDiscordClient : IDisposable { + /// + /// Gets the current state of connection. + /// ConnectionState ConnectionState { get; } + /// + /// Gets the currently logged-in user. + /// ISelfUser CurrentUser { get; } + /// + /// Gets the token type of the logged-in user. + /// TokenType TokenType { get; } Task StartAsync(); Task StopAsync(); /// - /// Gets the application information associated with this account. + /// Gets a Discord application information for the logged-in user. /// + /// + /// This method reflects your application information you submitted when creating a Discord application via + /// the Developer Portal. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the application information. + /// Task GetApplicationInfoAsync(RequestOptions options = null); /// @@ -36,11 +53,21 @@ namespace Discord /// Task> GetPrivateChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// - /// Gets a list of direct message channels. + /// Returns a collection of direct message channels. /// + /// + /// This method returns a collection of currently opened direct message channels. + /// + /// This method will not return previously opened DM channels outside of the current session! If you + /// have just started the client, this may return an empty collection. + /// + /// /// /// The that determines whether the object should be fetched from cache. /// + /// + /// An awaitable containing a collection of DM channels. + /// Task> GetDMChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// /// Gets a list of group channels. diff --git a/src/Discord.Net.Core/Net/HttpException.cs b/src/Discord.Net.Core/Net/HttpException.cs index c49273451..aa440bf1c 100644 --- a/src/Discord.Net.Core/Net/HttpException.cs +++ b/src/Discord.Net.Core/Net/HttpException.cs @@ -23,7 +23,7 @@ namespace Discord.Net /// /// A /// JSON error code - /// from Discord, or if none. + /// from Discord, or null if none. /// public int? DiscordCode { get; } /// diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 2318f3f98..c2d1f6549 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -14,7 +14,7 @@ namespace Discord /// /// Gets or set the max time, in milliseconds, to wait for this request to complete. If - /// , a request will not time out. If a rate limit has been triggered for this + /// null, a request will not time out. If a rate limit has been triggered for this /// request's bucket and will not be unpaused in time, this request will fail immediately. /// public int? Timeout { get; set; } diff --git a/src/Discord.Net.Core/Utils/Cacheable.cs b/src/Discord.Net.Core/Utils/Cacheable.cs index 15358dda0..02da682ae 100644 --- a/src/Discord.Net.Core/Utils/Cacheable.cs +++ b/src/Discord.Net.Core/Utils/Cacheable.cs @@ -25,7 +25,7 @@ namespace Discord /// /// /// 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; } diff --git a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs index 233d1b0b0..bbdc59087 100644 --- a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs +++ b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs @@ -157,7 +157,7 @@ namespace Discord : this(collection, EqualityComparer.Default) { } public ConcurrentHashSet(IEqualityComparer comparer) : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) { } - /// is + /// is null public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) : this(comparer) { @@ -165,7 +165,7 @@ namespace Discord InitializeFromCollection(collection); } /// - /// or is + /// or is null /// public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer comparer) : this(concurrencyLevel, DefaultCapacity, false, comparer) @@ -210,7 +210,7 @@ namespace Discord if (_budget == 0) _budget = _tables._buckets.Length / _tables._locks.Length; } - /// is + /// is null public bool ContainsKey(T value) { if (value == null) throw new ArgumentNullException(nameof(value)); @@ -234,7 +234,7 @@ namespace Discord return false; } - /// is + /// is null public bool TryAdd(T value) { if (value == null) throw new ArgumentNullException(nameof(value)); @@ -284,7 +284,7 @@ namespace Discord } } - /// is + /// is null public bool TryRemove(T value) { if (value == null) throw new ArgumentNullException(nameof(value)); diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index edfd3b12c..ae506e142 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -16,16 +16,25 @@ namespace Discord /// /// 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}>"; /// /// 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()); /// diff --git a/src/Discord.Net.Rest/API/Common/AuditLog.cs b/src/Discord.Net.Rest/API/Common/AuditLog.cs new file mode 100644 index 000000000..cd8ad147d --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLog.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AuditLog + { + [JsonProperty("webhooks")] + public Webhook[] Webhooks { get; set; } + + [JsonProperty("users")] + public User[] Users { get; set; } + + [JsonProperty("audit_log_entries")] + public AuditLogEntry[] Entries { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogChange.cs b/src/Discord.Net.Rest/API/Common/AuditLogChange.cs new file mode 100644 index 000000000..44e585021 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogChange.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Discord.API +{ + internal class AuditLogChange + { + [JsonProperty("key")] + public string ChangedProperty { get; set; } + + [JsonProperty("new_value")] + public JToken NewValue { get; set; } + + [JsonProperty("old_value")] + public JToken OldValue { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs new file mode 100644 index 000000000..80d9a9e97 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AuditLogEntry + { + [JsonProperty("target_id")] + public ulong? TargetId { get; set; } + [JsonProperty("user_id")] + public ulong UserId { get; set; } + + [JsonProperty("changes")] + public AuditLogChange[] Changes { get; set; } + [JsonProperty("options")] + public AuditLogOptions Options { get; set; } + + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("action_type")] + public ActionType Action { get; set; } + + [JsonProperty("reason")] + public string Reason { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs new file mode 100644 index 000000000..65b401cce --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AuditLogOptions + { + //Message delete + [JsonProperty("count")] + public int? MessageDeleteCount { get; set; } + [JsonProperty("channel_id")] + public ulong? MessageDeleteChannelId { get; set; } + + //Prune + [JsonProperty("delete_member_days")] + public int? PruneDeleteMemberDays { get; set; } + [JsonProperty("members_removed")] + public int? PruneMembersRemoved { get; set; } + + //Overwrite Update + [JsonProperty("role_name")] + public string OverwriteRoleName { get; set; } + [JsonProperty("type")] + public string OverwriteType { get; set; } + [JsonProperty("id")] + public ulong? OverwriteTargetId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs new file mode 100644 index 000000000..ceabccbc8 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs @@ -0,0 +1,8 @@ +namespace Discord.API.Rest +{ + class GetAuditLogsParams + { + public Optional Limit { get; set; } + public Optional BeforeEntryId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index c59fd0e42..f23547106 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -812,6 +812,15 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); return await SendAsync>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false); } + public async Task GetGuildBanAsync(ulong guildId, ulong userId, RequestOptions options) + { + Preconditions.NotEqual(userId, 0, nameof(userId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); + } public async Task CreateGuildBanAsync(ulong guildId, ulong userId, CreateGuildBanParams args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -1209,6 +1218,26 @@ namespace Discord.API return await SendAsync>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false); } + //Audit logs + public async Task GetAuditLogsAsync(ulong guildId, GetAuditLogsParams args, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + options = RequestOptions.CreateOrClone(options); + + int limit = args.Limit.GetValueOrDefault(int.MaxValue); + + var ids = new BucketIds(guildId: guildId); + Expression> endpoint; + + if (args.BeforeEntryId.IsSpecified) + endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}&before={args.BeforeEntryId.Value}"; + else + endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}"; + + return await SendAsync("GET", endpoint, ids, options: options).ConfigureAwait(false); + } + //Webhooks public async Task CreateWebhookAsync(ulong channelId, CreateWebhookParams args, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs new file mode 100644 index 000000000..7936343f3 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + internal static class AuditLogHelper + { + private static readonly Dictionary> CreateMapping + = new Dictionary>() + { + [ActionType.GuildUpdated] = GuildUpdateAuditLogData.Create, + + [ActionType.ChannelCreated] = ChannelCreateAuditLogData.Create, + [ActionType.ChannelUpdated] = ChannelUpdateAuditLogData.Create, + [ActionType.ChannelDeleted] = ChannelDeleteAuditLogData.Create, + + [ActionType.OverwriteCreated] = OverwriteCreateAuditLogData.Create, + [ActionType.OverwriteUpdated] = OverwriteUpdateAuditLogData.Create, + [ActionType.OverwriteDeleted] = OverwriteDeleteAuditLogData.Create, + + [ActionType.Kick] = KickAuditLogData.Create, + [ActionType.Prune] = PruneAuditLogData.Create, + [ActionType.Ban] = BanAuditLogData.Create, + [ActionType.Unban] = UnbanAuditLogData.Create, + [ActionType.MemberUpdated] = MemberUpdateAuditLogData.Create, + [ActionType.MemberRoleUpdated] = MemberRoleAuditLogData.Create, + + [ActionType.RoleCreated] = RoleCreateAuditLogData.Create, + [ActionType.RoleUpdated] = RoleUpdateAuditLogData.Create, + [ActionType.RoleDeleted] = RoleDeleteAuditLogData.Create, + + [ActionType.InviteCreated] = InviteCreateAuditLogData.Create, + [ActionType.InviteUpdated] = InviteUpdateAuditLogData.Create, + [ActionType.InviteDeleted] = InviteDeleteAuditLogData.Create, + + [ActionType.WebhookCreated] = WebhookCreateAuditLogData.Create, + [ActionType.WebhookUpdated] = WebhookUpdateAuditLogData.Create, + [ActionType.WebhookDeleted] = WebhookDeleteAuditLogData.Create, + + [ActionType.EmojiCreated] = EmoteCreateAuditLogData.Create, + [ActionType.EmojiUpdated] = EmoteUpdateAuditLogData.Create, + [ActionType.EmojiDeleted] = EmoteDeleteAuditLogData.Create, + + [ActionType.MessageDeleted] = MessageDeleteAuditLogData.Create, + }; + + public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) + { + if (CreateMapping.TryGetValue(entry.Action, out var func)) + return func(discord, log, entry); + + return null; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs new file mode 100644 index 000000000..4b9d5875f --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs @@ -0,0 +1,23 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class BanAuditLogData : IAuditLogData + { + private BanAuditLogData(IUser user) + { + Target = user; + } + + internal static BanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + return new BanAuditLogData(RestUser.Create(discord, userInfo)); + } + + 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 new file mode 100644 index 000000000..ef4787295 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class ChannelCreateAuditLogData : IAuditLogData + { + private ChannelCreateAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) + { + ChannelId = id; + ChannelName = name; + ChannelType = type; + Overwrites = overwrites; + } + + internal static ChannelCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + var overwrites = new List(); + + var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var type = typeModel.NewValue.ToObject(); + var name = nameModel.NewValue.ToObject(); + + foreach (var overwrite in overwritesModel.NewValue) + { + var deny = overwrite.Value("deny"); + var _type = overwrite.Value("type"); + var id = overwrite.Value("id"); + var allow = overwrite.Value("allow"); + + PermissionTarget permType = _type == "member" ? PermissionTarget.User : PermissionTarget.Role; + + overwrites.Add(new Overwrite(id, permType, new OverwritePermissions(allow, deny))); + } + + return new ChannelCreateAuditLogData(entry.TargetId.Value, name, type, overwrites.ToReadOnlyCollection()); + } + + public ulong ChannelId { get; } + public string ChannelName { get; } + public ChannelType ChannelType { get; } + 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 new file mode 100644 index 000000000..4816ce770 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs @@ -0,0 +1,45 @@ +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 +{ + public class ChannelDeleteAuditLogData : IAuditLogData + { + private ChannelDeleteAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) + { + ChannelId = id; + ChannelName = name; + ChannelType = type; + Overwrites = overwrites; + } + + internal static ChannelDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var overwrites = overwritesModel.OldValue.ToObject() + .Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny))) + .ToList(); + var type = typeModel.OldValue.ToObject(); + var name = nameModel.OldValue.ToObject(); + var id = entry.TargetId.Value; + + return new ChannelDeleteAuditLogData(id, name, type, overwrites.ToReadOnlyCollection()); + } + + public ulong ChannelId { get; } + public string ChannelName { get; } + public ChannelType ChannelType { get; } + 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 new file mode 100644 index 000000000..e2d6064a9 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs @@ -0,0 +1,18 @@ +namespace Discord.Rest +{ + public struct ChannelInfo + { + internal ChannelInfo(string name, string topic, int? bitrate, int? limit) + { + Name = name; + Topic = topic; + Bitrate = bitrate; + UserLimit = limit; + } + + public string Name { get; } + public string Topic { get; } + public int? Bitrate { get; } + 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 new file mode 100644 index 000000000..f3403138d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -0,0 +1,45 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class ChannelUpdateAuditLogData : IAuditLogData + { + private ChannelUpdateAuditLogData(ulong id, ChannelInfo before, ChannelInfo after) + { + ChannelId = id; + Before = before; + After = after; + } + + internal static ChannelUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var topicModel = changes.FirstOrDefault(x => x.ChangedProperty == "topic"); + var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); + var userLimitModel = changes.FirstOrDefault(x => x.ChangedProperty == "user_limit"); + + string oldName = nameModel?.OldValue?.ToObject(), + newName = nameModel?.NewValue?.ToObject(); + string oldTopic = topicModel?.OldValue?.ToObject(), + newTopic = topicModel?.NewValue?.ToObject(); + int? oldBitrate = bitrateModel?.OldValue?.ToObject(), + newBitrate = bitrateModel?.NewValue?.ToObject(); + int? oldLimit = userLimitModel?.OldValue?.ToObject(), + newLimit = userLimitModel?.NewValue?.ToObject(); + + var before = new ChannelInfo(oldName, oldTopic, oldBitrate, oldLimit); + var after = new ChannelInfo(newName, newTopic, newBitrate, newLimit); + + return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after); + } + + public ulong ChannelId { get; } + public ChannelInfo Before { get; set; } + public ChannelInfo After { get; set; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs new file mode 100644 index 000000000..5d1ef8463 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs @@ -0,0 +1,31 @@ +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 +{ + public class EmoteCreateAuditLogData : IAuditLogData + { + private EmoteCreateAuditLogData(ulong id, string name) + { + EmoteId = id; + Name = name; + } + + internal static EmoteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var emoteName = change.NewValue?.ToObject(); + return new EmoteCreateAuditLogData(entry.TargetId.Value, emoteName); + } + + public ulong EmoteId { get; } + 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 new file mode 100644 index 000000000..d0a11191f --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs @@ -0,0 +1,28 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class EmoteDeleteAuditLogData : IAuditLogData + { + private EmoteDeleteAuditLogData(ulong id, string name) + { + EmoteId = id; + Name = name; + } + + internal static EmoteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var emoteName = change.OldValue?.ToObject(); + + return new EmoteDeleteAuditLogData(entry.TargetId.Value, emoteName); + } + + public ulong EmoteId { get; } + 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 new file mode 100644 index 000000000..60020bcaa --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs @@ -0,0 +1,31 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class EmoteUpdateAuditLogData : IAuditLogData + { + private EmoteUpdateAuditLogData(ulong id, string oldName, string newName) + { + EmoteId = id; + OldName = oldName; + NewName = newName; + } + + internal static EmoteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var newName = change.NewValue?.ToObject(); + var oldName = change.OldValue?.ToObject(); + + return new EmoteUpdateAuditLogData(entry.TargetId.Value, oldName, newName); + } + + public ulong EmoteId { get; } + public string NewName { get; } + 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 new file mode 100644 index 000000000..90865ef72 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs @@ -0,0 +1,32 @@ +namespace Discord.Rest +{ + public struct GuildInfo + { + internal GuildInfo(int? afkTimeout, DefaultMessageNotifications? defaultNotifs, + ulong? afkChannel, string name, string region, string icon, + VerificationLevel? verification, IUser owner, MfaLevel? mfa, int? filter) + { + AfkTimeout = afkTimeout; + DefaultMessageNotifications = defaultNotifs; + AfkChannelId = afkChannel; + Name = name; + RegionId = region; + IconHash = icon; + VerificationLevel = verification; + Owner = owner; + MfaLevel = mfa; + ContentFilterLevel = filter; + } + + public int? AfkTimeout { get; } + public DefaultMessageNotifications? DefaultMessageNotifications { get; } + public ulong? AfkChannelId { get; } + public string Name { get; } + public string RegionId { get; } + public string IconHash { get; } + public VerificationLevel? VerificationLevel { get; } + public IUser Owner { get; } + 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 new file mode 100644 index 000000000..08550ed7a --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs @@ -0,0 +1,79 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class GuildUpdateAuditLogData : IAuditLogData + { + private GuildUpdateAuditLogData(GuildInfo before, GuildInfo after) + { + Before = before; + After = after; + } + + internal static GuildUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var afkTimeoutModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var defaultMessageNotificationsModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var afkChannelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var regionIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var iconHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var verificationLevelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var ownerIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var mfaLevelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var contentFilterModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + + int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject(), + newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject(); + DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject(), + newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject(); + ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject(), + newAfkChannelId = afkChannelModel?.NewValue?.ToObject(); + string oldName = nameModel?.OldValue?.ToObject(), + newName = nameModel?.NewValue?.ToObject(); + string oldRegionId = regionIdModel?.OldValue?.ToObject(), + newRegionId = regionIdModel?.NewValue?.ToObject(); + string oldIconHash = iconHashModel?.OldValue?.ToObject(), + newIconHash = iconHashModel?.NewValue?.ToObject(); + VerificationLevel? oldVerificationLevel = verificationLevelModel?.OldValue?.ToObject(), + newVerificationLevel = verificationLevelModel?.NewValue?.ToObject(); + ulong? oldOwnerId = ownerIdModel?.OldValue?.ToObject(), + newOwnerId = ownerIdModel?.NewValue?.ToObject(); + MfaLevel? oldMfaLevel = mfaLevelModel?.OldValue?.ToObject(), + newMfaLevel = mfaLevelModel?.NewValue?.ToObject(); + int? oldContentFilter = contentFilterModel?.OldValue?.ToObject(), + newContentFilter = contentFilterModel?.NewValue?.ToObject(); + + IUser oldOwner = null; + if (oldOwnerId != null) + { + var oldOwnerInfo = log.Users.FirstOrDefault(x => x.Id == oldOwnerId.Value); + oldOwner = RestUser.Create(discord, oldOwnerInfo); + } + + IUser newOwner = null; + if (newOwnerId != null) + { + var newOwnerInfo = log.Users.FirstOrDefault(x => x.Id == newOwnerId.Value); + newOwner = RestUser.Create(discord, newOwnerInfo); + } + + var before = new GuildInfo(oldAfkTimeout, oldDefaultMessageNotifications, + oldAfkChannelId, oldName, oldRegionId, oldIconHash, oldVerificationLevel, oldOwner, + oldMfaLevel, oldContentFilter); + var after = new GuildInfo(newAfkTimeout, newDefaultMessageNotifications, + newAfkChannelId, newName, newRegionId, newIconHash, newVerificationLevel, newOwner, + newMfaLevel, newContentFilter); + + return new GuildUpdateAuditLogData(before, after); + } + + public GuildInfo Before { get; } + 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 new file mode 100644 index 000000000..292715420 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs @@ -0,0 +1,55 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class InviteCreateAuditLogData : IAuditLogData + { + private InviteCreateAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) + { + MaxAge = maxAge; + Code = code; + Temporary = temporary; + Creator = inviter; + ChannelId = channelId; + Uses = uses; + MaxUses = maxUses; + } + + internal static InviteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age"); + var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code"); + var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary"); + var inviterIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "inviter_id"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); + var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); + + var maxAge = maxAgeModel.NewValue.ToObject(); + var code = codeModel.NewValue.ToObject(); + var temporary = temporaryModel.NewValue.ToObject(); + var inviterId = inviterIdModel.NewValue.ToObject(); + var channelId = channelIdModel.NewValue.ToObject(); + var uses = usesModel.NewValue.ToObject(); + var maxUses = maxUsesModel.NewValue.ToObject(); + + var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); + var inviter = RestUser.Create(discord, inviterInfo); + + return new InviteCreateAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); + } + + public int MaxAge { get; } + public string Code { get; } + public bool Temporary { get; } + public IUser Creator { get; } + public ulong ChannelId { get; } + public int Uses { get; } + 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 new file mode 100644 index 000000000..1dc6d518b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs @@ -0,0 +1,55 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class InviteDeleteAuditLogData : IAuditLogData + { + private InviteDeleteAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) + { + MaxAge = maxAge; + Code = code; + Temporary = temporary; + Creator = inviter; + ChannelId = channelId; + Uses = uses; + MaxUses = maxUses; + } + + internal static InviteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age"); + var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code"); + var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary"); + var inviterIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "inviter_id"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); + var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); + + var maxAge = maxAgeModel.OldValue.ToObject(); + var code = codeModel.OldValue.ToObject(); + var temporary = temporaryModel.OldValue.ToObject(); + var inviterId = inviterIdModel.OldValue.ToObject(); + var channelId = channelIdModel.OldValue.ToObject(); + var uses = usesModel.OldValue.ToObject(); + var maxUses = maxUsesModel.OldValue.ToObject(); + + var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); + var inviter = RestUser.Create(discord, inviterInfo); + + return new InviteDeleteAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); + } + + public int MaxAge { get; } + public string Code { get; } + public bool Temporary { get; } + public IUser Creator { get; } + public ulong ChannelId { get; } + public int Uses { get; } + 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 new file mode 100644 index 000000000..c9840f6cc --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs @@ -0,0 +1,20 @@ +namespace Discord.Rest +{ + public struct InviteInfo + { + internal InviteInfo(int? maxAge, string code, bool? temporary, ulong? channelId, int? maxUses) + { + MaxAge = maxAge; + Code = code; + Temporary = temporary; + ChannelId = channelId; + MaxUses = maxUses; + } + + public int? MaxAge { get; } + public string Code { get; } + public bool? Temporary { get; } + public ulong? ChannelId { get; } + 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 new file mode 100644 index 000000000..b932cfbfc --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs @@ -0,0 +1,46 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class InviteUpdateAuditLogData : IAuditLogData + { + private InviteUpdateAuditLogData(InviteInfo before, InviteInfo after) + { + Before = before; + After = after; + } + + internal static InviteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age"); + var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code"); + var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); + + int? oldMaxAge = maxAgeModel?.OldValue?.ToObject(), + newMaxAge = maxAgeModel?.NewValue?.ToObject(); + string oldCode = codeModel?.OldValue?.ToObject(), + newCode = codeModel?.NewValue?.ToObject(); + bool? oldTemporary = temporaryModel?.OldValue?.ToObject(), + newTemporary = temporaryModel?.NewValue?.ToObject(); + ulong? oldChannelId = channelIdModel?.OldValue?.ToObject(), + newChannelId = channelIdModel?.NewValue?.ToObject(); + int? oldMaxUses = maxUsesModel?.OldValue?.ToObject(), + newMaxUses = maxUsesModel?.NewValue?.ToObject(); + + var before = new InviteInfo(oldMaxAge, oldCode, oldTemporary, oldChannelId, oldMaxUses); + var after = new InviteInfo(newMaxAge, newCode, newTemporary, newChannelId, newMaxUses); + + return new InviteUpdateAuditLogData(before, after); + } + + public InviteInfo Before { get; } + 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 new file mode 100644 index 000000000..41b5526b8 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs @@ -0,0 +1,23 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class KickAuditLogData : IAuditLogData + { + private KickAuditLogData(RestUser user) + { + Target = user; + } + + internal static KickAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + return new KickAuditLogData(RestUser.Create(discord, userInfo)); + } + + 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 new file mode 100644 index 000000000..b0f0a1fe1 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class MemberRoleAuditLogData : IAuditLogData + { + private MemberRoleAuditLogData(IReadOnlyCollection roles, IUser target) + { + Roles = roles; + Target = target; + } + + internal static MemberRoleAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(), + (model, role) => new { model.ChangedProperty, Role = role }) + .Select(x => new RoleInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) + .ToList(); + + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + var user = RestUser.Create(discord, userInfo); + + return new MemberRoleAuditLogData(roleInfos.ToReadOnlyCollection(), user); + } + + public IReadOnlyCollection Roles { get; } + public IUser Target { get; } + + public struct RoleInfo + { + internal RoleInfo(string name, ulong roleId, bool added) + { + Name = name; + RoleId = roleId; + Added = added; + } + + public string Name { get; } + public ulong RoleId { get; } + 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 new file mode 100644 index 000000000..38f078848 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs @@ -0,0 +1,35 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class MemberUpdateAuditLogData : IAuditLogData + { + private MemberUpdateAuditLogData(IUser target, string newNick, string oldNick) + { + Target = target; + NewNick = newNick; + OldNick = oldNick; + } + + internal static MemberUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "nick"); + + var newNick = changes.NewValue?.ToObject(); + var oldNick = changes.OldValue?.ToObject(); + + var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + var user = RestUser.Create(discord, targetInfo); + + return new MemberUpdateAuditLogData(user, newNick, oldNick); + } + + public IUser Target { get; } + public string NewNick { get; } + public string OldNick { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs new file mode 100644 index 000000000..3949cdd68 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs @@ -0,0 +1,22 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class MessageDeleteAuditLogData : IAuditLogData + { + private MessageDeleteAuditLogData(ulong channelId, int count) + { + ChannelId = channelId; + MessageCount = count; + } + + internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + return new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, entry.Options.MessageDeleteCount.Value); + } + + public int MessageCount { get; } + 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 new file mode 100644 index 000000000..d58488136 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs @@ -0,0 +1,37 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class OverwriteCreateAuditLogData : IAuditLogData + { + private OverwriteCreateAuditLogData(Overwrite overwrite) + { + Overwrite = overwrite; + } + + internal static OverwriteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); + var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); + + var deny = denyModel.NewValue.ToObject(); + var allow = allowModel.NewValue.ToObject(); + + var permissions = new OverwritePermissions(allow, deny); + + var id = entry.Options.OverwriteTargetId.Value; + var type = entry.Options.OverwriteType; + + PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; + + return new OverwriteCreateAuditLogData(new Overwrite(id, target, permissions)); + } + + 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 new file mode 100644 index 000000000..445c2e302 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs @@ -0,0 +1,42 @@ +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 +{ + public class OverwriteDeleteAuditLogData : IAuditLogData + { + private OverwriteDeleteAuditLogData(Overwrite deletedOverwrite) + { + Overwrite = deletedOverwrite; + } + + internal static OverwriteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var idModel = changes.FirstOrDefault(x => x.ChangedProperty == "id"); + var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); + + var deny = denyModel.OldValue.ToObject(); + var type = typeModel.OldValue.ToObject(); + var id = idModel.OldValue.ToObject(); + var allow = allowModel.OldValue.ToObject(); + + PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; + + return new OverwriteDeleteAuditLogData(new Overwrite(id, target, new OverwritePermissions(allow, deny))); + } + + 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 new file mode 100644 index 000000000..d000146c3 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs @@ -0,0 +1,44 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class OverwriteUpdateAuditLogData : IAuditLogData + { + private OverwriteUpdateAuditLogData(OverwritePermissions before, OverwritePermissions after, ulong targetId, PermissionTarget targetType) + { + OldPermissions = before; + NewPermissions = after; + OverwriteTargetId = targetId; + OverwriteType = targetType; + } + + internal static OverwriteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); + var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); + + var beforeAllow = allowModel?.OldValue?.ToObject(); + var afterAllow = allowModel?.NewValue?.ToObject(); + var beforeDeny = denyModel?.OldValue?.ToObject(); + var afterDeny = denyModel?.OldValue?.ToObject(); + + var beforePermissions = new OverwritePermissions(beforeAllow ?? 0, beforeDeny ?? 0); + var afterPermissions = new OverwritePermissions(afterAllow ?? 0, afterDeny ?? 0); + + PermissionTarget target = entry.Options.OverwriteType == "member" ? PermissionTarget.User : PermissionTarget.Role; + + return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, target); + } + + public OverwritePermissions OldPermissions { get; } + public OverwritePermissions NewPermissions { get; } + + public ulong OverwriteTargetId { get; } + 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 new file mode 100644 index 000000000..0005e304d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs @@ -0,0 +1,22 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class PruneAuditLogData : IAuditLogData + { + private PruneAuditLogData(int pruneDays, int membersRemoved) + { + PruneDays = pruneDays; + MembersRemoved = membersRemoved; + } + + internal static PruneAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + return new PruneAuditLogData(entry.Options.PruneDeleteMemberDays.Value, entry.Options.PruneMembersRemoved.Value); + } + + public int PruneDays { get; } + 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 new file mode 100644 index 000000000..aa951d6e7 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs @@ -0,0 +1,47 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RoleCreateAuditLogData : IAuditLogData + { + private RoleCreateAuditLogData(ulong id, RoleInfo props) + { + RoleId = id; + Properties = props; + } + + internal static RoleCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var colorModel = changes.FirstOrDefault(x => x.ChangedProperty == "color"); + var mentionableModel = changes.FirstOrDefault(x => x.ChangedProperty == "mentionable"); + var hoistModel = changes.FirstOrDefault(x => x.ChangedProperty == "hoist"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); + + uint? colorRaw = colorModel?.NewValue?.ToObject(); + bool? mentionable = mentionableModel?.NewValue?.ToObject(); + bool? hoist = hoistModel?.NewValue?.ToObject(); + string name = nameModel?.NewValue?.ToObject(); + ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject(); + + Color? color = null; + GuildPermissions? permissions = null; + + if (colorRaw.HasValue) + color = new Color(colorRaw.Value); + if (permissionsRaw.HasValue) + permissions = new GuildPermissions(permissionsRaw.Value); + + return new RoleCreateAuditLogData(entry.TargetId.Value, + new RoleInfo(color, mentionable, hoist, name, permissions)); + } + + public ulong RoleId { get; } + public RoleInfo Properties { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs new file mode 100644 index 000000000..e90d70d4d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs @@ -0,0 +1,47 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RoleDeleteAuditLogData : IAuditLogData + { + private RoleDeleteAuditLogData(ulong id, RoleInfo props) + { + RoleId = id; + Properties = props; + } + + internal static RoleDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var colorModel = changes.FirstOrDefault(x => x.ChangedProperty == "color"); + var mentionableModel = changes.FirstOrDefault(x => x.ChangedProperty == "mentionable"); + var hoistModel = changes.FirstOrDefault(x => x.ChangedProperty == "hoist"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); + + uint? colorRaw = colorModel?.OldValue?.ToObject(); + bool? mentionable = mentionableModel?.OldValue?.ToObject(); + bool? hoist = hoistModel?.OldValue?.ToObject(); + string name = nameModel?.OldValue?.ToObject(); + ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject(); + + Color? color = null; + GuildPermissions? permissions = null; + + if (colorRaw.HasValue) + color = new Color(colorRaw.Value); + if (permissionsRaw.HasValue) + permissions = new GuildPermissions(permissionsRaw.Value); + + return new RoleDeleteAuditLogData(entry.TargetId.Value, + new RoleInfo(color, mentionable, hoist, name, permissions)); + } + + public ulong RoleId { get; } + public RoleInfo Properties { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs new file mode 100644 index 000000000..2208990e6 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs @@ -0,0 +1,21 @@ +namespace Discord.Rest +{ + public struct RoleInfo + { + internal RoleInfo(Color? color, bool? mentionable, bool? hoist, string name, + GuildPermissions? permissions) + { + Color = color; + Mentionable = mentionable; + Hoist = hoist; + Name = name; + Permissions = permissions; + } + + public Color? Color { get; } + public bool? Mentionable { get; } + public bool? Hoist { get; } + public string Name { get; } + 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 new file mode 100644 index 000000000..be484e2d5 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs @@ -0,0 +1,62 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RoleUpdateAuditLogData : IAuditLogData + { + private RoleUpdateAuditLogData(ulong id, RoleInfo oldProps, RoleInfo newProps) + { + RoleId = id; + Before = oldProps; + After = newProps; + } + + internal static RoleUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var colorModel = changes.FirstOrDefault(x => x.ChangedProperty == "color"); + var mentionableModel = changes.FirstOrDefault(x => x.ChangedProperty == "mentionable"); + var hoistModel = changes.FirstOrDefault(x => x.ChangedProperty == "hoist"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); + + uint? oldColorRaw = colorModel?.OldValue?.ToObject(), + newColorRaw = colorModel?.NewValue?.ToObject(); + bool? oldMentionable = mentionableModel?.OldValue?.ToObject(), + newMentionable = mentionableModel?.NewValue?.ToObject(); + bool? oldHoist = hoistModel?.OldValue?.ToObject(), + newHoist = hoistModel?.NewValue?.ToObject(); + string oldName = nameModel?.OldValue?.ToObject(), + newName = nameModel?.NewValue?.ToObject(); + ulong? oldPermissionsRaw = permissionsModel?.OldValue?.ToObject(), + newPermissionsRaw = permissionsModel?.OldValue?.ToObject(); + + Color? oldColor = null, + newColor = null; + GuildPermissions? oldPermissions = null, + newPermissions = null; + + if (oldColorRaw.HasValue) + oldColor = new Color(oldColorRaw.Value); + if (newColorRaw.HasValue) + newColor = new Color(newColorRaw.Value); + if (oldPermissionsRaw.HasValue) + oldPermissions = new GuildPermissions(oldPermissionsRaw.Value); + if (newPermissionsRaw.HasValue) + newPermissions = new GuildPermissions(newPermissionsRaw.Value); + + var oldProps = new RoleInfo(oldColor, oldMentionable, oldHoist, oldName, oldPermissions); + var newProps = new RoleInfo(newColor, newMentionable, newHoist, newName, newPermissions); + + return new RoleUpdateAuditLogData(entry.TargetId.Value, oldProps, newProps); + } + + public ulong RoleId { get; } + public RoleInfo Before { get; } + public RoleInfo After { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs new file mode 100644 index 000000000..c94f18271 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs @@ -0,0 +1,23 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class UnbanAuditLogData : IAuditLogData + { + private UnbanAuditLogData(IUser user) + { + Target = user; + } + + internal static UnbanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + return new UnbanAuditLogData(RestUser.Create(discord, userInfo)); + } + + 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 new file mode 100644 index 000000000..1ae45fb8c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs @@ -0,0 +1,44 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class WebhookCreateAuditLogData : IAuditLogData + { + private WebhookCreateAuditLogData(IWebhook webhook, WebhookType type, string name, ulong channelId) + { + Webhook = webhook; + Name = name; + Type = type; + ChannelId = channelId; + } + + internal static WebhookCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var channelId = channelIdModel.NewValue.ToObject(); + var type = typeModel.NewValue.ToObject(); + var name = nameModel.NewValue.ToObject(); + + var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); + var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); + + return new WebhookCreateAuditLogData(webhook, type, name, channelId); + } + + //Corresponds to the *current* data + public IWebhook Webhook { get; } + + //Corresponds to the *audit log* data + public WebhookType Type { get; } + public string Name { get; } + 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 new file mode 100644 index 000000000..4133d5dff --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs @@ -0,0 +1,46 @@ +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 +{ + public class WebhookDeleteAuditLogData : IAuditLogData + { + private WebhookDeleteAuditLogData(ulong id, ulong channel, WebhookType type, string name, string avatar) + { + WebhookId = id; + ChannelId = channel; + Name = name; + Type = type; + Avatar = avatar; + } + + internal static WebhookDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); + + var channelId = channelIdModel.OldValue.ToObject(); + var type = typeModel.OldValue.ToObject(); + var name = nameModel.OldValue.ToObject(); + var avatarHash = avatarHashModel?.OldValue?.ToObject(); + + return new WebhookDeleteAuditLogData(entry.TargetId.Value, channelId, type, name, avatarHash); + } + + public ulong WebhookId { get; } + public ulong ChannelId { get; } + public WebhookType Type { get; } + public string Name { get; } + 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 new file mode 100644 index 000000000..26975cc7c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs @@ -0,0 +1,16 @@ +namespace Discord.Rest +{ + public struct WebhookInfo + { + internal WebhookInfo(string name, ulong? channelId, string avatar) + { + Name = name; + ChannelId = channelId; + Avatar = avatar; + } + + public string Name { get; } + public ulong? ChannelId { get; } + 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 new file mode 100644 index 000000000..54da42a8b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs @@ -0,0 +1,52 @@ +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 +{ + public class WebhookUpdateAuditLogData : IAuditLogData + { + private WebhookUpdateAuditLogData(IWebhook webhook, WebhookInfo before, WebhookInfo after) + { + Webhook = webhook; + Before = before; + After = after; + } + + internal static WebhookUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); + + var oldName = nameModel?.OldValue?.ToObject(); + var oldChannelId = channelIdModel?.OldValue?.ToObject(); + var oldAvatar = avatarHashModel?.OldValue?.ToObject(); + var before = new WebhookInfo(oldName, oldChannelId, oldAvatar); + + var newName = nameModel?.NewValue?.ToObject(); + var newChannelId = channelIdModel?.NewValue?.ToObject(); + var newAvatar = avatarHashModel?.NewValue?.ToObject(); + var after = new WebhookInfo(newName, newChannelId, newAvatar); + + var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); + var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); + + return new WebhookUpdateAuditLogData(webhook, before, after); + } + + //Again, the *current* data + public IWebhook Webhook { get; } + + //And the *audit log* data + public WebhookInfo Before { get; } + public WebhookInfo After { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs new file mode 100644 index 000000000..9e30a5014 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -0,0 +1,38 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RestAuditLogEntry : RestEntity, IAuditLogEntry + { + private RestAuditLogEntry(BaseDiscordClient discord, Model fullLog, EntryModel model, IUser user) + : base(discord, model.Id) + { + Action = model.Action; + Data = AuditLogHelper.CreateData(discord, fullLog, model); + User = user; + Reason = model.Reason; + } + + internal static RestAuditLogEntry Create(BaseDiscordClient discord, Model fullLog, EntryModel model) + { + var userInfo = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId); + IUser user = null; + if (userInfo != null) + user = RestUser.Create(discord, userInfo); + + return new RestAuditLogEntry(discord, fullLog, model, user); + } + + /// + public ActionType Action { get; } + /// + public IAuditLogData Data { get; } + /// + public IUser User { get; } + /// + public string Reason { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 8976a8557..6a3521bff 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -168,7 +168,7 @@ namespace Discord.Rest /// invalid characters as defined by . /// /// - /// is . + /// is null. /// /// /// The specified path, file name, or both exceed the system-defined maximum length. For example, on diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index 2895dc17d..b0eed8b25 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -12,37 +12,102 @@ namespace Discord.Rest /// /// Sends a message to this message channel. /// + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); #if FILESYSTEM /// /// Sends a file to this message channel, with an optional caption. /// + /// The file path of the file. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); #endif /// /// Sends a file to this message channel, with an optional caption. /// + /// The of the file to be sent. + /// The name of the attachment. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// If you wish to upload an image and have it embedded in a embed, you may + /// upload the file and refer to the file with "attachment://filename.ext" in the + /// . + /// + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); /// - /// Gets a message from this message channel with the given ID, or if not found. + /// Gets a message from this message channel with the given id, or null if not found. /// + /// The ID of the message. + /// The options to be used when sending the request. + /// + /// The message gotten from either the cache or the download, or null if none is found. + /// Task GetMessageAsync(ulong id, RequestOptions options = null); /// /// Gets the last N messages from this message channel. /// + /// The numbers of message to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. Flattening the paginated response into a collection of messages with + /// is required if you wish to access the messages. + /// IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); /// /// Gets a collection of messages in this channel. /// + /// The ID of the starting message to get the messages from. + /// The direction of the messages to be gotten from. + /// The numbers of message to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. Flattening the paginated response into a collection of messages with + /// is required if you wish to access the messages. + /// IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); /// /// Gets a collection of messages in this channel. /// + /// The starting message to get the messages from. + /// The direction of the messages to be gotten from. + /// The numbers of message to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of messages. Flattening the paginated response into a collection of messages with + /// is required if you wish to access the messages. + /// IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); /// /// Gets a collection of pinned messages in this channel. /// + /// The options to be used when sending the request. + /// + /// An awaitable Task containing a collection of messages. + /// new Task> GetPinnedMessagesAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index d9a6c1a31..4b4ccb057 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -84,14 +84,40 @@ namespace Discord.Rest => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); /// + /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM /// + /// + /// 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); #endif /// + /// 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); diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index b279f06a3..64f195a0d 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -14,7 +14,7 @@ namespace Discord.Rest internal static class GuildHelper { //General - /// is . + /// is null. public static async Task ModifyAsync(IGuild guild, BaseDiscordClient client, Action func, RequestOptions options) { @@ -32,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 }; @@ -63,7 +62,7 @@ namespace Discord.Rest return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); } - /// is . + /// is null. public static async Task ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, Action func, RequestOptions options) { @@ -113,6 +112,11 @@ namespace Discord.Rest var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); } + public static async Task GetBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, RequestOptions options) + { + var model = await client.ApiClient.GetGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false); + return RestBan.Create(client, model); + } public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, int pruneDays, string reason, RequestOptions options) @@ -141,7 +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 . + /// is null. public static async Task CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options) { @@ -151,7 +155,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestTextChannel.Create(client, guild, model); } - /// is . + /// is null. public static async Task CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options) { @@ -161,7 +165,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestVoiceChannel.Create(client, guild, model); } - /// is . + /// is null. public static async Task CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options) { @@ -196,7 +200,7 @@ namespace Discord.Rest } //Roles - /// is . + /// is null. public static async Task CreateRoleAsync(IGuild guild, BaseDiscordClient client, string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) { @@ -269,6 +273,35 @@ namespace Discord.Rest return model.Pruned; } + // Audit logs + public static IAsyncEnumerable> GetAuditLogsAsync(IGuild guild, BaseDiscordClient client, + ulong? from, int? limit, RequestOptions options) + { + return new PagedAsyncEnumerable( + DiscordConfig.MaxAuditLogEntriesPerBatch, + async (info, ct) => + { + var args = new GetAuditLogsParams + { + Limit = info.PageSize + }; + if (info.Position != null) + args.BeforeEntryId = info.Position.Value; + var model = await client.ApiClient.GetAuditLogsAsync(guild.Id, args, options); + return model.Entries.Select((x) => RestAuditLogEntry.Create(client, model, x)).ToImmutableArray(); + }, + nextPage: (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxAuditLogEntriesPerBatch) + return false; + info.Position = lastPage.Min(x => x.Id); + return true; + }, + start: from, + count: limit + ); + } + //Webhooks public static async Task GetWebhookAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) { @@ -303,7 +336,7 @@ namespace Discord.Rest var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options).ConfigureAwait(false); return emote.ToEntity(); } - /// is . + /// is null. public static async Task ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action func, RequestOptions options) { diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 5a79565ba..e301db892 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -132,7 +132,7 @@ namespace Discord.Rest => GuildHelper.DeleteAsync(this, Discord, options); /// - /// is . + /// is null. public async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await GuildHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); @@ -140,7 +140,7 @@ namespace Discord.Rest } /// - /// is . + /// is null. public async Task ModifyEmbedAsync(Action func, RequestOptions options = null) { var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); @@ -148,7 +148,7 @@ namespace Discord.Rest } /// - /// is . + /// is null. public async Task ReorderChannelsAsync(IEnumerable args, RequestOptions options = null) { var arr = args.ToArray(); @@ -172,6 +172,10 @@ namespace Discord.Rest //Bans public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); + public Task GetBanAsync(IUser user, RequestOptions options = null) + => GuildHelper.GetBanAsync(this, Discord, user.Id, options); + 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) @@ -301,6 +305,10 @@ namespace Discord.Rest public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); + //Audit logs + public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) + => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); + //Webhooks public Task GetWebhookAsync(ulong id, RequestOptions options = null) => GuildHelper.GetWebhookAsync(this, Discord, id, options); @@ -324,7 +332,7 @@ namespace Discord.Rest public Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null) => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); /// - /// is . + /// is null. public Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null) => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); /// @@ -344,6 +352,12 @@ namespace Discord.Rest /// async Task> IGuild.GetBansAsync(RequestOptions options) => await GetBansAsync(options).ConfigureAwait(false); + /// + async Task IGuild.GetBanAsync(IUser user, RequestOptions options) + => await GetBanAsync(user, options).ConfigureAwait(false); + /// + async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) + => await GetBanAsync(userId, options).ConfigureAwait(false); /// async Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) @@ -498,6 +512,14 @@ namespace Discord.Rest Task IGuild.DownloadUsersAsync() => throw new NotSupportedException(); + async Task> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) + { + if (cacheMode == CacheMode.AllowDownload) + return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); + else + return ImmutableArray.Create(); + } + /// async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) => await GetWebhookAsync(id, options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs index a51ac8e09..0f5aaf438 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs @@ -3,7 +3,9 @@ using Model = Discord.API.Attachment; namespace Discord { - /// A Discord attachment. + /// + /// An attachment file seen in a . + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Attachment : IAttachment { @@ -39,7 +41,12 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - /// Returns the filename of the attachment. + /// + /// 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/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index 198ce1a61..d033978d0 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -6,7 +6,7 @@ using Model = Discord.API.Application; namespace Discord.Rest { /// - /// Represents a REST entity that contains information about a Discord application created via the developer portal. + /// 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 diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index fdfe9b5a1..68930e74c 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -9,7 +9,7 @@ using Model = Discord.API.GuildMember; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGuildUser : RestUser, IGuildUser, IUpdateable + public class RestGuildUser : RestUser, IGuildUser { private long? _joinedAtTicks; private ImmutableArray _roleIds; diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index c3cbc9ca7..c3960fa67 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -16,7 +16,7 @@ using System.Collections.Generic; namespace Discord.Audio { //TODO: Add audio reconnecting - internal partial class AudioClient : IAudioClient, IDisposable + internal partial class AudioClient : IAudioClient { internal struct StreamPair { diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 304592442..c236b1045 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -165,6 +165,13 @@ namespace Discord.WebSocket remove { _userVoiceStateUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _userVoiceStateUpdatedEvent = new AsyncEvent>(); + /// Fired when the bot connects to a Discord voice server. + public event Func VoiceServerUpdated + { + add { _voiceServerUpdatedEvent.Add(value); } + remove { _voiceServerUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _voiceServerUpdatedEvent = new AsyncEvent>(); /// Fired when the connected account is updated. public event Func CurrentUserUpdated { add { _selfUpdatedEvent.Add(value); } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 81e573e1d..858fec7fe 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -50,55 +50,80 @@ namespace Discord.WebSocket /// /// 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. /// - /// Application information. This reflects your application information you submitted when creating a - /// Discord application via the Developer Portal. + /// An awaitable containing the application information. /// public abstract Task GetApplicationInfoAsync(RequestOptions options = null); /// - /// Gets a user who shares a mutual guild with logged-in user with the provided snowflake ID. + /// Gets a user. /// /// The user snowflake ID. + /// + /// This method gets the user present in the WebSocket cache with the given condition. + /// + /// Sometimes a user may return null due to Discord not sending offline users in large + /// guilds (i.e. guild with 100+ members) actively. To download users on startup, consider enabling + /// . + /// + /// + /// This method does not attempt to fetch users that the logged-in user does not have access to (i.e. + /// users who don't share mutual guild(s) with the current user). + /// + /// /// - /// A user who shares a mutual guild with the logged-in user and who is also present in the WebSocket cache; - /// or when the user cannot be found. + /// A WebSocket-based generic user; null when the user cannot be found. /// public abstract SocketUser GetUser(ulong id); /// - /// Gets a user who shares a mutual guild with the logged-in user with the provided username and discriminator value combo. + /// 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 user who shares a mutual guild with the logged-in user and who is also present in the WebSocket cache; - /// or when the user cannot be found. + /// A WebSocket-based generic user; null when the user cannot be found. /// public abstract SocketUser GetUser(string username, string discriminator); /// - /// Gets a channel that the logged-in user is accessible to with the provided ID. + /// Gets a channel. /// /// The channel snowflake ID. /// - /// A generic channel object (voice, text, category, etc.); or when the channel - /// cannot be found. + /// A generic WebSocket-based channel object (voice, text, category, etc.); null when the + /// channel cannot be found. /// public abstract SocketChannel GetChannel(ulong id); /// - /// Gets a guild that the logged-in user is accessible to with the provided ID. + /// Gets a guild. /// /// The guild snowflake ID. /// - /// A guild; or when the guild cannot be found. + /// A WebSocket-based guild; null when the guild cannot be found. /// public abstract SocketGuild GetGuild(ulong id); /// - /// Gets a voice region with the provided ID. + /// Gets a voice region. /// /// The unique identifier of the voice region. /// - /// A voice region; or if none can be found. + /// A REST-based voice region; null if none can be found. /// public abstract RestVoiceRegion GetVoiceRegion(string id); /// @@ -127,9 +152,14 @@ namespace Discord.WebSocket /// Sets the of the logged-in user. /// /// - /// This method sets the of the user. Please note that Rich Presence cannot be - /// set via this method or client. Rich Presence is strictly limited to RPC clients only. Furthermore, - /// Discord will only accept setting of name and the type of activity. + /// This method sets the of the user. + /// + /// Discord will only accept setting of name and the type of activity. + /// + /// + /// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC + /// clients only. + /// /// /// The activty to be set. /// @@ -149,8 +179,10 @@ namespace Discord.WebSocket /// 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. Note that due to Discord's limitation, - /// this method will only work for users that are in less than 10 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. diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index eef1b1c90..039ea2fe5 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -134,7 +134,7 @@ namespace Discord.WebSocket private int GetShardIdFor(ulong guildId) => (int)((guildId >> 22) % (uint)_totalShards); public int GetShardIdFor(IGuild guild) - => GetShardIdFor(guild.Id); + => GetShardIdFor(guild?.Id ?? 0); private DiscordSocketClient GetShardFor(ulong guildId) => GetShard(GetShardIdFor(guildId)); public DiscordSocketClient GetShardFor(IGuild guild) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index d43e8a1d4..dc4e72020 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -66,9 +66,9 @@ namespace Discord.WebSocket public override IReadOnlyCollection Guilds => State.Guilds; /// public override IReadOnlyCollection PrivateChannels => State.PrivateChannels; - public IReadOnlyCollection DMChannels + public IReadOnlyCollection DMChannels => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); - public IReadOnlyCollection GroupChannels + public IReadOnlyCollection GroupChannels => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); /// public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); @@ -94,11 +94,11 @@ namespace Discord.WebSocket _stateLock = new SemaphoreSlim(1, 1); _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); - _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, + _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); _connection.Connected += () => TimedInvokeAsync(_connectedEvent, nameof(Connected)); _connection.Disconnected += (ex, recon) => TimedInvokeAsync(_disconnectedEvent, nameof(Disconnected), ex); - + _nextAudioId = 1; _connectionGroupLock = groupLock; _parentClient = parentClient; @@ -109,7 +109,7 @@ namespace Discord.WebSocket _gatewayLogger.WarningAsync("Serializer Error", e.ErrorContext.Error).GetAwaiter().GetResult(); e.ErrorContext.Handled = true; }; - + ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); ApiClient.ReceivedGatewayEvent += ProcessMessageAsync; @@ -168,7 +168,7 @@ namespace Discord.WebSocket /// public override async Task StopAsync() => await _connection.StopAsync().ConfigureAwait(false); - + private async Task OnConnectingAsync() { if (_connectionGroupLock != null) @@ -191,11 +191,11 @@ namespace Discord.WebSocket //Wait for READY await _connection.WaitAsync().ConfigureAwait(false); - + await _gatewayLogger.DebugAsync("Sending Status").ConfigureAwait(false); await SendStatusAsync().ConfigureAwait(false); } - finally + finally { if (_connectionGroupLock != null) { @@ -240,22 +240,22 @@ namespace Discord.WebSocket } /// - public override async Task GetApplicationInfoAsync(RequestOptions options = null) + public override async Task GetApplicationInfoAsync(RequestOptions options = null) => _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options ?? RequestOptions.Default).ConfigureAwait(false)); /// - public override SocketGuild GetGuild(ulong id) - => State.GetGuild(id); + public override SocketGuild GetGuild(ulong id) + => State.GetGuild(id); /// - public override SocketChannel GetChannel(ulong id) + public override SocketChannel GetChannel(ulong id) => State.GetChannel(id); - + /// - public override SocketUser GetUser(ulong id) + public override SocketUser GetUser(ulong id) => State.GetUser(id); /// - public override SocketUser GetUser(string username, string discriminator) + public override SocketUser GetUser(string username, string discriminator) => State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) { @@ -276,7 +276,7 @@ namespace Discord.WebSocket return user; }); } - internal void RemoveUser(ulong id) + internal void RemoveUser(ulong id) => State.RemoveUser(id); /// @@ -353,7 +353,7 @@ namespace Discord.WebSocket Activity = activity; await SendStatusAsync().ConfigureAwait(false); } - + private async Task SendStatusAsync() { if (CurrentUser == null) @@ -387,7 +387,7 @@ namespace Discord.WebSocket if (seq != null) _lastSeq = seq.Value; _lastMessageTime = Environment.TickCount; - + try { switch (opCode) @@ -403,7 +403,7 @@ namespace Discord.WebSocket case GatewayOpCode.Heartbeat: { await _gatewayLogger.DebugAsync("Received Heartbeat").ConfigureAwait(false); - + await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); } break; @@ -428,7 +428,7 @@ namespace Discord.WebSocket _sessionId = null; _lastSeq = 0; - + await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards).ConfigureAwait(false); } break; @@ -488,7 +488,7 @@ namespace Discord.WebSocket } else if (_connection.CancelToken.IsCancellationRequested) return; - + await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); }); @@ -527,7 +527,7 @@ namespace Discord.WebSocket if (guild != null) { guild.Update(State, data); - + if (_unavailableGuildCount != 0) _unavailableGuildCount--; await GuildAvailableAsync(guild).ConfigureAwait(false); @@ -1038,7 +1038,7 @@ namespace Discord.WebSocket SocketUser user = guild.GetUser(data.User.Id); if (user == null) - user = SocketUnknownUser.Create(this, State, data.User); + user = SocketUnknownUser.Create(this, State, data.User); await TimedInvokeAsync(_userBannedEvent, nameof(UserBanned), user, guild).ConfigureAwait(false); } else @@ -1338,7 +1338,7 @@ namespace Discord.WebSocket await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), globalBefore, user).ConfigureAwait(false); } } - + var before = user.Clone(); user.Update(State, data, true); await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); @@ -1479,16 +1479,29 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); var guild = State.GetGuild(data.GuildId); - if (guild != null) + var isCached = guild != null; + var cachedGuild = new Cacheable(guild, data.GuildId, isCached, + () => Task.FromResult(State.GetGuild(data.GuildId) as IGuild)); + + var voiceServer = new SocketVoiceServer(cachedGuild, data.Endpoint, data.Token); + await TimedInvokeAsync(_voiceServerUpdatedEvent, nameof(UserVoiceStateUpdated), voiceServer).ConfigureAwait(false); + + if (isCached) { - string endpoint = data.Endpoint.Substring(0, data.Endpoint.LastIndexOf(':')); + var endpoint = data.Endpoint; + + //Only strip out the port if the endpoint contains it + var portBegin = endpoint.LastIndexOf(':'); + if (portBegin > 0) + endpoint = endpoint.Substring(0, portBegin); + var _ = guild.FinishConnectAudio(endpoint, data.Token).ConfigureAwait(false); } else { await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; } + } break; diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs index 17f200c08..d85230fec 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs @@ -15,7 +15,7 @@ namespace Discord.WebSocket public const string GatewayEncoding = "json"; /// - /// Gets or sets the WebSocket host to connect to. If , the client will use the + /// Gets or sets the WebSocket host to connect to. If null, the client will use the /// /gateway endpoint. /// public string GatewayHost { get; set; } = null; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index 6d769b9c4..c37311a41 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -10,7 +10,12 @@ namespace Discord.WebSocket /// public interface ISocketMessageChannel : IMessageChannel { - /// Gets all messages in this channel's cache. + /// + /// Gets all messages in this channel's cache. + /// + /// + /// A collection of WebSocket-based messages. + /// IReadOnlyCollection CachedMessages { get; } /// @@ -20,6 +25,9 @@ namespace Discord.WebSocket /// Whether the message should be read aloud by Discord or not. /// The to be sent. /// The options to be used when sending the request. + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); #if FILESYSTEM /// @@ -35,6 +43,9 @@ namespace Discord.WebSocket /// upload the file and refer to the file with "attachment://filename.ext" in the /// . /// + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); #endif /// @@ -51,14 +62,47 @@ namespace Discord.WebSocket /// upload the file and refer to the file with "attachment://filename.ext" in the /// . /// + /// + /// An awaitable Task containing the message sent to the channel. + /// new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + /// + /// Gets the cached message if one exists. + /// + /// The ID of the message. + /// + /// Cached message object; null if it doesn't exist in the cache. + /// SocketMessage GetCachedMessage(ulong id); - /// Gets the last N messages from this message channel. + /// + /// Gets the last N messages from this message channel. + /// + /// The number of messages to get. + /// + /// A collection of WebSocket-based messages. + /// IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch); - /// Gets a collection of messages in this channel. + + /// + /// Gets a collection of messages in this channel. + /// + /// The message ID to start the fetching from. + /// The direction of which the message should be gotten from. + /// The number of messages to get. + /// + /// A collection of WebSocket-based messages. + /// IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); - /// Gets a collection of messages in this channel. + /// + /// Gets a collection of messages in this channel. + /// + /// The message to start the fetching from. + /// The direction of which the message should be gotten from. + /// The number of messages to get. + /// + /// A collection of WebSocket-based messages. + /// IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); /// /// Gets a collection of pinned messages in this channel. diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs index 1305233e4..37e6afef1 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs @@ -14,6 +14,7 @@ namespace Discord.WebSocket [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)), diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 8008d434a..763296590 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -30,7 +30,7 @@ namespace Discord.WebSocket Recipient = recipient; recipient.GlobalUser.AddRef(); if (Discord.MessageCacheSize > 0) - _messages = new MessageCache(Discord, this); + _messages = new MessageCache(Discord); } internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) { @@ -78,6 +78,7 @@ namespace Discord.WebSocket => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); /// + /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM @@ -86,6 +87,7 @@ namespace Discord.WebSocket => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); #endif /// + /// 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); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 94bf70493..57fcc51a2 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -39,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); } @@ -108,6 +108,7 @@ namespace Discord.WebSocket => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); /// + /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 8d6f22133..1151f0141 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -21,7 +21,7 @@ namespace Discord.WebSocket /// Gets the guild associated with this channel. /// /// - /// The guild that this channel belongs to. + /// A guild that this channel belongs to. /// public SocketGuild Guild { get; } /// @@ -34,7 +34,7 @@ namespace Discord.WebSocket /// Gets the parent category of this channel. /// /// - /// The parent category ID associated with this channel, or if none is set. + /// A parent category ID associated with this channel, or null if none is set. /// public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; @@ -42,7 +42,7 @@ namespace Discord.WebSocket /// public IReadOnlyCollection PermissionOverwrites => _overwrites; /// - /// Returns a collection of users that are able to view the channel. + /// Gets a collection of users that are able to view the channel. /// /// /// A collection of users that can access the channel (i.e. the users seen in the user list). diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index ae8ab54da..92bc07c60 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -39,7 +39,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) { @@ -152,7 +152,7 @@ namespace Discord.WebSocket /// The ID of the webhook. /// The options to be used when sending the request. /// - /// A webhook associated with the , or if not found. + /// A webhook associated with the , or null if not found. /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetWebhookAsync(this, Discord, id, options); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 1f027e321..ca8f54d0b 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -91,11 +91,11 @@ namespace Discord.WebSocket public Task SyncPromise => _syncPromise.Task; public Task DownloaderPromise => _downloaderPromise.Task; /// - /// Returns the associated with this guild. + /// Gets the associated with this guild. /// public IAudioClient AudioClient => _audioClient; /// - /// Returns the first viewable text channel. + /// Gets the first viewable text channel. /// /// /// This property does not guarantee the user can send message to it. @@ -105,7 +105,7 @@ namespace Discord.WebSocket .OrderBy(c => c.Position) .FirstOrDefault(); /// - /// Returns the AFK voice channel, or if none is set. + /// Gets the AFK voice channel, or null if none is set. /// public SocketVoiceChannel AFKChannel { @@ -116,7 +116,7 @@ namespace Discord.WebSocket } } /// - /// Gets the embed channel set in the widget settings of this guild, or if none is set. + /// Gets the embed channel set in the widget settings of this guild, or null if none is set. /// public SocketGuildChannel EmbedChannel { @@ -127,7 +127,7 @@ namespace Discord.WebSocket } } /// - /// Gets the channel where randomized welcome messages are sent, or if none is set. + /// Gets the channel where randomized welcome messages are sent, or null if none is set. /// public SocketTextChannel SystemChannel { @@ -138,31 +138,34 @@ namespace Discord.WebSocket } } /// - /// Returns a collection of text channels present in this guild. + /// Gets a collection of text channels present in this guild. /// public IReadOnlyCollection TextChannels => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); /// - /// Returns a collection of voice channels present in this guild. + /// Gets a collection of voice channels present in this guild. /// public IReadOnlyCollection VoiceChannels => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); /// - /// Returns a collection of category channels present in this guild. + /// Gets a collection of category channels present in this guild. /// public IReadOnlyCollection CategoryChannels => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); /// - /// Returns the current logged-in user. + /// Gets the current logged-in user. /// public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; /// - /// Returns the @everyone role in this guild. + /// Gets the @everyone role in this guild. /// public SocketRole EveryoneRole => GetRole(Id); /// - /// Returns a collection of channels present in this guild. + /// Gets a collection of channels present in this guild. /// + /// + /// Collection of channels. + /// public IReadOnlyCollection Channels { get @@ -175,10 +178,16 @@ namespace Discord.WebSocket /// /// Gets a collection of emotes created in this guild. /// + /// + /// Collection of emotes. + /// public IReadOnlyCollection Emotes => _emotes; /// /// Gets a collection of features enabled in this guild. /// + /// + /// Collection of features in string. + /// public IReadOnlyCollection Features => _features; /// /// Gets a collection of users in this guild. @@ -188,10 +197,16 @@ namespace Discord.WebSocket /// You may need to enable to fetch the full user list /// upon startup, or use to manually download the users. /// + /// + /// Collection of users. + /// public IReadOnlyCollection Users => _members.ToReadOnlyCollection(); /// /// Gets a collection of roles in this guild. /// + /// + /// Collection of roles. + /// public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); internal SocketGuild(DiscordSocketClient client, ulong id) @@ -357,12 +372,12 @@ namespace Discord.WebSocket => GuildHelper.DeleteAsync(this, Discord, options); /// - /// is . + /// is null. public Task ModifyAsync(Action func, RequestOptions options = null) => GuildHelper.ModifyAsync(this, Discord, func, options); /// - /// is . + /// is null. public Task ModifyEmbedAsync(Action func, RequestOptions options = null) => GuildHelper.ModifyEmbedAsync(this, Discord, func, options); /// @@ -378,7 +393,7 @@ namespace Discord.WebSocket //Bans /// - /// Gets a collection of the banned users in this guild. + /// Returns a collection of the banned users in this guild. /// /// The options to be used when sending the request. /// @@ -386,6 +401,10 @@ namespace Discord.WebSocket /// public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); + public Task GetBanAsync(IUser user, RequestOptions options = null) + => GuildHelper.GetBanAsync(this, Discord, user.Id, options); + 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) @@ -440,7 +459,7 @@ namespace Discord.WebSocket /// /// The name of the new channel. /// The options to be used when sending the request. - /// is . + /// is null. /// /// The created text channel. /// @@ -452,7 +471,7 @@ namespace Discord.WebSocket /// /// The name of the new channel. /// The options to be used when sending the request. - /// is . + /// is null. /// /// The created voice channel. /// @@ -464,7 +483,7 @@ namespace Discord.WebSocket /// /// The name of the new channel. /// The options to be used when sending the request. - /// is . + /// is null. /// /// The created category channel. /// @@ -522,12 +541,12 @@ namespace Discord.WebSocket /// /// The name of the new role. /// - /// The permissions that the new role possesses. Set to to use the default permissions. + /// The permissions that the new role possesses. Set to null to use the default permissions. /// - /// The color of the role. Set to to use the default color. + /// The color of the role. Set to null to use the default color. /// Used to determine if users of this role are separated in the user list. /// The options to be used when sending the request. - /// is . + /// is null. /// /// The created role. /// @@ -627,6 +646,10 @@ namespace Discord.WebSocket _downloaderPromise.TrySetResultAsync(true); } + //Audit logs + public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) + => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); + //Webhooks /// /// Returns the webhook with the provided ID. @@ -634,7 +657,7 @@ namespace Discord.WebSocket /// The ID of the webhook. /// The options to be used when sending the request. /// - /// A webhook associated with the ID. + /// An awaitable Task containing the webhook associated with the ID. /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => GuildHelper.GetWebhookAsync(this, Discord, id, options); @@ -643,7 +666,7 @@ namespace Discord.WebSocket /// /// The options to be used when sending the request. /// - /// A collection of webhooks. + /// An awaitable Task containing a collection of webhooks. /// public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); @@ -656,7 +679,7 @@ namespace Discord.WebSocket public Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null) => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); /// - /// is . + /// is null. public Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null) => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); /// @@ -866,6 +889,12 @@ namespace Discord.WebSocket /// async Task> IGuild.GetBansAsync(RequestOptions options) => await GetBansAsync(options).ConfigureAwait(false); + /// + async Task IGuild.GetBanAsync(IUser user, RequestOptions options) + => await GetBanAsync(user, options).ConfigureAwait(false); + /// + async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) + => await GetBanAsync(userId, options).ConfigureAwait(false); /// Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) @@ -941,6 +970,14 @@ namespace Discord.WebSocket Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Owner); + async Task> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) + { + if (cacheMode == CacheMode.AllowDownload) + return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); + else + return ImmutableArray.Create(); + } + /// async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) => await GetWebhookAsync(id, options).ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index c2cad4d86..8cac95cd3 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -14,7 +14,7 @@ namespace Discord.WebSocket public IReadOnlyCollection Messages => _messages.ToReadOnlyCollection(); - public MessageCache(DiscordSocketClient discord, IChannel channel) + public MessageCache(DiscordSocketClient discord) { _size = discord.MessageCacheSize; _messages = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(_size * 1.05)); @@ -28,7 +28,7 @@ namespace Discord.WebSocket _orderedMessages.Enqueue(message.Id); while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out ulong msgId)) - _messages.TryRemove(msgId, out SocketMessage msg); + _messages.TryRemove(msgId, out SocketMessage _); } } @@ -44,6 +44,8 @@ namespace Discord.WebSocket return result; return null; } + + /// is less than 0. public IReadOnlyCollection GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index d339a20ed..0767f2ad7 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -14,8 +14,20 @@ namespace Discord.WebSocket 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; } @@ -31,10 +43,40 @@ namespace Discord.WebSocket 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(); @@ -69,6 +111,12 @@ namespace Discord.WebSocket 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; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs index bfd6aa042..8df6d51b4 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs @@ -2,12 +2,45 @@ using Model = Discord.API.Gateway.Reaction; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based reaction object. + /// public class SocketReaction : IReaction { + /// + /// Gets the ID of the user who added the reaction. + /// + /// + /// A user snowflake ID. + /// public ulong UserId { get; } + /// + /// Gets the user who added the reaction if possible. + /// + /// + /// A user object where possible. This value is not always returned. + /// public Optional User { get; } + /// + /// Gets the ID of the message that has been reacted to. + /// + /// + /// A message snowflake ID. + /// public ulong MessageId { get; } + /// + /// Gets the message that has been reacted to if possible. + /// + /// + /// A WebSocket-based message where possible. This value is not always returned. + /// public Optional Message { get; } + /// + /// Gets the channel where the reaction takes place in. + /// + /// + /// A WebSocket-based message channel. + /// public ISocketMessageChannel Channel { get; } /// public IEmote Emote { get; } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs index c37f04124..d0ce5025b 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs @@ -3,6 +3,9 @@ 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 { diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 58e87017c..d22464652 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 { 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 10701b418..601677e2e 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -35,6 +35,7 @@ namespace Discord.WebSocket return entity; } + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Group)"; internal new SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser; //IVoiceState diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 73f5f0b8a..8721e722b 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -61,7 +61,7 @@ namespace Discord.WebSocket 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 if none. + /// Returns the voice channel the user is in, or null if none. /// public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; /// @@ -173,6 +173,7 @@ namespace Discord.WebSocket 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 diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index 972ba6ea0..af7710629 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -67,6 +67,7 @@ namespace Discord.WebSocket 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 4cfaa686d..b3eb08f6d 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs @@ -29,6 +29,7 @@ namespace Discord.WebSocket return entity; } + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Unknown)"; internal new SocketUnknownUser Clone() => MemberwiseClone() as SocketUnknownUser; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs index 428405431..d5f0433ad 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs @@ -23,7 +23,7 @@ namespace Discord.WebSocket private readonly Flags _voiceStates; /// - /// Gets the voice channel that the user is currently in; or if none. + /// Gets the voice channel that the user is currently in; or null if none. /// public SocketVoiceChannel VoiceChannel { get; } /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index b0374c85d..b66f14e7d 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -44,6 +44,7 @@ namespace Discord.WebSocket return entity; } + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Webhook)"; internal new SocketWebhookUser Clone() => MemberwiseClone() as SocketWebhookUser; diff --git a/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs b/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs new file mode 100644 index 000000000..c5f13b1a9 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs @@ -0,0 +1,42 @@ +using System.Diagnostics; + +namespace Discord.WebSocket +{ + /// + /// Represents a WebSocket-based voice server. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketVoiceServer + { + /// + /// 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) + { + Guild = guild; + Endpoint = endpoint; + Token = token; + } + + private string DebuggerDisplay => $"SocketVoiceServer ({Guild.Id})"; + } +}