| @@ -1,5 +1,79 @@ | |||||
| # Changelog | # Changelog | ||||
| ## [2.2.0] - 2020-04-16 | |||||
| ### Added | |||||
| - #1247 Implement Client Status Support (9da11b4) | |||||
| - #1310 id overload for RemoveReactionAsync (c88b1da) | |||||
| - #1319 BOOST (faf23de) | |||||
| - #1326 Added a Rest property to DiscordShardedClient (9fede34) | |||||
| - #1348 Add Quote Formatting (265da99) | |||||
| - #1354 Add support for setting X-RateLimit-Precision (9482204) | |||||
| - #1355 Provide ParameterInfo with error ParseResult (3755a02) | |||||
| - #1357 add the "Stream" permission. (b00da3d) | |||||
| - #1358 Add ChannelFollowAdd MessageType (794eba5) | |||||
| - #1369 Add SelfStream voice state property (9bb08c9) | |||||
| - #1372 support X-RateLimit-Reset-After (7b9029d) | |||||
| - #1373 update audit log models (c54867f) | |||||
| - #1377 Support filtering audit log entries on user, action type, and before entry id (68eb71c) | |||||
| - #1386 support guild subscription opt-out (0d54207) | |||||
| - #1387 #1381 Guild PreferredLocale support (a61adb0) | |||||
| - #1406 CustomStatusGame Activity (79a0ea9) | |||||
| - #1413 Implemented Message Reference Property (f86c39d) | |||||
| - #1414 add StartedAt, EndsAt, Elapsed and Remaining to SpotifyGame. (2bba324) | |||||
| - #1432 Add ability to modify the banner for guilds (d734ce0) | |||||
| - suppress messages (cd28892) | |||||
| ### Fixed | |||||
| - #1318 #1314 Don't parse tags within code blocks (c977f2e) | |||||
| - #1333 Remove null coalescing on ToEmbedBuilder Color (120c0f7) | |||||
| - #1337 Fixed attempting to access a non-present optional value (4edda5b) | |||||
| - #1346 CommandExecuted event will fire when a parameter precondition fails like what happens when standard precondition fails. (e8cb031) | |||||
| - #1371 Fix keys of guild update audit (b0a595b) | |||||
| - #1375 Use double precision for X-Reset-After, set CultureInfo when parsing numeric types (606dac3) | |||||
| - #1392 patch todo in NamedTypeReader (0bda8a4) | |||||
| - #1405 add .NET Standard 2.1 support for Color (7f0c0c9) | |||||
| - #1412 GetUsersAsync to use MaxUsersPerBatch const as limit instead of MaxMessagesPerBatch. (5439cba) | |||||
| - #1416 false-positive detection of CustomStatusGame based on Id property (a484651) | |||||
| - #1418 #1335 Add isMentionable parameter to CreateRoleAsync in non-breaking manner (1c63fd4) | |||||
| - #1421 (3ff4e3d) | |||||
| - include MessageFlags and SuppressEmbedParams (d6d4429) | |||||
| ### Changed | |||||
| - #1368 Update ISystemMessage interface to allow reactions (07f4d5f) | |||||
| - #1417 fix #1415 Re-add support for overwrite permissions for news channels (e627f07) | |||||
| - use millisecond precision by default (bcb3534) | |||||
| ### Misc | |||||
| - #1290 Split Unit and Integration tests into separate projects (a797be9) | |||||
| - #1328 Fix #1327 Color.ToString returns wrong value (1e8aa08) | |||||
| - #1329 Fix invalid cref values in docs (363d1c6) | |||||
| - #1330 Fix spelling mistake in ExclusiveBulkDelete warning (c864f48) | |||||
| - #1331 Change token explanation (0484fe8) | |||||
| - #1349 Fixed a spelling error. (af79ed5) | |||||
| - #1353 [ci skip] Removed duplicate "any" from the readme (15b2a36) | |||||
| - #1359 Fixing GatewayEncoding comment (52565ed) | |||||
| - #1379 September 2019 Documentation Update (fd3810e) | |||||
| - #1382 Fix .NET Core 3.0 compatibility + Drop NS1.3 (d199d93) | |||||
| - #1388 fix coercion error with DateTime/Offset (3d39704) | |||||
| - #1393 Utilize ValueTuples (99d7135) | |||||
| - #1400 Fix #1394 Misworded doc for command params args (1c6ee72) | |||||
| - #1401 Fix package publishing in azure pipelines (a08d529) | |||||
| - #1402 Fix packaging (65223a6) | |||||
| - #1403 Cache regex instances in MessageHelper (007b011) | |||||
| - #1424 Fix the Comparer descriptions not linking the type (911523d) | |||||
| - #1426 Fix incorrect and missing colour values for Color fields (9ede6b9) | |||||
| - #1470 Added System.Linq reference (adf823c) | |||||
| - temporary sanity checking in SocketGuild (c870e67) | |||||
| - build and deploy docs automatically (2981d6b) | |||||
| - 2.2.0 (4b602b4) | |||||
| - target the Process env-var scope (3c6b376) | |||||
| - fix metapackage build (1794f95) | |||||
| - copy only _site to docs-static (a8cdadc) | |||||
| - do not exit on failed robocopy (fd204ee) | |||||
| - add idn debugger (91aec9f) | |||||
| - rename IsStream to IsStreaming (dcd9cdd) | |||||
| - feature (40844b9) | |||||
| ## [2.1.1] - 2019-06-08 | ## [2.1.1] - 2019-06-08 | ||||
| ### Fixed | ### Fixed | ||||
| - #994: Remainder parameters now ignore character escaping, as there is no reason to escape characters here (2e95c49) | - #994: Remainder parameters now ignore character escaping, as there is no reason to escape characters here (2e95c49) | ||||
| @@ -40,6 +40,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Analyzers.Tests | |||||
| EndProject | EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Examples", "src\Discord.Net.Examples\Discord.Net.Examples.csproj", "{47820065-3CFB-401C-ACEA-862BD564A404}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Examples", "src\Discord.Net.Examples\Discord.Net.Examples.csproj", "{47820065-3CFB-401C-ACEA-862BD564A404}" | ||||
| EndProject | EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}" | |||||
| EndProject | |||||
| Global | Global | ||||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
| @@ -218,6 +220,18 @@ Global | |||||
| {47820065-3CFB-401C-ACEA-862BD564A404}.Release|x64.Build.0 = Release|Any CPU | {47820065-3CFB-401C-ACEA-862BD564A404}.Release|x64.Build.0 = Release|Any CPU | ||||
| {47820065-3CFB-401C-ACEA-862BD564A404}.Release|x86.ActiveCfg = Release|Any CPU | {47820065-3CFB-401C-ACEA-862BD564A404}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| {47820065-3CFB-401C-ACEA-862BD564A404}.Release|x86.Build.0 = Release|Any CPU | {47820065-3CFB-401C-ACEA-862BD564A404}.Release|x86.Build.0 = Release|Any CPU | ||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Debug|x64.ActiveCfg = Debug|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Debug|x64.Build.0 = Debug|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Debug|x86.ActiveCfg = Debug|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Debug|x86.Build.0 = Debug|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x64.ActiveCfg = Release|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x64.Build.0 = Release|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x86.ActiveCfg = Release|Any CPU | |||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x86.Build.0 = Release|Any CPU | |||||
| EndGlobalSection | EndGlobalSection | ||||
| GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
| HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
| @@ -236,6 +250,7 @@ Global | |||||
| {E169E15A-E82C-45BF-8C24-C2CADB7093AA} = {C7CF5621-7D36-433B-B337-5A2E3C101A71} | {E169E15A-E82C-45BF-8C24-C2CADB7093AA} = {C7CF5621-7D36-433B-B337-5A2E3C101A71} | ||||
| {FC67057C-E92F-4E1C-98BE-46F839C8AD71} = {C7CF5621-7D36-433B-B337-5A2E3C101A71} | {FC67057C-E92F-4E1C-98BE-46F839C8AD71} = {C7CF5621-7D36-433B-B337-5A2E3C101A71} | ||||
| {47820065-3CFB-401C-ACEA-862BD564A404} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} | {47820065-3CFB-401C-ACEA-862BD564A404} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} | ||||
| {4A03840B-9EBE-47E3-89AB-E0914DF21AFB} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} | |||||
| EndGlobalSection | EndGlobalSection | ||||
| GlobalSection(ExtensibilityGlobals) = postSolution | GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} | SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} | ||||
| @@ -1,9 +1,9 @@ | |||||
| <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <VersionPrefix>2.2.0</VersionPrefix> | |||||
| <VersionPrefix>2.3.0</VersionPrefix> | |||||
| <VersionSuffix>dev</VersionSuffix> | <VersionSuffix>dev</VersionSuffix> | ||||
| <Authors>RogueException</Authors> | |||||
| <LangVersion>8.0</LangVersion> | |||||
| <LangVersion>latest</LangVersion> | |||||
| <Authors>Discord.Net Contributors</Authors> | |||||
| <PackageTags>discord;discordapp</PackageTags> | <PackageTags>discord;discordapp</PackageTags> | ||||
| <PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | <PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | ||||
| <PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> | <PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> | ||||
| @@ -0,0 +1,29 @@ | |||||
| --- | |||||
| uid: Discord.DiscordComparers.ChannelComparer | |||||
| summary: *content | |||||
| --- | |||||
| Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IChannel>> to be used to compare channels. | |||||
| --- | |||||
| uid: Discord.DiscordComparers.GuildComparer | |||||
| summary: *content | |||||
| --- | |||||
| Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IGuild>> to be used to compare guilds. | |||||
| --- | |||||
| uid: Discord.DiscordComparers.MessageComparer | |||||
| summary: *content | |||||
| --- | |||||
| Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IMessage>> to be used to compare messages. | |||||
| --- | |||||
| uid: Discord.DiscordComparers.RoleComparer | |||||
| summary: *content | |||||
| --- | |||||
| Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IRole>> to be used to compare roles. | |||||
| --- | |||||
| uid: Discord.DiscordComparers.UserComparer | |||||
| summary: *content | |||||
| --- | |||||
| Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IUser>> to be used to compare users. | |||||
| @@ -9,7 +9,7 @@ | |||||
| "dest": "api", | "dest": "api", | ||||
| "filter": "filterConfig.yml", | "filter": "filterConfig.yml", | ||||
| "properties": { | "properties": { | ||||
| "TargetFramework": "netstandard1.3" | |||||
| "TargetFramework": "netstandard2.0" | |||||
| } | } | ||||
| }], | }], | ||||
| "build": { | "build": { | ||||
| @@ -51,7 +51,7 @@ | |||||
| "overwrite": "_overwrites/**/**.md", | "overwrite": "_overwrites/**/**.md", | ||||
| "globalMetadata": { | "globalMetadata": { | ||||
| "_appTitle": "Discord.Net Documentation", | "_appTitle": "Discord.Net Documentation", | ||||
| "_appFooter": "Discord.Net (c) 2015-2019 2.1.1", | |||||
| "_appFooter": "Discord.Net (c) 2015-2020 2.2.0", | |||||
| "_enableSearch": true, | "_enableSearch": true, | ||||
| "_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg", | "_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg", | ||||
| "_appFaviconPath": "favicon.ico" | "_appFaviconPath": "favicon.ico" | ||||
| @@ -88,7 +88,7 @@ implement [IEmote] and are valid options. | |||||
| *** | *** | ||||
| [AddReactionAsync]: xref:Discord.IUserMessage.AddReactionAsync* | |||||
| [AddReactionAsync]: xref:Discord.IMessage.AddReactionAsync* | |||||
| ## What is a "preemptive rate limit?" | ## What is a "preemptive rate limit?" | ||||
| @@ -71,11 +71,11 @@ By now, your module should look like this: | |||||
| > [!WARNING] | > [!WARNING] | ||||
| > **Avoid using long-running code** in your modules wherever possible. | > **Avoid using long-running code** in your modules wherever possible. | ||||
| > You should **not** be implementing very much logic into your | |||||
| > modules, instead, outsource to a service for that. | |||||
| > Long-running code, by default, within a command module | |||||
| > can cause gateway thread to be blocked; therefore, interrupting | |||||
| > the bot's connection to Discord. | |||||
| > | > | ||||
| > If you are unfamiliar with Inversion of Control, it is recommended | |||||
| > to read the MSDN article on [IoC] and [Dependency Injection]. | |||||
| > You may read more about it in @FAQ.Commands.General . | |||||
| The next step to creating commands is actually creating the commands. | The next step to creating commands is actually creating the commands. | ||||
| @@ -0,0 +1,79 @@ | |||||
| --- | |||||
| uid: Guides.Commands.NamedArguments | |||||
| title: Named Arguments | |||||
| --- | |||||
| # Named Arguments | |||||
| By default, arguments for commands are parsed positionally, meaning | |||||
| that the order matters. But sometimes you may want to define a command | |||||
| with many optional parameters, and it'd be easier for end-users | |||||
| to only specify what they want to set, instead of needing them | |||||
| to specify everything by hand. | |||||
| ## Setting up Named Arguments | |||||
| In order to be able to specify different arguments by name, you have | |||||
| to create a new class that contains all of the optional values that | |||||
| the command will use, and apply an instance of | |||||
| [NamedArgumentTypeAttribute] on it. | |||||
| ### Example - Creating a Named Arguments Type | |||||
| ```cs | |||||
| [NamedArgumentType] | |||||
| public class NamableArguments | |||||
| { | |||||
| public string First { get; set; } | |||||
| public string Second { get; set; } | |||||
| public string Third { get; set; } | |||||
| public string Fourth { get; set; } | |||||
| } | |||||
| ``` | |||||
| ## Usage in a Command | |||||
| The command where you want to use these values can be declared like so: | |||||
| ```cs | |||||
| [Command("act")] | |||||
| public async Task Act(int requiredArg, NamableArguments namedArgs) | |||||
| ``` | |||||
| The command can now be invoked as | |||||
| `.act 42 first: Hello fourth: "A string with spaces must be wrapped in quotes" second: World`. | |||||
| A TypeReader for the named arguments container type is | |||||
| automatically registered. | |||||
| It's important that any other arguments that would be required | |||||
| are placed before the container type. | |||||
| > [!IMPORTANT] | |||||
| > A single command can have only __one__ parameter of a | |||||
| > type annotated with [NamedArgumentTypeAttribute], and it | |||||
| > **MUST** be the last parameter in the list. | |||||
| > A command parameter of such an annotated type | |||||
| > is automatically treated as if that parameter | |||||
| > has [RemainderAttribute](xref:Discord.Commands.RemainderAttribute) | |||||
| > applied. | |||||
| ## Complex Types | |||||
| The TypeReader for Named Argument Types will look for a TypeReader | |||||
| of every property type, meaning any other command parameter type | |||||
| will work just the same. | |||||
| You can also read multiple values into a single property | |||||
| by making that property an `IEnumerable<T>`. So for example, if your | |||||
| Named Argument Type has the following field, | |||||
| ```cs | |||||
| public IEnumerable<int> Numbers { get; set; } | |||||
| ``` | |||||
| then the command can be invoked as | |||||
| `.cmd numbers: "1, 2, 4, 8, 16, 32"` | |||||
| ## Additional Notes | |||||
| The use of [`[OverrideTypeReader]`](xref:Discord.Commands.OverrideTypeReaderAttribute) | |||||
| is also supported on the properties of a Named Argument Type. | |||||
| [NamedArgumentTypeAttribute]: xref:Discord.Commands.NamedArgumentTypeAttribute | |||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Commands; | using Discord.Commands; | ||||
| using Discord.WebSocket; | using Discord.WebSocket; | ||||
| @@ -27,6 +27,8 @@ | |||||
| topicUid: Guides.Commands.Intro | topicUid: Guides.Commands.Intro | ||||
| - name: TypeReaders | - name: TypeReaders | ||||
| topicUid: Guides.Commands.TypeReaders | topicUid: Guides.Commands.TypeReaders | ||||
| - name: Named Arguments | |||||
| topicUid: Guides.Commands.NamedArguments | |||||
| - name: Preconditions | - name: Preconditions | ||||
| topicUid: Guides.Commands.Preconditions | topicUid: Guides.Commands.Preconditions | ||||
| - name: Dependency Injection | - name: Dependency Injection | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord; | using Discord; | ||||
| using Discord.WebSocket; | using Discord.WebSocket; | ||||
| @@ -43,7 +44,7 @@ namespace _01_basic_ping_bot | |||||
| await _client.StartAsync(); | await _client.StartAsync(); | ||||
| // Block the program until it is closed. | // Block the program until it is closed. | ||||
| await Task.Delay(-1); | |||||
| await Task.Delay(Timeout.Infinite); | |||||
| } | } | ||||
| private Task LogAsync(LogMessage log) | private Task LogAsync(LogMessage log) | ||||
| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Net.Http; | using System.Net.Http; | ||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
| using Discord; | using Discord; | ||||
| @@ -45,7 +46,7 @@ namespace _02_commands_framework | |||||
| // Here we initialize the logic required to register our commands. | // Here we initialize the logic required to register our commands. | ||||
| await services.GetRequiredService<CommandHandlingService>().InitializeAsync(); | await services.GetRequiredService<CommandHandlingService>().InitializeAsync(); | ||||
| await Task.Delay(-1); | |||||
| await Task.Delay(Timeout.Infinite); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using _03_sharded_client.Services; | using _03_sharded_client.Services; | ||||
| using Discord; | using Discord; | ||||
| @@ -45,7 +46,7 @@ namespace _03_sharded_client | |||||
| await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); | await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); | ||||
| await client.StartAsync(); | await client.StartAsync(); | ||||
| await Task.Delay(-1); | |||||
| await Task.Delay(Timeout.Infinite); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,74 @@ | |||||
| using System.Collections; | |||||
| using System.Linq; | |||||
| using System.Reflection; | |||||
| using System.Text; | |||||
| namespace idn | |||||
| { | |||||
| public static class Inspector | |||||
| { | |||||
| public static string Inspect(object value) | |||||
| { | |||||
| var builder = new StringBuilder(); | |||||
| if (value != null) | |||||
| { | |||||
| var type = value.GetType().GetTypeInfo(); | |||||
| builder.AppendLine($"[{type.Namespace}.{type.Name}]"); | |||||
| builder.AppendLine($"{InspectProperty(value)}"); | |||||
| if (value is IEnumerable) | |||||
| { | |||||
| var items = (value as IEnumerable).Cast<object>().ToArray(); | |||||
| if (items.Length > 0) | |||||
| { | |||||
| builder.AppendLine(); | |||||
| foreach (var item in items) | |||||
| builder.AppendLine($"- {InspectProperty(item)}"); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| var groups = type.GetProperties(BindingFlags.Instance | BindingFlags.Public) | |||||
| .Where(x => x.GetIndexParameters().Length == 0) | |||||
| .GroupBy(x => x.Name) | |||||
| .OrderBy(x => x.Key) | |||||
| .ToArray(); | |||||
| if (groups.Length > 0) | |||||
| { | |||||
| builder.AppendLine(); | |||||
| int pad = groups.Max(x => x.Key.Length) + 1; | |||||
| foreach (var group in groups) | |||||
| builder.AppendLine($"{group.Key.PadRight(pad, ' ')}{InspectProperty(group.First().GetValue(value))}"); | |||||
| } | |||||
| } | |||||
| } | |||||
| else | |||||
| builder.AppendLine("null"); | |||||
| return builder.ToString(); | |||||
| } | |||||
| private static string InspectProperty(object obj) | |||||
| { | |||||
| if (obj == null) | |||||
| return "null"; | |||||
| var type = obj.GetType(); | |||||
| var debuggerDisplay = type.GetProperty("DebuggerDisplay", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | |||||
| if (debuggerDisplay != null) | |||||
| return debuggerDisplay.GetValue(obj).ToString(); | |||||
| var toString = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) | |||||
| .Where(x => x.Name == "ToString" && x.DeclaringType != typeof(object)) | |||||
| .FirstOrDefault(); | |||||
| if (toString != null) | |||||
| return obj.ToString(); | |||||
| var count = type.GetProperty("Count", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | |||||
| if (count != null) | |||||
| return $"[{count.GetValue(obj)} Items]"; | |||||
| return obj.ToString(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,152 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Linq; | |||||
| using System.Reflection; | |||||
| using System.Threading.Tasks; | |||||
| using Microsoft.CodeAnalysis.CSharp.Scripting; | |||||
| using Microsoft.CodeAnalysis.Scripting; | |||||
| using Discord; | |||||
| using Discord.WebSocket; | |||||
| using System.Collections.Concurrent; | |||||
| using System.Threading; | |||||
| using System.Text; | |||||
| using System.Diagnostics; | |||||
| namespace idn | |||||
| { | |||||
| public class Program | |||||
| { | |||||
| public static readonly string[] Imports = | |||||
| { | |||||
| "System", | |||||
| "System.Collections.Generic", | |||||
| "System.Linq", | |||||
| "System.Threading.Tasks", | |||||
| "System.Diagnostics", | |||||
| "System.IO", | |||||
| "Discord", | |||||
| "Discord.Rest", | |||||
| "Discord.WebSocket", | |||||
| "idn" | |||||
| }; | |||||
| static async Task Main(string[] args) | |||||
| { | |||||
| var token = File.ReadAllText("token.ignore"); | |||||
| var client = new DiscordSocketClient(new DiscordSocketConfig { LogLevel = LogSeverity.Debug }); | |||||
| var logQueue = new ConcurrentQueue<LogMessage>(); | |||||
| var logCancelToken = new CancellationTokenSource(); | |||||
| int presenceUpdates = 0; | |||||
| client.Log += msg => | |||||
| { | |||||
| logQueue.Enqueue(msg); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| Console.CancelKeyPress += (_ev, _s) => | |||||
| { | |||||
| logCancelToken.Cancel(); | |||||
| }; | |||||
| var logTask = Task.Run(async () => | |||||
| { | |||||
| var fs = new FileStream("idn.log", FileMode.Append); | |||||
| var logStringBuilder = new StringBuilder(200); | |||||
| string logString = ""; | |||||
| byte[] helloBytes = Encoding.UTF8.GetBytes($"### new log session: {DateTime.Now} ###\n\n"); | |||||
| await fs.WriteAsync(helloBytes); | |||||
| while (!logCancelToken.IsCancellationRequested) | |||||
| { | |||||
| if (logQueue.TryDequeue(out var msg)) | |||||
| { | |||||
| if (msg.Message?.IndexOf("PRESENCE_UPDATE)") > 0) | |||||
| { | |||||
| presenceUpdates++; | |||||
| continue; | |||||
| } | |||||
| _ = msg.ToString(builder: logStringBuilder); | |||||
| logStringBuilder.AppendLine(); | |||||
| logString = logStringBuilder.ToString(); | |||||
| Debug.Write(logString, "DNET"); | |||||
| await fs.WriteAsync(Encoding.UTF8.GetBytes(logString)); | |||||
| } | |||||
| await fs.FlushAsync(); | |||||
| try | |||||
| { | |||||
| await Task.Delay(100, logCancelToken.Token); | |||||
| } | |||||
| finally { } | |||||
| } | |||||
| byte[] goodbyeBytes = Encoding.UTF8.GetBytes($"#!! end log session: {DateTime.Now} !!#\n\n\n"); | |||||
| await fs.WriteAsync(goodbyeBytes); | |||||
| await fs.DisposeAsync(); | |||||
| }); | |||||
| await client.LoginAsync(TokenType.Bot, token); | |||||
| await client.StartAsync(); | |||||
| var options = ScriptOptions.Default | |||||
| .AddReferences(GetAssemblies().ToArray()) | |||||
| .AddImports(Imports); | |||||
| var globals = new ScriptGlobals | |||||
| { | |||||
| Client = client, | |||||
| PUCount = -1, | |||||
| }; | |||||
| while (true) | |||||
| { | |||||
| Console.Write("> "); | |||||
| string input = Console.ReadLine(); | |||||
| if (input == "quit!") | |||||
| { | |||||
| break; | |||||
| } | |||||
| object eval; | |||||
| try | |||||
| { | |||||
| globals.PUCount = presenceUpdates; | |||||
| eval = await CSharpScript.EvaluateAsync(input, options, globals); | |||||
| } | |||||
| catch (Exception e) | |||||
| { | |||||
| eval = e; | |||||
| } | |||||
| Console.WriteLine(Inspector.Inspect(eval)); | |||||
| } | |||||
| await client.StopAsync(); | |||||
| client.Dispose(); | |||||
| logCancelToken.Cancel(); | |||||
| try | |||||
| { await logTask; } | |||||
| finally { Console.WriteLine("goodbye!"); } | |||||
| } | |||||
| static IEnumerable<Assembly> GetAssemblies() | |||||
| { | |||||
| var Assemblies = Assembly.GetEntryAssembly().GetReferencedAssemblies(); | |||||
| foreach (var a in Assemblies) | |||||
| { | |||||
| var asm = Assembly.Load(a); | |||||
| yield return asm; | |||||
| } | |||||
| yield return Assembly.GetEntryAssembly(); | |||||
| } | |||||
| public class ScriptGlobals | |||||
| { | |||||
| public DiscordSocketClient Client { get; set; } | |||||
| public int PUCount { get; set; } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | |||||
| <PropertyGroup> | |||||
| <OutputType>Exe</OutputType> | |||||
| <TargetFramework>netcoreapp3.1</TargetFramework> | |||||
| </PropertyGroup> | |||||
| <ItemGroup> | |||||
| <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.5.0" /> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" /> | |||||
| </ItemGroup> | |||||
| </Project> | |||||
| @@ -0,0 +1 @@ | |||||
| Get-Content .\bin\Debug\netcoreapp3.1\idn.log -Tail 3 -Wait | |||||
| @@ -36,7 +36,7 @@ namespace Discord.Commands | |||||
| internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | ||||
| /// <summary> | /// <summary> | ||||
| /// Occurs when a command is successfully executed without any error. | |||||
| /// Occurs when a command is executed. | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// This event is fired when a command has been executed, successfully or not. When a command fails to | /// This event is fired when a command has been executed, successfully or not. When a command fails to | ||||
| @@ -49,7 +49,7 @@ namespace Discord.Commands | |||||
| private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | ||||
| private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders; | private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders; | ||||
| private readonly ConcurrentDictionary<Type, TypeReader> _defaultTypeReaders; | private readonly ConcurrentDictionary<Type, TypeReader> _defaultTypeReaders; | ||||
| private readonly ImmutableList<Tuple<Type, Type>> _entityTypeReaders; //TODO: Candidate for C#7 Tuple | |||||
| private readonly ImmutableList<(Type EntityType, Type TypeReaderType)> _entityTypeReaders; | |||||
| private readonly HashSet<ModuleInfo> _moduleDefs; | private readonly HashSet<ModuleInfo> _moduleDefs; | ||||
| private readonly CommandMap _map; | private readonly CommandMap _map; | ||||
| @@ -124,11 +124,11 @@ namespace Discord.Commands | |||||
| _defaultTypeReaders[typeof(string)] = | _defaultTypeReaders[typeof(string)] = | ||||
| new PrimitiveTypeReader<string>((string x, out string y) => { y = x; return true; }, 0); | new PrimitiveTypeReader<string>((string x, out string y) => { y = x; return true; }, 0); | ||||
| var entityTypeReaders = ImmutableList.CreateBuilder<Tuple<Type, Type>>(); | |||||
| entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IMessage), typeof(MessageTypeReader<>))); | |||||
| entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IChannel), typeof(ChannelTypeReader<>))); | |||||
| entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IRole), typeof(RoleTypeReader<>))); | |||||
| entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IUser), typeof(UserTypeReader<>))); | |||||
| var entityTypeReaders = ImmutableList.CreateBuilder<(Type, Type)>(); | |||||
| entityTypeReaders.Add((typeof(IMessage), typeof(MessageTypeReader<>))); | |||||
| entityTypeReaders.Add((typeof(IChannel), typeof(ChannelTypeReader<>))); | |||||
| entityTypeReaders.Add((typeof(IRole), typeof(RoleTypeReader<>))); | |||||
| entityTypeReaders.Add((typeof(IUser), typeof(UserTypeReader<>))); | |||||
| _entityTypeReaders = entityTypeReaders.ToImmutable(); | _entityTypeReaders = entityTypeReaders.ToImmutable(); | ||||
| } | } | ||||
| @@ -408,7 +408,7 @@ namespace Discord.Commands | |||||
| var typeInfo = type.GetTypeInfo(); | var typeInfo = type.GetTypeInfo(); | ||||
| if (typeInfo.IsEnum) | if (typeInfo.IsEnum) | ||||
| return true; | return true; | ||||
| return _entityTypeReaders.Any(x => type == x.Item1 || typeInfo.ImplementedInterfaces.Contains(x.Item2)); | |||||
| return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.TypeReaderType)); | |||||
| } | } | ||||
| internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) | internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) | ||||
| { | { | ||||
| @@ -439,9 +439,9 @@ namespace Discord.Commands | |||||
| //Is this an entity? | //Is this an entity? | ||||
| for (int i = 0; i < _entityTypeReaders.Count; i++) | for (int i = 0; i < _entityTypeReaders.Count; i++) | ||||
| { | { | ||||
| if (type == _entityTypeReaders[i].Item1 || typeInfo.ImplementedInterfaces.Contains(_entityTypeReaders[i].Item1)) | |||||
| if (type == _entityTypeReaders[i].EntityType || typeInfo.ImplementedInterfaces.Contains(_entityTypeReaders[i].EntityType)) | |||||
| { | { | ||||
| reader = Activator.CreateInstance(_entityTypeReaders[i].Item2.MakeGenericType(type)) as TypeReader; | |||||
| reader = Activator.CreateInstance(_entityTypeReaders[i].TypeReaderType.MakeGenericType(type)) as TypeReader; | |||||
| _defaultTypeReaders[type] = reader; | _defaultTypeReaders[type] = reader; | ||||
| return reader; | return reader; | ||||
| } | } | ||||
| @@ -44,7 +44,7 @@ namespace Discord.Commands | |||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// QuotationMarkAliasMap = new Dictionary<char, char%gt;() | |||||
| /// QuotationMarkAliasMap = new Dictionary<char, char>() | |||||
| /// { | /// { | ||||
| /// {'\"', '\"' }, | /// {'\"', '\"' }, | ||||
| /// {'“', '”' }, | /// {'“', '”' }, | ||||
| @@ -31,9 +31,13 @@ namespace Discord.Commands | |||||
| /// </param> | /// </param> | ||||
| /// <param name="isTTS">Specifies if Discord should read this <paramref name="message"/> aloud using text-to-speech.</param> | /// <param name="isTTS">Specifies if Discord should read this <paramref name="message"/> aloud using text-to-speech.</param> | ||||
| /// <param name="embed">An embed to be displayed alongside the <paramref name="message"/>.</param> | /// <param name="embed">An embed to be displayed alongside the <paramref name="message"/>.</param> | ||||
| protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the <paramref name="message"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| { | { | ||||
| return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); | |||||
| return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// The method to execute before executing the command. | /// The method to execute before executing the command. | ||||
| @@ -20,6 +20,10 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// The user is watching some form of media. | /// The user is watching some form of media. | ||||
| /// </summary> | /// </summary> | ||||
| Watching = 3 | |||||
| Watching = 3, | |||||
| /// <summary> | |||||
| /// The user has set a custom status. | |||||
| /// </summary> | |||||
| CustomStatus = 4, | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,40 @@ | |||||
| using System; | |||||
| using System.Diagnostics; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// A user's activity for their custom status. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class CustomStatusGame : Game | |||||
| { | |||||
| internal CustomStatusGame() { } | |||||
| /// <summary> | |||||
| /// Gets the emote, if it is set. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// An <see cref="IEmote"/> containing the <see cref="Emoji"/> or <see cref="GuildEmote"/> set by the user. | |||||
| /// </returns> | |||||
| public IEmote Emote { get; internal set; } | |||||
| /// <summary> | |||||
| /// Gets the timestamp of when this status was created. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="DateTimeOffset"/> containing the time when this status was created. | |||||
| /// </returns> | |||||
| public DateTimeOffset CreatedAt { get; internal set; } | |||||
| /// <summary> | |||||
| /// Gets the state of the status. | |||||
| /// </summary> | |||||
| public string State { get; internal set; } | |||||
| public override string ToString() | |||||
| => $"{Emote} {State}"; | |||||
| private string DebuggerDisplay => $"{Name}"; | |||||
| } | |||||
| } | |||||
| @@ -31,6 +31,23 @@ namespace Discord | |||||
| /// A string containing the name of the song (e.g. <c>Lonely Together (feat. Rita Ora)</c>). | /// A string containing the name of the song (e.g. <c>Lonely Together (feat. Rita Ora)</c>). | ||||
| /// </returns> | /// </returns> | ||||
| public string TrackTitle { get; internal set; } | public string TrackTitle { get; internal set; } | ||||
| /// <summary> | |||||
| /// Gets the date when the track started playing. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="DateTimeOffset"/> containing the start timestamp of the song. | |||||
| /// </returns> | |||||
| public DateTimeOffset? StartedAt { get; internal set; } | |||||
| /// <summary> | |||||
| /// Gets the date when the track ends. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="DateTimeOffset"/> containing the finish timestamp of the song. | |||||
| /// </returns> | |||||
| public DateTimeOffset? EndsAt { get; internal set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the duration of the song. | /// Gets the duration of the song. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -39,6 +56,22 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| public TimeSpan? Duration { get; internal set; } | public TimeSpan? Duration { get; internal set; } | ||||
| /// <summary> | |||||
| /// Gets the elapsed duration of the song. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="TimeSpan"/> containing the elapsed duration of the song. | |||||
| /// </returns> | |||||
| public TimeSpan? Elapsed => DateTimeOffset.UtcNow - StartedAt; | |||||
| /// <summary> | |||||
| /// Gets the remaining duration of the song. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="TimeSpan"/> containing the remaining duration of the song. | |||||
| /// </returns> | |||||
| public TimeSpan? Remaining => EndsAt - DateTimeOffset.UtcNow; | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the track ID of the song. | /// Gets the track ID of the song. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -61,6 +61,18 @@ namespace Discord | |||||
| /// A guild member's role collection was updated. | /// A guild member's role collection was updated. | ||||
| /// </summary> | /// </summary> | ||||
| MemberRoleUpdated = 25, | MemberRoleUpdated = 25, | ||||
| /// <summary> | |||||
| /// A guild member moved to a voice channel. | |||||
| /// </summary> | |||||
| MemberMoved = 26, | |||||
| /// <summary> | |||||
| /// A guild member disconnected from a voice channel. | |||||
| /// </summary> | |||||
| MemberDisconnected = 27, | |||||
| /// <summary> | |||||
| /// A bot was added to this guild. | |||||
| /// </summary> | |||||
| BotAdded = 28, | |||||
| /// <summary> | /// <summary> | ||||
| /// A role was created in this guild. | /// A role was created in this guild. | ||||
| @@ -117,6 +129,18 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// A message was deleted from this guild. | /// A message was deleted from this guild. | ||||
| /// </summary> | /// </summary> | ||||
| MessageDeleted = 72 | |||||
| MessageDeleted = 72, | |||||
| /// <summary> | |||||
| /// Multiple messages were deleted from this guild. | |||||
| /// </summary> | |||||
| MessageBulkDeleted = 73, | |||||
| /// <summary> | |||||
| /// A message was pinned from this guild. | |||||
| /// </summary> | |||||
| MessagePinned = 74, | |||||
| /// <summary> | |||||
| /// A message was unpinned from this guild. | |||||
| /// </summary> | |||||
| MessageUnpinned = 75, | |||||
| } | } | ||||
| } | } | ||||
| @@ -23,11 +23,15 @@ namespace Discord | |||||
| /// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | /// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | ||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
| /// contains the sent message. | /// contains the sent message. | ||||
| /// </returns> | /// </returns> | ||||
| Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||||
| Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -40,8 +40,8 @@ namespace Discord | |||||
| /// Creates a new invite to this channel. | /// Creates a new invite to this channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// The following example creates a new invite to this channel; the invite lasts for 12 hours and can only | |||||
| /// be used 3 times throughout its lifespan. | |||||
| /// <para>The following example creates a new invite to this channel; the invite lasts for 12 hours and can only | |||||
| /// be used 3 times throughout its lifespan.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// await guildChannel.CreateInviteAsync(maxAge: 43200, maxUses: 3); | /// await guildChannel.CreateInviteAsync(maxAge: 43200, maxUses: 3); | ||||
| /// </code> | /// </code> | ||||
| @@ -60,8 +60,8 @@ namespace Discord | |||||
| /// Gets a collection of all invites to this channel. | /// Gets a collection of all invites to this channel. | ||||
| /// </summary>B | /// </summary>B | ||||
| /// <example> | /// <example> | ||||
| /// The following example gets all of the invites that have been created in this channel and selects the | |||||
| /// most used invite. | |||||
| /// <para>The following example gets all of the invites that have been created in this channel and selects the | |||||
| /// most used invite.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// var invites = await channel.GetInvitesAsync(); | /// var invites = await channel.GetInvitesAsync(); | ||||
| /// if (invites.Count == 0) return; | /// if (invites.Count == 0) return; | ||||
| @@ -30,7 +30,7 @@ namespace Discord | |||||
| /// Gets the current slow-mode delay for this channel. | /// Gets the current slow-mode delay for this channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// An <see cref="Int32"/> representing the time in seconds required before the user can send another | |||||
| /// An <see cref="int"/> representing the time in seconds required before the user can send another | |||||
| /// message; <c>0</c> if disabled. | /// message; <c>0</c> if disabled. | ||||
| /// </returns> | /// </returns> | ||||
| int SlowModeInterval { get; } | int SlowModeInterval { get; } | ||||
| @@ -39,7 +39,7 @@ namespace Discord | |||||
| /// Bulk-deletes multiple messages. | /// Bulk-deletes multiple messages. | ||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// The following example gets 250 messages from the channel and deletes them. | |||||
| /// <para>The following example gets 250 messages from the channel and deletes them.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// var messages = await textChannel.GetMessagesAsync(250).FlattenAsync(); | /// var messages = await textChannel.GetMessagesAsync(250).FlattenAsync(); | ||||
| /// await textChannel.DeleteMessagesAsync(messages); | /// await textChannel.DeleteMessagesAsync(messages); | ||||
| @@ -38,6 +38,10 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| public Optional<Image?> Icon { get; set; } | public Optional<Image?> Icon { get; set; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the banner of the guild. | |||||
| /// </summary> | |||||
| public Optional<Image?> Banner { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the guild's splash image. | /// Gets or sets the guild's splash image. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| @@ -510,7 +510,7 @@ namespace Discord | |||||
| /// Creates a new text channel in this guild. | /// Creates a new text channel in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// The following example creates a new text channel under an existing category named <c>Wumpus</c> with a set topic. | |||||
| /// <para>The following example creates a new text channel under an existing category named <c>Wumpus</c> with a set topic.</para> | |||||
| /// <code language="cs" region="CreateTextChannelAsync" | /// <code language="cs" region="CreateTextChannelAsync" | ||||
| /// source="..\..\..\Discord.Net.Examples\Core\Entities\Guilds\IGuild.Examples.cs"/> | /// source="..\..\..\Discord.Net.Examples\Core\Entities\Guilds\IGuild.Examples.cs"/> | ||||
| /// </example> | /// </example> | ||||
| @@ -598,6 +598,21 @@ namespace Discord | |||||
| /// role. | /// role. | ||||
| /// </returns> | /// </returns> | ||||
| Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null); | Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null); | ||||
| // TODO remove CreateRoleAsync overload that does not have isMentionable when breaking change is acceptable | |||||
| /// <summary> | |||||
| /// Creates a new role with the provided name. | |||||
| /// </summary> | |||||
| /// <param name="name">The new name for the role.</param> | |||||
| /// <param name="permissions">The guild permission that the role should possess.</param> | |||||
| /// <param name="color">The color of the role.</param> | |||||
| /// <param name="isHoisted">Whether the role is separated from others on the sidebar.</param> | |||||
| /// <param name="isMentionable">Whether the role can be mentioned.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. The task result contains the newly created | |||||
| /// role. | |||||
| /// </returns> | |||||
| Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, bool isMentionable = false, RequestOptions options = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Adds a user to this guild. | /// Adds a user to this guild. | ||||
| @@ -0,0 +1,24 @@ | |||||
| using System; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Specifies the type of mentions that will be notified from the message content. | |||||
| /// </summary> | |||||
| [Flags] | |||||
| public enum AllowedMentionTypes | |||||
| { | |||||
| /// <summary> | |||||
| /// Controls role mentions. | |||||
| /// </summary> | |||||
| Roles, | |||||
| /// <summary> | |||||
| /// Controls user mentions. | |||||
| /// </summary> | |||||
| Users, | |||||
| /// <summary> | |||||
| /// Controls <code>@everyone</code> and <code>@here</code> mentions. | |||||
| /// </summary> | |||||
| Everyone, | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Defines which mentions and types of mentions that will notify users from the message content. | |||||
| /// </summary> | |||||
| public class AllowedMentions | |||||
| { | |||||
| private static readonly Lazy<AllowedMentions> none = new Lazy<AllowedMentions>(() => new AllowedMentions()); | |||||
| private static readonly Lazy<AllowedMentions> all = new Lazy<AllowedMentions>(() => | |||||
| new AllowedMentions(AllowedMentionTypes.Everyone | AllowedMentionTypes.Users | AllowedMentionTypes.Roles)); | |||||
| /// <summary> | |||||
| /// Gets a value which indicates that no mentions in the message content should notify users. | |||||
| /// </summary> | |||||
| public static AllowedMentions None => none.Value; | |||||
| /// <summary> | |||||
| /// Gets a value which indicates that all mentions in the message content should notify users. | |||||
| /// </summary> | |||||
| public static AllowedMentions All => all.Value; | |||||
| /// <summary> | |||||
| /// Gets or sets the type of mentions that will be parsed from the message content. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// The <see cref="AllowedMentionTypes.Users"/> flag is mutually exclusive with the <see cref="UserIds"/> | |||||
| /// property, and the <see cref="AllowedMentionTypes.Roles"/> flag is mutually exclusive with the | |||||
| /// <see cref="RoleIds"/> property. | |||||
| /// If <c>null</c>, only the ids specified in <see cref="UserIds"/> and <see cref="RoleIds"/> will be mentioned. | |||||
| /// </remarks> | |||||
| public AllowedMentionTypes? AllowedTypes { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the list of all role ids that will be mentioned. | |||||
| /// This property is mutually exclusive with the <see cref="AllowedMentionTypes.Roles"/> | |||||
| /// flag of the <see cref="AllowedTypes"/> property. If the flag is set, the value of this property | |||||
| /// must be <c>null</c> or empty. | |||||
| /// </summary> | |||||
| public List<ulong> RoleIds { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the list of all user ids that will be mentioned. | |||||
| /// This property is mutually exclusive with the <see cref="AllowedMentionTypes.Users"/> | |||||
| /// flag of the <see cref="AllowedTypes"/> property. If the flag is set, the value of this property | |||||
| /// must be <c>null</c> or empty. | |||||
| /// </summary> | |||||
| public List<ulong> UserIds { get; set; } | |||||
| /// <summary> | |||||
| /// Initializes a new instance of the <see cref="AllowedMentions"/> class. | |||||
| /// </summary> | |||||
| /// <param name="allowedTypes"> | |||||
| /// The types of mentions to parse from the message content. | |||||
| /// If <c>null</c>, only the ids specified in <see cref="UserIds"/> and <see cref="RoleIds"/> will be mentioned. | |||||
| /// </param> | |||||
| public AllowedMentions(AllowedMentionTypes? allowedTypes = null) | |||||
| { | |||||
| AllowedTypes = allowedTypes; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -140,6 +140,18 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| MessageApplication Application { get; } | MessageApplication Application { get; } | ||||
| /// <summary> | |||||
| /// Gets the reference to the original message if it was crossposted. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Sent with Cross-posted messages, meaning they were published from news channels | |||||
| /// and received by subscriber channels. | |||||
| /// </remarks> | |||||
| /// <returns> | |||||
| /// A message's reference, if any is associated. | |||||
| /// </returns> | |||||
| MessageReference Reference { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets all reactions included in this message. | /// Gets all reactions included in this message. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -149,7 +161,7 @@ namespace Discord | |||||
| /// Adds a reaction to this message. | /// Adds a reaction to this message. | ||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// The following example adds the reaction, <c>💕</c>, to the message. | |||||
| /// <para>The following example adds the reaction, <c>💕</c>, to the message.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// await msg.AddReactionAsync(new Emoji("\U0001f495")); | /// await msg.AddReactionAsync(new Emoji("\U0001f495")); | ||||
| /// </code> | /// </code> | ||||
| @@ -165,7 +177,7 @@ namespace Discord | |||||
| /// Removes a reaction from message. | /// Removes a reaction from message. | ||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// The following example removes the reaction, <c>💕</c>, added by the message author from the message. | |||||
| /// <para>The following example removes the reaction, <c>💕</c>, added by the message author from the message.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// await msg.RemoveReactionAsync(new Emoji("\U0001f495"), msg.Author); | /// await msg.RemoveReactionAsync(new Emoji("\U0001f495"), msg.Author); | ||||
| /// </code> | /// </code> | ||||
| @@ -182,7 +194,7 @@ namespace Discord | |||||
| /// Removes a reaction from message. | /// Removes a reaction from message. | ||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// The following example removes the reaction, <c>💕</c>, added by the user with ID 84291986575613952 from the message. | |||||
| /// <para>The following example removes the reaction, <c>💕</c>, added by the user with ID 84291986575613952 from the message.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// await msg.RemoveReactionAsync(new Emoji("\U0001f495"), 84291986575613952); | /// await msg.RemoveReactionAsync(new Emoji("\U0001f495"), 84291986575613952); | ||||
| /// </code> | /// </code> | ||||
| @@ -207,8 +219,25 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets all users that reacted to a message with a given emote. | /// Gets all users that reacted to a message with a given emote. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// The returned collection is an asynchronous enumerable object; one must call | |||||
| /// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the users as a | |||||
| /// collection. | |||||
| /// </note> | |||||
| /// <note type="warning"> | |||||
| /// Do not fetch too many users at once! This may cause unwanted preemptive rate limit or even actual | |||||
| /// rate limit, causing your bot to freeze! | |||||
| /// </note> | |||||
| /// This method will attempt to fetch the number of reactions specified under <paramref name="limit"/>. | |||||
| /// The library will attempt to split up the requests according to your <paramref name="limit"/> and | |||||
| /// <see cref="DiscordConfig.MaxUserReactionsPerBatch"/>. In other words, should the user request 500 reactions, | |||||
| /// and the <see cref="Discord.DiscordConfig.MaxUserReactionsPerBatch"/> constant is <c>100</c>, the request will | |||||
| /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need | |||||
| /// of flattening. | |||||
| /// </remarks> | |||||
| /// <example> | /// <example> | ||||
| /// The following example gets the users that have reacted with the emoji <c>💕</c> to the message. | |||||
| /// <para>The following example gets the users that have reacted with the emoji <c>💕</c> to the message.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// var emoji = new Emoji("\U0001f495"); | /// var emoji = new Emoji("\U0001f495"); | ||||
| /// var reactedUsers = await message.GetReactionUsersAsync(emoji, 100).FlattenAsync(); | /// var reactedUsers = await message.GetReactionUsersAsync(emoji, 100).FlattenAsync(); | ||||
| @@ -218,9 +247,7 @@ namespace Discord | |||||
| /// <param name="limit">The number of users to request.</param> | /// <param name="limit">The number of users to request.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <returns> | /// <returns> | ||||
| /// A paged collection containing a read-only collection of users that has reacted to this message. | |||||
| /// Flattening the paginated response into a collection of users with | |||||
| /// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> is required if you wish to access the users. | |||||
| /// Paged collection of users. | |||||
| /// </returns> | /// </returns> | ||||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emoji, int limit, RequestOptions options = null); | IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emoji, int limit, RequestOptions options = null); | ||||
| } | } | ||||
| @@ -17,7 +17,7 @@ namespace Discord | |||||
| /// method and what properties are available, please refer to <see cref="MessageProperties"/>. | /// method and what properties are available, please refer to <see cref="MessageProperties"/>. | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <example> | /// <example> | ||||
| /// The following example replaces the content of the message with <c>Hello World!</c>. | |||||
| /// <para>The following example replaces the content of the message with <c>Hello World!</c>.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// await msg.ModifyAsync(x => x.Content = "Hello World!"); | /// await msg.ModifyAsync(x => x.Content = "Hello World!"); | ||||
| /// </code> | /// </code> | ||||
| @@ -0,0 +1,33 @@ | |||||
| using System.Diagnostics; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Contains the IDs sent from a crossposted message. | |||||
| /// </summary> | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
| public class MessageReference | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the Message ID of the original message. | |||||
| /// </summary> | |||||
| public Optional<ulong> MessageId { get; internal set; } | |||||
| /// <summary> | |||||
| /// Gets the Channel ID of the original message. | |||||
| /// </summary> | |||||
| public ulong ChannelId { get; internal set; } | |||||
| /// <summary> | |||||
| /// Gets the Guild ID of the original message. | |||||
| /// </summary> | |||||
| public Optional<ulong> GuildId { get; internal set; } | |||||
| private string DebuggerDisplay | |||||
| => $"Channel ID: ({ChannelId}){(GuildId.IsSpecified ? $", Guild ID: ({GuildId.Value})" : "")}" + | |||||
| $"{(MessageId.IsSpecified ? $", Message ID: ({MessageId.Value})" : "")}"; | |||||
| public override string ToString() | |||||
| => DebuggerDisplay; | |||||
| } | |||||
| } | |||||
| @@ -16,60 +16,61 @@ namespace Discord | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/1ABC9C">1ABC9C</see>.</returns> | /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/1ABC9C">1ABC9C</see>.</returns> | ||||
| public static readonly Color Teal = new Color(0x1ABC9C); | public static readonly Color Teal = new Color(0x1ABC9C); | ||||
| /// <summary> Gets the dark teal color value. </summary> | /// <summary> Gets the dark teal color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/11806A">11806A</see>.</returns> | |||||
| public static readonly Color DarkTeal = new Color(0x11806A); | public static readonly Color DarkTeal = new Color(0x11806A); | ||||
| /// <summary> Gets the green color value. </summary> | /// <summary> Gets the green color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/11806A">11806A</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/2ECC71">2ECC71</see>.</returns> | |||||
| public static readonly Color Green = new Color(0x2ECC71); | public static readonly Color Green = new Color(0x2ECC71); | ||||
| /// <summary> Gets the dark green color value. </summary> | /// <summary> Gets the dark green color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/2ECC71">2ECC71</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/1F8B4C">1F8B4C</see>.</returns> | |||||
| public static readonly Color DarkGreen = new Color(0x1F8B4C); | public static readonly Color DarkGreen = new Color(0x1F8B4C); | ||||
| /// <summary> Gets the blue color value. </summary> | /// <summary> Gets the blue color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/1F8B4C">1F8B4C</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/3498DB">3498DB</see>.</returns> | |||||
| public static readonly Color Blue = new Color(0x3498DB); | public static readonly Color Blue = new Color(0x3498DB); | ||||
| /// <summary> Gets the dark blue color value. </summary> | /// <summary> Gets the dark blue color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/3498DB">3498DB</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/206694">206694</see>.</returns> | |||||
| public static readonly Color DarkBlue = new Color(0x206694); | public static readonly Color DarkBlue = new Color(0x206694); | ||||
| /// <summary> Gets the purple color value. </summary> | /// <summary> Gets the purple color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/206694">206694</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/9B59B6">9B59B6</see>.</returns> | |||||
| public static readonly Color Purple = new Color(0x9B59B6); | public static readonly Color Purple = new Color(0x9B59B6); | ||||
| /// <summary> Gets the dark purple color value. </summary> | /// <summary> Gets the dark purple color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/9B59B6">9B59B6</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/71368A">71368A</see>.</returns> | |||||
| public static readonly Color DarkPurple = new Color(0x71368A); | public static readonly Color DarkPurple = new Color(0x71368A); | ||||
| /// <summary> Gets the magenta color value. </summary> | /// <summary> Gets the magenta color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/71368A">71368A</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E91E63">E91E63</see>.</returns> | |||||
| public static readonly Color Magenta = new Color(0xE91E63); | public static readonly Color Magenta = new Color(0xE91E63); | ||||
| /// <summary> Gets the dark magenta color value. </summary> | /// <summary> Gets the dark magenta color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E91E63">E91E63</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/AD1457">AD1457</see>.</returns> | |||||
| public static readonly Color DarkMagenta = new Color(0xAD1457); | public static readonly Color DarkMagenta = new Color(0xAD1457); | ||||
| /// <summary> Gets the gold color value. </summary> | /// <summary> Gets the gold color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/AD1457">AD1457</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/F1C40F">F1C40F</see>.</returns> | |||||
| public static readonly Color Gold = new Color(0xF1C40F); | public static readonly Color Gold = new Color(0xF1C40F); | ||||
| /// <summary> Gets the light orange color value. </summary> | /// <summary> Gets the light orange color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/F1C40F">F1C40F</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/C27C0E">C27C0E</see>.</returns> | |||||
| public static readonly Color LightOrange = new Color(0xC27C0E); | public static readonly Color LightOrange = new Color(0xC27C0E); | ||||
| /// <summary> Gets the orange color value. </summary> | /// <summary> Gets the orange color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/C27C0E">C27C0E</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E67E22">E67E22</see>.</returns> | |||||
| public static readonly Color Orange = new Color(0xE67E22); | public static readonly Color Orange = new Color(0xE67E22); | ||||
| /// <summary> Gets the dark orange color value. </summary> | /// <summary> Gets the dark orange color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E67E22">E67E22</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/A84300">A84300</see>.</returns> | |||||
| public static readonly Color DarkOrange = new Color(0xA84300); | public static readonly Color DarkOrange = new Color(0xA84300); | ||||
| /// <summary> Gets the red color value. </summary> | /// <summary> Gets the red color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/A84300">A84300</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E74C3C">E74C3C</see>.</returns> | |||||
| public static readonly Color Red = new Color(0xE74C3C); | public static readonly Color Red = new Color(0xE74C3C); | ||||
| /// <summary> Gets the dark red color value. </summary> | /// <summary> Gets the dark red color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E74C3C">E74C3C</see>.</returns> | |||||
| public static readonly Color DarkRed = new Color(0x992D22); | |||||
| /// <summary> Gets the light grey color value. </summary> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/992D22">992D22</see>.</returns> | /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/992D22">992D22</see>.</returns> | ||||
| public static readonly Color DarkRed = new Color(0x992D22); | |||||
| /// <summary> Gets the light grey color value. </summary> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/979C9F">979C9F</see>.</returns> | |||||
| public static readonly Color LightGrey = new Color(0x979C9F); | public static readonly Color LightGrey = new Color(0x979C9F); | ||||
| /// <summary> Gets the lighter grey color value. </summary> | /// <summary> Gets the lighter grey color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/979C9F">979C9F</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/95A5A6">95A5A6</see>.</returns> | |||||
| public static readonly Color LighterGrey = new Color(0x95A5A6); | public static readonly Color LighterGrey = new Color(0x95A5A6); | ||||
| /// <summary> Gets the dark grey color value. </summary> | /// <summary> Gets the dark grey color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/95A5A6">95A5A6</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/607D8B">607D8B</see>.</returns> | |||||
| public static readonly Color DarkGrey = new Color(0x607D8B); | public static readonly Color DarkGrey = new Color(0x607D8B); | ||||
| /// <summary> Gets the darker grey color value. </summary> | /// <summary> Gets the darker grey color value. </summary> | ||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/607D8B">607D8B</see>.</returns> | |||||
| /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/546E7A">546E7A</see>.</returns> | |||||
| public static readonly Color DarkerGrey = new Color(0x546E7A); | public static readonly Color DarkerGrey = new Color(0x546E7A); | ||||
| /// <summary> Gets the encoded value for this color. </summary> | /// <summary> Gets the encoded value for this color. </summary> | ||||
| @@ -72,8 +72,8 @@ namespace Discord | |||||
| /// Gets the level permissions granted to this user to a given channel. | /// Gets the level permissions granted to this user to a given channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// The following example checks if the current user has the ability to send a message with attachment in | |||||
| /// this channel; if so, uploads a file via <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool)"/>. | |||||
| /// <para>The following example checks if the current user has the ability to send a message with attachment in | |||||
| /// this channel; if so, uploads a file via <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool)"/>.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles) | /// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles) | ||||
| /// await targetChannel.SendFileAsync("fortnite.png"); | /// await targetChannel.SendFileAsync("fortnite.png"); | ||||
| @@ -21,8 +21,8 @@ namespace Discord | |||||
| /// example). | /// example). | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <example> | /// <example> | ||||
| /// The following example attempts to retrieve the user's current avatar and send it to a channel; if one is | |||||
| /// not set, a default avatar for this user will be returned instead. | |||||
| /// <para>The following example attempts to retrieve the user's current avatar and send it to a channel; if one is | |||||
| /// not set, a default avatar for this user will be returned instead.</para> | |||||
| /// <code language="cs" region="GetAvatarUrl" | /// <code language="cs" region="GetAvatarUrl" | ||||
| /// source="..\..\..\Discord.Net.Examples\Core\Entities\Users\IUser.Examples.cs"/> | /// source="..\..\..\Discord.Net.Examples\Core\Entities\Users\IUser.Examples.cs"/> | ||||
| /// </example> | /// </example> | ||||
| @@ -90,8 +90,8 @@ namespace Discord | |||||
| /// </note> | /// </note> | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <example> | /// <example> | ||||
| /// The following example attempts to send a direct message to the target user and logs the incident should | |||||
| /// it fail. | |||||
| /// <para>The following example attempts to send a direct message to the target user and logs the incident should | |||||
| /// it fail.</para> | |||||
| /// <code region="GetOrCreateDMChannelAsync" language="cs" | /// <code region="GetOrCreateDMChannelAsync" language="cs" | ||||
| /// source="../../../Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs"/> | /// source="../../../Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs"/> | ||||
| /// </example> | /// </example> | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| using System.Collections; | using System.Collections; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| @@ -15,7 +15,7 @@ namespace Discord | |||||
| //public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> source) | //public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> source) | ||||
| // => new CollectionWrapper<TValue>(source.Select(x => x.Value), () => source.Count); | // => new CollectionWrapper<TValue>(source.Select(x => x.Value), () => source.Count); | ||||
| public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IDictionary<TKey, TValue> source) | public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IDictionary<TKey, TValue> source) | ||||
| => new CollectionWrapper<TValue>(source.Select(x => x.Value), () => source.Count); | |||||
| => new CollectionWrapper<TValue>(source.Values, () => source.Count); | |||||
| public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue, TSource>(this IEnumerable<TValue> query, IReadOnlyCollection<TSource> source) | public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue, TSource>(this IEnumerable<TValue> query, IReadOnlyCollection<TSource> source) | ||||
| => new CollectionWrapper<TValue>(query, () => source.Count); | => new CollectionWrapper<TValue>(query, () => source.Count); | ||||
| public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue>(this IEnumerable<TValue> query, Func<int> countFunc) | public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue>(this IEnumerable<TValue> query, Func<int> countFunc) | ||||
| @@ -39,7 +39,7 @@ namespace Discord | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous operation for adding a reaction to this message. | /// A task that represents the asynchronous operation for adding a reaction to this message. | ||||
| /// </returns> | /// </returns> | ||||
| /// <seealso cref="IUserMessage.AddReactionAsync(IEmote, RequestOptions)"/> | |||||
| /// <seealso cref="IMessage.AddReactionAsync(IEmote, RequestOptions)"/> | |||||
| /// <seealso cref="IEmote"/> | /// <seealso cref="IEmote"/> | ||||
| public static async Task AddReactionsAsync(this IUserMessage msg, IEmote[] reactions, RequestOptions options = null) | public static async Task AddReactionsAsync(this IUserMessage msg, IEmote[] reactions, RequestOptions options = null) | ||||
| { | { | ||||
| @@ -51,7 +51,7 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// This method does not bulk remove reactions! If you want to clear reactions from a message, | /// This method does not bulk remove reactions! If you want to clear reactions from a message, | ||||
| /// <see cref="IUserMessage.RemoveAllReactionsAsync(RequestOptions)"/> | |||||
| /// <see cref="IMessage.RemoveAllReactionsAsync(RequestOptions)"/> | |||||
| /// </remarks> | /// </remarks> | ||||
| /// <example> | /// <example> | ||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| @@ -64,7 +64,7 @@ namespace Discord | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous operation for removing a reaction to this message. | /// A task that represents the asynchronous operation for removing a reaction to this message. | ||||
| /// </returns> | /// </returns> | ||||
| /// <seealso cref="IUserMessage.RemoveReactionAsync(IEmote, IUser, RequestOptions)"/> | |||||
| /// <seealso cref="IMessage.RemoveReactionAsync(IEmote, IUser, RequestOptions)"/> | |||||
| /// <seealso cref="IEmote"/> | /// <seealso cref="IEmote"/> | ||||
| public static async Task RemoveReactionsAsync(this IUserMessage msg, IUser user, IEmote[] reactions, RequestOptions options = null) | public static async Task RemoveReactionsAsync(this IUserMessage msg, IUser user, IEmote[] reactions, RequestOptions options = null) | ||||
| { | { | ||||
| @@ -28,6 +28,10 @@ namespace Discord | |||||
| /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | ||||
| /// <param name="embed">The <see cref="EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | /// <param name="embed">The <see cref="EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous send operation. The task result contains the sent message. | /// A task that represents the asynchronous send operation. The task result contains the sent message. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -35,17 +39,18 @@ namespace Discord | |||||
| string text = null, | string text = null, | ||||
| bool isTTS = false, | bool isTTS = false, | ||||
| Embed embed = null, | Embed embed = null, | ||||
| RequestOptions options = null) | |||||
| RequestOptions options = null, | |||||
| AllowedMentions allowedMentions = null) | |||||
| { | { | ||||
| return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||||
| return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// The following example uploads a streamed image that will be called <c>b1nzy.jpg</c> embedded inside a | |||||
| /// rich embed to the channel. | |||||
| /// <para>The following example uploads a streamed image that will be called <c>b1nzy.jpg</c> embedded inside a | |||||
| /// rich embed to the channel.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// await channel.SendFileAsync(b1nzyStream, "b1nzy.jpg", | /// await channel.SendFileAsync(b1nzyStream, "b1nzy.jpg", | ||||
| /// embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); | /// embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); | ||||
| @@ -270,7 +270,7 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous get operation. The task result contains an <see cref="Int32"/> | |||||
| /// A task that represents the asynchronous get operation. The task result contains an <see cref="int"/> | |||||
| /// that represents the number of shards that should be used with this account. | /// that represents the number of shards that should be used with this account. | ||||
| /// </returns> | /// </returns> | ||||
| Task<int> GetRecommendedShardCountAsync(RequestOptions options = null); | Task<int> GetRecommendedShardCountAsync(RequestOptions options = null); | ||||
| @@ -14,7 +14,7 @@ namespace Discord.Net.WebSockets | |||||
| void SetCancelToken(CancellationToken cancelToken); | void SetCancelToken(CancellationToken cancelToken); | ||||
| Task ConnectAsync(string host); | Task ConnectAsync(string host); | ||||
| Task DisconnectAsync(); | |||||
| Task DisconnectAsync(int closeCode = 1000); | |||||
| Task SendAsync(byte[] data, int index, int count, bool isText); | Task SendAsync(byte[] data, int index, int count, bool isText); | ||||
| } | } | ||||
| @@ -49,8 +49,7 @@ namespace Discord | |||||
| /// clock for rate-limiting. Defaults to <c>true</c>. | /// clock for rate-limiting. Defaults to <c>true</c>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// This property can also be set in <see cref="DiscordConfig">. | |||||
| /// | |||||
| /// This property can also be set in <see cref="DiscordConfig"/>. | |||||
| /// On a per-request basis, the system clock should only be disabled | /// On a per-request basis, the system clock should only be disabled | ||||
| /// when millisecond precision is especially important, and the | /// when millisecond precision is especially important, and the | ||||
| /// hosting system is known to have a desynced clock. | /// hosting system is known to have a desynced clock. | ||||
| @@ -40,7 +40,7 @@ namespace Discord.Net.Providers.WS4Net | |||||
| { | { | ||||
| if (disposing) | if (disposing) | ||||
| { | { | ||||
| DisconnectInternalAsync(true).GetAwaiter().GetResult(); | |||||
| DisconnectInternalAsync(isDisposing: true).GetAwaiter().GetResult(); | |||||
| _lock?.Dispose(); | _lock?.Dispose(); | ||||
| _cancelTokenSource?.Dispose(); | _cancelTokenSource?.Dispose(); | ||||
| } | } | ||||
| @@ -92,19 +92,19 @@ namespace Discord.Net.Providers.WS4Net | |||||
| _waitUntilConnect.Wait(_cancelToken); | _waitUntilConnect.Wait(_cancelToken); | ||||
| } | } | ||||
| public async Task DisconnectAsync() | |||||
| public async Task DisconnectAsync(int closeCode = 1000) | |||||
| { | { | ||||
| await _lock.WaitAsync().ConfigureAwait(false); | await _lock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||||
| await DisconnectInternalAsync(closeCode: closeCode).ConfigureAwait(false); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _lock.Release(); | _lock.Release(); | ||||
| } | } | ||||
| } | } | ||||
| private Task DisconnectInternalAsync(bool isDisposing = false) | |||||
| private Task DisconnectInternalAsync(int closeCode = 1000, bool isDisposing = false) | |||||
| { | { | ||||
| _disconnectCancelTokenSource.Cancel(); | _disconnectCancelTokenSource.Cancel(); | ||||
| if (_client == null) | if (_client == null) | ||||
| @@ -112,7 +112,7 @@ namespace Discord.Net.Providers.WS4Net | |||||
| if (_client.State == WebSocketState.Open) | if (_client.State == WebSocketState.Open) | ||||
| { | { | ||||
| try { _client.Close(1000, ""); } | |||||
| try { _client.Close(closeCode, ""); } | |||||
| catch { } | catch { } | ||||
| } | } | ||||
| @@ -4,11 +4,12 @@ namespace Discord.API | |||||
| { | { | ||||
| internal class AuditLogOptions | internal class AuditLogOptions | ||||
| { | { | ||||
| //Message delete | |||||
| [JsonProperty("count")] | [JsonProperty("count")] | ||||
| public int? MessageDeleteCount { get; set; } | |||||
| public int? Count { get; set; } | |||||
| [JsonProperty("channel_id")] | [JsonProperty("channel_id")] | ||||
| public ulong? MessageDeleteChannelId { get; set; } | |||||
| public ulong? ChannelId { get; set; } | |||||
| [JsonProperty("message_id")] | |||||
| public ulong? MessageId { get; set; } | |||||
| //Prune | //Prune | ||||
| [JsonProperty("delete_member_days")] | [JsonProperty("delete_member_days")] | ||||
| @@ -35,6 +35,12 @@ namespace Discord.API | |||||
| public Optional<string> SessionId { get; set; } | public Optional<string> SessionId { get; set; } | ||||
| [JsonProperty("Flags")] | [JsonProperty("Flags")] | ||||
| public Optional<ActivityProperties> Flags { get; set; } | public Optional<ActivityProperties> Flags { get; set; } | ||||
| [JsonProperty("id")] | |||||
| public Optional<string> Id { get; set; } | |||||
| [JsonProperty("emoji")] | |||||
| public Optional<Emoji> Emoji { get; set; } | |||||
| [JsonProperty("created_at")] | |||||
| public Optional<long> CreatedAt { get; set; } | |||||
| [OnError] | [OnError] | ||||
| internal void OnError(StreamingContext context, ErrorContext errorContext) | internal void OnError(StreamingContext context, ErrorContext errorContext) | ||||
| @@ -50,7 +50,11 @@ namespace Discord.API | |||||
| // sent with Rich Presence-related chat embeds | // sent with Rich Presence-related chat embeds | ||||
| [JsonProperty("application")] | [JsonProperty("application")] | ||||
| public Optional<MessageApplication> Application { get; set; } | public Optional<MessageApplication> Application { get; set; } | ||||
| [JsonProperty("message_reference")] | |||||
| public Optional<MessageReference> Reference { get; set; } | |||||
| [JsonProperty("flags")] | [JsonProperty("flags")] | ||||
| public Optional<MessageFlags> Flags { get; set; } | public Optional<MessageFlags> Flags { get; set; } | ||||
| [JsonProperty("allowed_mentions")] | |||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,16 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class MessageReference | |||||
| { | |||||
| [JsonProperty("message_id")] | |||||
| public Optional<ulong> MessageId { get; set; } | |||||
| [JsonProperty("channel_id")] | |||||
| public ulong ChannelId { get; set; } | |||||
| [JsonProperty("guild_id")] | |||||
| public Optional<ulong> GuildId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| #pragma warning disable CS1591 | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
| @@ -15,6 +15,8 @@ namespace Discord.API.Rest | |||||
| public Optional<bool> IsTTS { get; set; } | public Optional<bool> IsTTS { get; set; } | ||||
| [JsonProperty("embed")] | [JsonProperty("embed")] | ||||
| public Optional<Embed> Embed { get; set; } | public Optional<Embed> Embed { get; set; } | ||||
| [JsonProperty("allowed_mentions")] | |||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||||
| public CreateMessageParams(string content) | public CreateMessageParams(string content) | ||||
| { | { | ||||
| @@ -22,6 +22,8 @@ namespace Discord.API.Rest | |||||
| public Optional<ulong?> SystemChannelId { get; set; } | public Optional<ulong?> SystemChannelId { get; set; } | ||||
| [JsonProperty("icon")] | [JsonProperty("icon")] | ||||
| public Optional<Image?> Icon { get; set; } | public Optional<Image?> Icon { get; set; } | ||||
| [JsonProperty("banner")] | |||||
| public Optional<Image?> Banner { get; set; } | |||||
| [JsonProperty("splash")] | [JsonProperty("splash")] | ||||
| public Optional<Image?> Splash { get; set; } | public Optional<Image?> Splash { get; set; } | ||||
| [JsonProperty("afk_channel_id")] | [JsonProperty("afk_channel_id")] | ||||
| @@ -47,7 +47,7 @@ namespace Discord.API | |||||
| internal ulong? CurrentUserId { get; set; } | internal ulong? CurrentUserId { get; set; } | ||||
| public RateLimitPrecision RateLimitPrecision { get; private set; } | public RateLimitPrecision RateLimitPrecision { get; private set; } | ||||
| internal bool UseSystemClock { get; set; } | internal bool UseSystemClock { get; set; } | ||||
| internal JsonSerializer Serializer => _serializer; | internal JsonSerializer Serializer => _serializer; | ||||
| /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | ||||
| @@ -164,7 +164,7 @@ namespace Discord.API | |||||
| try { _loginCancelToken?.Cancel(false); } | try { _loginCancelToken?.Cancel(false); } | ||||
| catch { } | catch { } | ||||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||||
| await DisconnectInternalAsync(null).ConfigureAwait(false); | |||||
| await RequestQueue.ClearAsync().ConfigureAwait(false); | await RequestQueue.ClearAsync().ConfigureAwait(false); | ||||
| await RequestQueue.SetCancelTokenAsync(CancellationToken.None).ConfigureAwait(false); | await RequestQueue.SetCancelTokenAsync(CancellationToken.None).ConfigureAwait(false); | ||||
| @@ -175,7 +175,7 @@ namespace Discord.API | |||||
| } | } | ||||
| internal virtual Task ConnectInternalAsync() => Task.Delay(0); | internal virtual Task ConnectInternalAsync() => Task.Delay(0); | ||||
| internal virtual Task DisconnectInternalAsync() => Task.Delay(0); | |||||
| internal virtual Task DisconnectInternalAsync(Exception ex = null) => Task.Delay(0); | |||||
| //Core | //Core | ||||
| internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids, | internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids, | ||||
| @@ -602,13 +602,8 @@ namespace Discord.API | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | Preconditions.NotEqual(channelId, 0, nameof(channelId)); | ||||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | Preconditions.NotEqual(messageId, 0, nameof(messageId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
| if (args.Content.IsSpecified) | |||||
| { | |||||
| if (!args.Embed.IsSpecified) | |||||
| Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||||
| if (args.Content.Value?.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||||
| } | |||||
| if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| var ids = new BucketIds(channelId: channelId); | var ids = new BucketIds(channelId: channelId); | ||||
| @@ -1062,7 +1057,7 @@ namespace Discord.API | |||||
| { | { | ||||
| foreach (var roleId in args.RoleIds.Value) | foreach (var roleId in args.RoleIds.Value) | ||||
| Preconditions.NotEqual(roleId, 0, nameof(roleId)); | Preconditions.NotEqual(roleId, 0, nameof(roleId)); | ||||
| } | |||||
| } | |||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| @@ -27,6 +27,9 @@ namespace Discord.Rest | |||||
| [ActionType.Unban] = UnbanAuditLogData.Create, | [ActionType.Unban] = UnbanAuditLogData.Create, | ||||
| [ActionType.MemberUpdated] = MemberUpdateAuditLogData.Create, | [ActionType.MemberUpdated] = MemberUpdateAuditLogData.Create, | ||||
| [ActionType.MemberRoleUpdated] = MemberRoleAuditLogData.Create, | [ActionType.MemberRoleUpdated] = MemberRoleAuditLogData.Create, | ||||
| [ActionType.MemberMoved] = MemberMoveAuditLogData.Create, | |||||
| [ActionType.MemberDisconnected] = MemberDisconnectAuditLogData.Create, | |||||
| [ActionType.BotAdded] = BotAddAuditLogData.Create, | |||||
| [ActionType.RoleCreated] = RoleCreateAuditLogData.Create, | [ActionType.RoleCreated] = RoleCreateAuditLogData.Create, | ||||
| [ActionType.RoleUpdated] = RoleUpdateAuditLogData.Create, | [ActionType.RoleUpdated] = RoleUpdateAuditLogData.Create, | ||||
| @@ -45,6 +48,9 @@ namespace Discord.Rest | |||||
| [ActionType.EmojiDeleted] = EmoteDeleteAuditLogData.Create, | [ActionType.EmojiDeleted] = EmoteDeleteAuditLogData.Create, | ||||
| [ActionType.MessageDeleted] = MessageDeleteAuditLogData.Create, | [ActionType.MessageDeleted] = MessageDeleteAuditLogData.Create, | ||||
| [ActionType.MessageBulkDeleted] = MessageBulkDeleteAuditLogData.Create, | |||||
| [ActionType.MessagePinned] = MessagePinAuditLogData.Create, | |||||
| [ActionType.MessageUnpinned] = MessageUnpinAuditLogData.Create, | |||||
| }; | }; | ||||
| public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) | public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) | ||||
| @@ -0,0 +1,32 @@ | |||||
| using System.Linq; | |||||
| using Model = Discord.API.AuditLog; | |||||
| using EntryModel = Discord.API.AuditLogEntry; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| /// <summary> | |||||
| /// Contains a piece of audit log data related to a adding a bot to a guild. | |||||
| /// </summary> | |||||
| public class BotAddAuditLogData : IAuditLogData | |||||
| { | |||||
| private BotAddAuditLogData(IUser bot) | |||||
| { | |||||
| Target = bot; | |||||
| } | |||||
| internal static BotAddAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | |||||
| { | |||||
| var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||||
| return new BotAddAuditLogData(RestUser.Create(discord, userInfo)); | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets the bot that was added. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A user object representing the bot. | |||||
| /// </returns> | |||||
| public IUser Target { get; } | |||||
| } | |||||
| } | |||||
| @@ -25,7 +25,6 @@ namespace Discord.Rest | |||||
| internal static ChannelCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | internal static ChannelCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | ||||
| { | { | ||||
| var changes = entry.Changes; | var changes = entry.Changes; | ||||
| var overwrites = new List<Overwrite>(); | |||||
| var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); | var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); | ||||
| var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); | var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); | ||||
| @@ -34,23 +33,17 @@ namespace Discord.Rest | |||||
| var nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw"); | var nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw"); | ||||
| var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); | var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); | ||||
| var overwrites = overwritesModel.NewValue.ToObject<API.Overwrite[]>(discord.ApiClient.Serializer) | |||||
| .Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny))) | |||||
| .ToList(); | |||||
| var type = typeModel.NewValue.ToObject<ChannelType>(discord.ApiClient.Serializer); | var type = typeModel.NewValue.ToObject<ChannelType>(discord.ApiClient.Serializer); | ||||
| var name = nameModel.NewValue.ToObject<string>(discord.ApiClient.Serializer); | var name = nameModel.NewValue.ToObject<string>(discord.ApiClient.Serializer); | ||||
| int? rateLimitPerUser = rateLimitPerUserModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | int? rateLimitPerUser = rateLimitPerUserModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | ||||
| bool? nsfw = nsfwModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | bool? nsfw = nsfwModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | ||||
| int? bitrate = bitrateModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | int? bitrate = bitrateModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | ||||
| var id = entry.TargetId.Value; | |||||
| foreach (var overwrite in overwritesModel.NewValue) | |||||
| { | |||||
| var deny = overwrite["deny"].ToObject<ulong>(discord.ApiClient.Serializer); | |||||
| var permType = overwrite["type"].ToObject<PermissionTarget>(discord.ApiClient.Serializer); | |||||
| var id = overwrite["id"].ToObject<ulong>(discord.ApiClient.Serializer); | |||||
| var allow = overwrite["allow"].ToObject<ulong>(discord.ApiClient.Serializer); | |||||
| overwrites.Add(new Overwrite(id, permType, new OverwritePermissions(allow, deny))); | |||||
| } | |||||
| return new ChannelCreateAuditLogData(entry.TargetId.Value, name, type, rateLimitPerUser, nsfw, bitrate, overwrites.ToReadOnlyCollection()); | |||||
| return new ChannelCreateAuditLogData(id, name, type, rateLimitPerUser, nsfw, bitrate, overwrites.ToReadOnlyCollection()); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -78,7 +71,7 @@ namespace Discord.Rest | |||||
| /// Gets the current slow-mode delay of the created channel. | /// Gets the current slow-mode delay of the created channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// An <see cref="Int32"/> representing the time in seconds required before the user can send another | |||||
| /// An <see cref="int"/> representing the time in seconds required before the user can send another | |||||
| /// message; <c>0</c> if disabled. | /// message; <c>0</c> if disabled. | ||||
| /// <c>null</c> if this is not mentioned in this entry. | /// <c>null</c> if this is not mentioned in this entry. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -95,7 +88,7 @@ namespace Discord.Rest | |||||
| /// Gets the bit-rate that the clients in the created voice channel are requested to use. | /// Gets the bit-rate that the clients in the created voice channel are requested to use. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// An <see cref="Int32"/> representing the bit-rate (bps) that the created voice channel defines and requests the | |||||
| /// An <see cref="int"/> representing the bit-rate (bps) that the created voice channel defines and requests the | |||||
| /// client(s) to use. | /// client(s) to use. | ||||
| /// <c>null</c> if this is not mentioned in this entry. | /// <c>null</c> if this is not mentioned in this entry. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -71,7 +71,7 @@ namespace Discord.Rest | |||||
| /// Gets the slow-mode delay of the deleted channel. | /// Gets the slow-mode delay of the deleted channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// An <see cref="Int32"/> representing the time in seconds required before the user can send another | |||||
| /// An <see cref="int"/> representing the time in seconds required before the user can send another | |||||
| /// message; <c>0</c> if disabled. | /// message; <c>0</c> if disabled. | ||||
| /// <c>null</c> if this is not mentioned in this entry. | /// <c>null</c> if this is not mentioned in this entry. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -88,7 +88,7 @@ namespace Discord.Rest | |||||
| /// Gets the bit-rate of this channel if applicable. | /// Gets the bit-rate of this channel if applicable. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// An <see cref="Int32"/> representing the bit-rate set of the voice channel. | |||||
| /// An <see cref="int"/> representing the bit-rate set of the voice channel. | |||||
| /// <c>null</c> if this is not mentioned in this entry. | /// <c>null</c> if this is not mentioned in this entry. | ||||
| /// </returns> | /// </returns> | ||||
| public int? Bitrate { get; } | public int? Bitrate { get; } | ||||
| @@ -32,7 +32,7 @@ namespace Discord.Rest | |||||
| /// Gets the current slow-mode delay of this channel. | /// Gets the current slow-mode delay of this channel. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// An <see cref="Int32"/> representing the time in seconds required before the user can send another | |||||
| /// An <see cref="int"/> representing the time in seconds required before the user can send another | |||||
| /// message; <c>0</c> if disabled. | /// message; <c>0</c> if disabled. | ||||
| /// <c>null</c> if this is not mentioned in this entry. | /// <c>null</c> if this is not mentioned in this entry. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -49,7 +49,7 @@ namespace Discord.Rest | |||||
| /// Gets the bit-rate of this channel if applicable. | /// Gets the bit-rate of this channel if applicable. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// An <see cref="Int32"/> representing the bit-rate set for the voice channel; | |||||
| /// An <see cref="int"/> representing the bit-rate set for the voice channel; | |||||
| /// <c>null</c> if this is not mentioned in this entry. | /// <c>null</c> if this is not mentioned in this entry. | ||||
| /// </returns> | /// </returns> | ||||
| public int? Bitrate { get; } | public int? Bitrate { get; } | ||||
| @@ -0,0 +1,29 @@ | |||||
| using Model = Discord.API.AuditLog; | |||||
| using EntryModel = Discord.API.AuditLogEntry; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| /// <summary> | |||||
| /// Contains a piece of audit log data related to disconnecting members from voice channels. | |||||
| /// </summary> | |||||
| public class MemberDisconnectAuditLogData : IAuditLogData | |||||
| { | |||||
| private MemberDisconnectAuditLogData(int count) | |||||
| { | |||||
| MemberCount = count; | |||||
| } | |||||
| internal static MemberDisconnectAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | |||||
| { | |||||
| return new MemberDisconnectAuditLogData(entry.Options.Count.Value); | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets the number of members that were disconnected. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// An <see cref="int"/> representing the number of members that were disconnected from a voice channel. | |||||
| /// </returns> | |||||
| public int MemberCount { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| using Model = Discord.API.AuditLog; | |||||
| using EntryModel = Discord.API.AuditLogEntry; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| /// <summary> | |||||
| /// Contains a piece of audit log data related to moving members between voice channels. | |||||
| /// </summary> | |||||
| public class MemberMoveAuditLogData : IAuditLogData | |||||
| { | |||||
| private MemberMoveAuditLogData(ulong channelId, int count) | |||||
| { | |||||
| ChannelId = channelId; | |||||
| MemberCount = count; | |||||
| } | |||||
| internal static MemberMoveAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | |||||
| { | |||||
| return new MemberMoveAuditLogData(entry.Options.ChannelId.Value, entry.Options.Count.Value); | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets the ID of the channel that the members were moved to. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="ulong"/> representing the snowflake identifier for the channel that the members were moved to. | |||||
| /// </returns> | |||||
| public ulong ChannelId { get; } | |||||
| /// <summary> | |||||
| /// Gets the number of members that were moved. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// An <see cref="int"/> representing the number of members that were moved to another voice channel. | |||||
| /// </returns> | |||||
| public int MemberCount { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| using Model = Discord.API.AuditLog; | |||||
| using EntryModel = Discord.API.AuditLogEntry; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| /// <summary> | |||||
| /// Contains a piece of audit log data related to message deletion(s). | |||||
| /// </summary> | |||||
| public class MessageBulkDeleteAuditLogData : IAuditLogData | |||||
| { | |||||
| private MessageBulkDeleteAuditLogData(ulong channelId, int count) | |||||
| { | |||||
| ChannelId = channelId; | |||||
| MessageCount = count; | |||||
| } | |||||
| internal static MessageBulkDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | |||||
| { | |||||
| return new MessageBulkDeleteAuditLogData(entry.TargetId.Value, entry.Options.Count.Value); | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets the ID of the channel that the messages were deleted from. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="ulong"/> representing the snowflake identifier for the channel that the messages were | |||||
| /// deleted from. | |||||
| /// </returns> | |||||
| public ulong ChannelId { get; } | |||||
| /// <summary> | |||||
| /// Gets the number of messages that were deleted. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// An <see cref="int"/> representing the number of messages that were deleted from the channel. | |||||
| /// </returns> | |||||
| public int MessageCount { get; } | |||||
| } | |||||
| } | |||||
| @@ -1,3 +1,5 @@ | |||||
| using System.Linq; | |||||
| using Model = Discord.API.AuditLog; | using Model = Discord.API.AuditLog; | ||||
| using EntryModel = Discord.API.AuditLogEntry; | using EntryModel = Discord.API.AuditLogEntry; | ||||
| @@ -8,16 +10,17 @@ namespace Discord.Rest | |||||
| /// </summary> | /// </summary> | ||||
| public class MessageDeleteAuditLogData : IAuditLogData | public class MessageDeleteAuditLogData : IAuditLogData | ||||
| { | { | ||||
| private MessageDeleteAuditLogData(ulong channelId, int count, ulong authorId) | |||||
| private MessageDeleteAuditLogData(ulong channelId, int count, IUser user) | |||||
| { | { | ||||
| ChannelId = channelId; | ChannelId = channelId; | ||||
| MessageCount = count; | MessageCount = count; | ||||
| AuthorId = authorId; | |||||
| Target = user; | |||||
| } | } | ||||
| internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | ||||
| { | { | ||||
| return new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, entry.Options.MessageDeleteCount.Value, entry.TargetId.Value); | |||||
| var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||||
| return new MessageDeleteAuditLogData(entry.Options.ChannelId.Value, entry.Options.Count.Value, RestUser.Create(discord, userInfo)); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -36,11 +39,11 @@ namespace Discord.Rest | |||||
| /// </returns> | /// </returns> | ||||
| public ulong ChannelId { get; } | public ulong ChannelId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the author of the messages that were deleted. | |||||
| /// Gets the user of the messages that were deleted. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// A <see cref="ulong"/> representing the snowflake identifier for the user that created the deleted messages. | |||||
| /// A user object representing the user that created the deleted messages. | |||||
| /// </returns> | /// </returns> | ||||
| public ulong AuthorId { get; } | |||||
| public IUser Target { get; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,48 @@ | |||||
| using System.Linq; | |||||
| using Model = Discord.API.AuditLog; | |||||
| using EntryModel = Discord.API.AuditLogEntry; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| /// <summary> | |||||
| /// Contains a piece of audit log data related to a pinned message. | |||||
| /// </summary> | |||||
| public class MessagePinAuditLogData : IAuditLogData | |||||
| { | |||||
| private MessagePinAuditLogData(ulong messageId, ulong channelId, IUser user) | |||||
| { | |||||
| MessageId = messageId; | |||||
| ChannelId = channelId; | |||||
| Target = user; | |||||
| } | |||||
| internal static MessagePinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | |||||
| { | |||||
| var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||||
| return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo)); | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets the ID of the messages that was pinned. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="ulong"/> representing the snowflake identifier for the messages that was pinned. | |||||
| /// </returns> | |||||
| public ulong MessageId { get; } | |||||
| /// <summary> | |||||
| /// Gets the ID of the channel that the message was pinned from. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="ulong"/> representing the snowflake identifier for the channel that the message was pinned from. | |||||
| /// </returns> | |||||
| public ulong ChannelId { get; } | |||||
| /// <summary> | |||||
| /// Gets the user of the message that was pinned. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A user object representing the user that created the pinned message. | |||||
| /// </returns> | |||||
| public IUser Target { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,48 @@ | |||||
| using System.Linq; | |||||
| using Model = Discord.API.AuditLog; | |||||
| using EntryModel = Discord.API.AuditLogEntry; | |||||
| namespace Discord.Rest | |||||
| { | |||||
| /// <summary> | |||||
| /// Contains a piece of audit log data related to an unpinned message. | |||||
| /// </summary> | |||||
| public class MessageUnpinAuditLogData : IAuditLogData | |||||
| { | |||||
| private MessageUnpinAuditLogData(ulong messageId, ulong channelId, IUser user) | |||||
| { | |||||
| MessageId = messageId; | |||||
| ChannelId = channelId; | |||||
| Target = user; | |||||
| } | |||||
| internal static MessageUnpinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | |||||
| { | |||||
| var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||||
| return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo)); | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets the ID of the messages that was unpinned. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="ulong"/> representing the snowflake identifier for the messages that was unpinned. | |||||
| /// </returns> | |||||
| public ulong MessageId { get; } | |||||
| /// <summary> | |||||
| /// Gets the ID of the channel that the message was unpinned from. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="ulong"/> representing the snowflake identifier for the channel that the message was unpinned from. | |||||
| /// </returns> | |||||
| public ulong ChannelId { get; } | |||||
| /// <summary> | |||||
| /// Gets the user of the message that was unpinned. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A user object representing the user that created the unpinned message. | |||||
| /// </returns> | |||||
| public IUser Target { get; } | |||||
| } | |||||
| } | |||||
| @@ -21,16 +21,17 @@ namespace Discord.Rest | |||||
| var changes = entry.Changes; | var changes = entry.Changes; | ||||
| var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); | 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 allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); | ||||
| var deny = denyModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | var deny = denyModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | ||||
| var type = typeModel.OldValue.ToObject<PermissionTarget>(discord.ApiClient.Serializer); | |||||
| var id = idModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||||
| var allow = allowModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | var allow = allowModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | ||||
| return new OverwriteDeleteAuditLogData(entry.TargetId.Value, new Overwrite(id, type, new OverwritePermissions(allow, deny))); | |||||
| var permissions = new OverwritePermissions(allow, deny); | |||||
| var id = entry.Options.OverwriteTargetId.Value; | |||||
| var type = entry.Options.OverwriteType; | |||||
| return new OverwriteDeleteAuditLogData(entry.TargetId.Value, new Overwrite(id, type, permissions)); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -167,9 +167,28 @@ namespace Discord.Rest | |||||
| /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
| public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, | public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
| string text, bool isTTS, Embed embed, RequestOptions options) | |||||
| string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, RequestOptions options) | |||||
| { | { | ||||
| var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel() }; | |||||
| Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | |||||
| Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); | |||||
| // check that user flag and user Id list are exclusive, same with role flag and role Id list | |||||
| if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) | |||||
| { | |||||
| if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && | |||||
| allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) | |||||
| { | |||||
| throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); | |||||
| } | |||||
| if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && | |||||
| allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) | |||||
| { | |||||
| throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); | |||||
| } | |||||
| } | |||||
| var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel() }; | |||||
| var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
| return RestUserMessage.Create(client, channel, client.CurrentUser, model); | return RestUserMessage.Create(client, channel, client.CurrentUser, model); | ||||
| } | } | ||||
| @@ -20,11 +20,15 @@ namespace Discord.Rest | |||||
| /// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | /// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | ||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
| /// contains the sent message. | /// contains the sent message. | ||||
| /// </returns> | /// </returns> | ||||
| new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||||
| new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -93,8 +93,8 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentException"> | /// <exception cref="ArgumentException"> | ||||
| @@ -206,8 +206,8 @@ namespace Discord.Rest | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | ||||
| => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | |||||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions) | |||||
| => await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||||
| //IChannel | //IChannel | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -95,8 +95,8 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentException"> | /// <exception cref="ArgumentException"> | ||||
| @@ -183,8 +183,9 @@ namespace Discord.Rest | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | ||||
| => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | ||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | |||||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions) | |||||
| => await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||||
| //IAudioChannel | //IAudioChannel | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -25,29 +25,5 @@ namespace Discord.Rest | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| public override int SlowModeInterval => throw new NotSupportedException("News channels do not support Slow Mode."); | public override int SlowModeInterval => throw new NotSupportedException("News channels do not support Slow Mode."); | ||||
| public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) | |||||
| { | |||||
| throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| } | |||||
| public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) | |||||
| { | |||||
| throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| } | |||||
| public override OverwritePermissions? GetPermissionOverwrite(IRole role) | |||||
| { | |||||
| throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| } | |||||
| public override OverwritePermissions? GetPermissionOverwrite(IUser user) | |||||
| { | |||||
| throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| } | |||||
| public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) | |||||
| { | |||||
| throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| } | |||||
| public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) | |||||
| { | |||||
| throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -101,8 +101,8 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentException"> | /// <exception cref="ArgumentException"> | ||||
| @@ -273,8 +273,8 @@ namespace Discord.Rest | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | ||||
| => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | |||||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions) | |||||
| => await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||||
| //IGuildChannel | //IGuildChannel | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -32,6 +32,7 @@ namespace Discord.Rest | |||||
| Icon = args.Icon.IsSpecified ? args.Icon.Value?.ToModel() : Optional.Create<ImageModel?>(), | Icon = args.Icon.IsSpecified ? args.Icon.Value?.ToModel() : Optional.Create<ImageModel?>(), | ||||
| Name = args.Name, | Name = args.Name, | ||||
| Splash = args.Splash.IsSpecified ? args.Splash.Value?.ToModel() : Optional.Create<ImageModel?>(), | Splash = args.Splash.IsSpecified ? args.Splash.Value?.ToModel() : Optional.Create<ImageModel?>(), | ||||
| Banner = args.Banner.IsSpecified ? args.Banner.Value?.ToModel() : Optional.Create<ImageModel?>(), | |||||
| VerificationLevel = args.VerificationLevel, | VerificationLevel = args.VerificationLevel, | ||||
| ExplicitContentFilter = args.ExplicitContentFilter, | ExplicitContentFilter = args.ExplicitContentFilter, | ||||
| SystemChannelFlags = args.SystemChannelFlags | SystemChannelFlags = args.SystemChannelFlags | ||||
| @@ -57,6 +58,8 @@ namespace Discord.Rest | |||||
| else if (args.RegionId.IsSpecified) | else if (args.RegionId.IsSpecified) | ||||
| apiArgs.RegionId = args.RegionId.Value; | apiArgs.RegionId = args.RegionId.Value; | ||||
| if (!apiArgs.Banner.IsSpecified && guild.BannerId != null) | |||||
| apiArgs.Banner = new ImageModel(guild.BannerId); | |||||
| if (!apiArgs.Splash.IsSpecified && guild.SplashId != null) | if (!apiArgs.Splash.IsSpecified && guild.SplashId != null) | ||||
| apiArgs.Splash = new ImageModel(guild.SplashId); | apiArgs.Splash = new ImageModel(guild.SplashId); | ||||
| if (!apiArgs.Icon.IsSpecified && guild.IconId != null) | if (!apiArgs.Icon.IsSpecified && guild.IconId != null) | ||||
| @@ -257,7 +260,7 @@ namespace Discord.Rest | |||||
| //Roles | //Roles | ||||
| /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception> | /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception> | ||||
| public static async Task<RestRole> CreateRoleAsync(IGuild guild, BaseDiscordClient client, | public static async Task<RestRole> CreateRoleAsync(IGuild guild, BaseDiscordClient client, | ||||
| string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) | |||||
| string name, GuildPermissions? permissions, Color? color, bool isHoisted, bool isMentionable, RequestOptions options) | |||||
| { | { | ||||
| if (name == null) throw new ArgumentNullException(paramName: nameof(name)); | if (name == null) throw new ArgumentNullException(paramName: nameof(name)); | ||||
| @@ -270,6 +273,7 @@ namespace Discord.Rest | |||||
| x.Permissions = (permissions ?? role.Permissions); | x.Permissions = (permissions ?? role.Permissions); | ||||
| x.Color = (color ?? Color.Default); | x.Color = (color ?? Color.Default); | ||||
| x.Hoist = isHoisted; | x.Hoist = isHoisted; | ||||
| x.Mentionable = isMentionable; | |||||
| }, options).ConfigureAwait(false); | }, options).ConfigureAwait(false); | ||||
| return role; | return role; | ||||
| @@ -349,7 +353,7 @@ namespace Discord.Rest | |||||
| ulong? fromUserId, int? limit, RequestOptions options) | ulong? fromUserId, int? limit, RequestOptions options) | ||||
| { | { | ||||
| return new PagedAsyncEnumerable<RestGuildUser>( | return new PagedAsyncEnumerable<RestGuildUser>( | ||||
| DiscordConfig.MaxMessagesPerBatch, | |||||
| DiscordConfig.MaxUsersPerBatch, | |||||
| async (info, ct) => | async (info, ct) => | ||||
| { | { | ||||
| var args = new GetGuildMembersParams | var args = new GetGuildMembersParams | ||||
| @@ -363,7 +367,7 @@ namespace Discord.Rest | |||||
| }, | }, | ||||
| nextPage: (info, lastPage) => | nextPage: (info, lastPage) => | ||||
| { | { | ||||
| if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) | |||||
| if (lastPage.Count != DiscordConfig.MaxUsersPerBatch) | |||||
| return false; | return false; | ||||
| info.Position = lastPage.Max(x => x.Id); | info.Position = lastPage.Max(x => x.Id); | ||||
| return true; | return true; | ||||
| @@ -530,6 +530,11 @@ namespace Discord.Rest | |||||
| return null; | return null; | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), | |||||
| bool isHoisted = false, RequestOptions options = null) | |||||
| => CreateRoleAsync(name, permissions, color, isHoisted, false, options); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new role with the provided name. | /// Creates a new role with the provided name. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -538,14 +543,15 @@ namespace Discord.Rest | |||||
| /// <param name="color">The color of the role.</param> | /// <param name="color">The color of the role.</param> | ||||
| /// <param name="isHoisted">Whether the role is separated from others on the sidebar.</param> | /// <param name="isHoisted">Whether the role is separated from others on the sidebar.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <param name="isMentionable">Whether the role can be mentioned.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous creation operation. The task result contains the newly created | /// A task that represents the asynchronous creation operation. The task result contains the newly created | ||||
| /// role. | /// role. | ||||
| /// </returns> | /// </returns> | ||||
| public async Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), | public async Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), | ||||
| bool isHoisted = false, RequestOptions options = null) | |||||
| bool isHoisted = false, bool isMentionable = false, RequestOptions options = null) | |||||
| { | { | ||||
| var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options).ConfigureAwait(false); | |||||
| var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, isMentionable, options).ConfigureAwait(false); | |||||
| _roles = _roles.Add(role.Id, role); | _roles = _roles.Add(role.Id, role); | ||||
| return role; | return role; | ||||
| } | } | ||||
| @@ -833,7 +839,10 @@ namespace Discord.Rest | |||||
| => GetRole(id); | => GetRole(id); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) | async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) | ||||
| => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); | |||||
| => await CreateRoleAsync(name, permissions, color, isHoisted, false, options).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | |||||
| async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, bool isMentionable, RequestOptions options) | |||||
| => await CreateRoleAsync(name, permissions, color, isHoisted, isMentionable, options).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IGuildUser> IGuild.AddGuildUserAsync(ulong userId, string accessToken, Action<AddGuildUserProperties> func, RequestOptions options) | async Task<IGuildUser> IGuild.AddGuildUserAsync(ulong userId, string accessToken, Action<AddGuildUserProperties> func, RequestOptions options) | ||||
| @@ -0,0 +1,15 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API | |||||
| { | |||||
| public class AllowedMentions | |||||
| { | |||||
| [JsonProperty("parse")] | |||||
| public Optional<string[]> Parse { get; set; } | |||||
| // Roles and Users have a max size of 100 | |||||
| [JsonProperty("roles")] | |||||
| public Optional<ulong[]> Roles { get; set; } | |||||
| [JsonProperty("users")] | |||||
| public Optional<ulong[]> Users { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -32,6 +32,11 @@ namespace Discord.Rest | |||||
| var args = new MessageProperties(); | var args = new MessageProperties(); | ||||
| func(args); | func(args); | ||||
| bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content); | |||||
| bool hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : msg.Embeds.Any(); | |||||
| if (!hasText && !hasEmbed) | |||||
| Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | |||||
| var apiArgs = new API.Rest.ModifyMessageParams | var apiArgs = new API.Rest.ModifyMessageParams | ||||
| { | { | ||||
| Content = args.Content, | Content = args.Content, | ||||
| @@ -62,6 +62,8 @@ namespace Discord.Rest | |||||
| public MessageActivity Activity { get; private set; } | public MessageActivity Activity { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public MessageApplication Application { get; private set; } | public MessageApplication Application { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public MessageReference Reference { get; private set; } | |||||
| internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | ||||
| : base(discord, id) | : base(discord, id) | ||||
| @@ -108,6 +110,17 @@ namespace Discord.Rest | |||||
| }; | }; | ||||
| } | } | ||||
| if(model.Reference.IsSpecified) | |||||
| { | |||||
| // Creates a new Reference from the API model | |||||
| Reference = new MessageReference | |||||
| { | |||||
| GuildId = model.Reference.Value.GuildId, | |||||
| ChannelId = model.Reference.Value.ChannelId, | |||||
| MessageId = model.Reference.Value.MessageId | |||||
| }; | |||||
| } | |||||
| if (model.Reactions.IsSpecified) | if (model.Reactions.IsSpecified) | ||||
| { | { | ||||
| var value = model.Reactions.Value; | var value = model.Reactions.Value; | ||||
| @@ -1,3 +1,4 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Linq; | using System.Linq; | ||||
| @@ -5,6 +6,13 @@ namespace Discord.Rest | |||||
| { | { | ||||
| internal static class EntityExtensions | internal static class EntityExtensions | ||||
| { | { | ||||
| public static IEmote ToIEmote(this API.Emoji model) | |||||
| { | |||||
| if (model.Id.HasValue) | |||||
| return model.ToEntity(); | |||||
| return new Emoji(model.Name); | |||||
| } | |||||
| public static GuildEmote ToEntity(this API.Emoji model) | public static GuildEmote ToEntity(this API.Emoji model) | ||||
| => new GuildEmote(model.Id.Value, | => new GuildEmote(model.Id.Value, | ||||
| model.Name, | model.Name, | ||||
| @@ -53,6 +61,24 @@ namespace Discord.Rest | |||||
| model.Video = entity.Video.Value.ToModel(); | model.Video = entity.Video.Value.ToModel(); | ||||
| return model; | return model; | ||||
| } | } | ||||
| public static API.AllowedMentions ToModel(this AllowedMentions entity) | |||||
| { | |||||
| return new API.AllowedMentions() | |||||
| { | |||||
| Parse = entity.AllowedTypes?.EnumerateMentionTypes().ToArray(), | |||||
| Roles = entity.RoleIds?.ToArray(), | |||||
| Users = entity.UserIds?.ToArray(), | |||||
| }; | |||||
| } | |||||
| public static IEnumerable<string> EnumerateMentionTypes(this AllowedMentionTypes mentionTypes) | |||||
| { | |||||
| if (mentionTypes.HasFlag(AllowedMentionTypes.Everyone)) | |||||
| yield return "everyone"; | |||||
| if (mentionTypes.HasFlag(AllowedMentionTypes.Roles)) | |||||
| yield return "roles"; | |||||
| if (mentionTypes.HasFlag(AllowedMentionTypes.Users)) | |||||
| yield return "users"; | |||||
| } | |||||
| public static EmbedAuthor ToEntity(this API.EmbedAuthor model) | public static EmbedAuthor ToEntity(this API.EmbedAuthor model) | ||||
| { | { | ||||
| return new EmbedAuthor(model.Name, model.Url, model.IconUrl, model.ProxyIconUrl); | return new EmbedAuthor(model.Name, model.Url, model.IconUrl, model.ProxyIconUrl); | ||||
| @@ -82,6 +82,20 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| return null; | return null; | ||||
| } | } | ||||
| internal void PurgeAllChannels() | |||||
| { | |||||
| foreach (var guild in _guilds.Values) | |||||
| guild.PurgeChannelCache(this); | |||||
| PurgeDMChannels(); | |||||
| } | |||||
| internal void PurgeDMChannels() | |||||
| { | |||||
| foreach (var channel in _dmChannels.Values) | |||||
| _channels.TryRemove(channel.Id, out _); | |||||
| _dmChannels.Clear(); | |||||
| } | |||||
| internal SocketGuild GetGuild(ulong id) | internal SocketGuild GetGuild(ulong id) | ||||
| { | { | ||||
| @@ -96,7 +110,11 @@ namespace Discord.WebSocket | |||||
| internal SocketGuild RemoveGuild(ulong id) | internal SocketGuild RemoveGuild(ulong id) | ||||
| { | { | ||||
| if (_guilds.TryRemove(id, out SocketGuild guild)) | if (_guilds.TryRemove(id, out SocketGuild guild)) | ||||
| { | |||||
| guild.PurgeChannelCache(this); | |||||
| guild.PurgeGuildUserCache(); | |||||
| return guild; | return guild; | ||||
| } | |||||
| return null; | return null; | ||||
| } | } | ||||
| @@ -116,5 +134,10 @@ namespace Discord.WebSocket | |||||
| return user; | return user; | ||||
| return null; | return null; | ||||
| } | } | ||||
| internal void PurgeUsers() | |||||
| { | |||||
| foreach (var guild in _guilds.Values) | |||||
| guild.PurgeGuildUserCache(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -389,8 +389,11 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| if (disposing) | if (disposing) | ||||
| { | { | ||||
| foreach (var client in _shards) | |||||
| client?.Dispose(); | |||||
| if (_shards != null) | |||||
| { | |||||
| foreach (var client in _shards) | |||||
| client?.Dispose(); | |||||
| } | |||||
| _connectionGroupLock?.Dispose(); | _connectionGroupLock?.Dispose(); | ||||
| } | } | ||||
| @@ -164,26 +164,17 @@ namespace Discord.API | |||||
| } | } | ||||
| } | } | ||||
| public async Task DisconnectAsync() | |||||
| public async Task DisconnectAsync(Exception ex = null) | |||||
| { | { | ||||
| await _stateLock.WaitAsync().ConfigureAwait(false); | await _stateLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||||
| } | |||||
| finally { _stateLock.Release(); } | |||||
| } | |||||
| public async Task DisconnectAsync(Exception ex) | |||||
| { | |||||
| await _stateLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||||
| await DisconnectInternalAsync(ex).ConfigureAwait(false); | |||||
| } | } | ||||
| finally { _stateLock.Release(); } | finally { _stateLock.Release(); } | ||||
| } | } | ||||
| /// <exception cref="NotSupportedException">This client is not configured with WebSocket support.</exception> | /// <exception cref="NotSupportedException">This client is not configured with WebSocket support.</exception> | ||||
| internal override async Task DisconnectInternalAsync() | |||||
| internal override async Task DisconnectInternalAsync(Exception ex = null) | |||||
| { | { | ||||
| if (WebSocketClient == null) | if (WebSocketClient == null) | ||||
| throw new NotSupportedException("This client is not configured with WebSocket support."); | throw new NotSupportedException("This client is not configured with WebSocket support."); | ||||
| @@ -194,6 +185,9 @@ namespace Discord.API | |||||
| try { _connectCancelToken?.Cancel(false); } | try { _connectCancelToken?.Cancel(false); } | ||||
| catch { } | catch { } | ||||
| if (ex is GatewayReconnectException) | |||||
| await WebSocketClient.DisconnectAsync(4000); | |||||
| else | |||||
| await WebSocketClient.DisconnectAsync().ConfigureAwait(false); | await WebSocketClient.DisconnectAsync().ConfigureAwait(false); | ||||
| ConnectionState = ConnectionState.Disconnected; | ConnectionState = ConnectionState.Disconnected; | ||||
| @@ -264,7 +264,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | ||||
| await ApiClient.DisconnectAsync().ConfigureAwait(false); | |||||
| await ApiClient.DisconnectAsync(ex).ConfigureAwait(false); | |||||
| //Wait for tasks to complete | //Wait for tasks to complete | ||||
| await _gatewayLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | ||||
| @@ -306,6 +306,14 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override SocketChannel GetChannel(ulong id) | public override SocketChannel GetChannel(ulong id) | ||||
| => State.GetChannel(id); | => State.GetChannel(id); | ||||
| /// <summary> | |||||
| /// Clears all cached channels from the client. | |||||
| /// </summary> | |||||
| public void PurgeChannelCache() => State.PurgeAllChannels(); | |||||
| /// <summary> | |||||
| /// Clears cached DM channels from the client. | |||||
| /// </summary> | |||||
| public void PurgeDMChannelCache() => State.PurgeDMChannels(); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override SocketUser GetUser(ulong id) | public override SocketUser GetUser(ulong id) | ||||
| @@ -313,6 +321,10 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| 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); | => State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); | ||||
| /// <summary> | |||||
| /// Clears cached users from the client. | |||||
| /// </summary> | |||||
| public void PurgeUserCache() => State.PurgeUsers(); | |||||
| internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) | internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) | ||||
| { | { | ||||
| return state.GetOrAddUser(model.Id, x => | return state.GetOrAddUser(model.Id, x => | ||||
| @@ -511,7 +523,7 @@ namespace Discord.WebSocket | |||||
| case GatewayOpCode.Reconnect: | case GatewayOpCode.Reconnect: | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); | ||||
| _connection.Error(new Exception("Server requested a reconnect")); | |||||
| _connection.Error(new GatewayReconnectException("Server requested a reconnect")); | |||||
| } | } | ||||
| break; | break; | ||||
| case GatewayOpCode.Dispatch: | case GatewayOpCode.Dispatch: | ||||
| @@ -628,6 +640,7 @@ namespace Discord.WebSocket | |||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| await TimedInvokeAsync(_joinedGuildEvent, nameof(JoinedGuild), guild).ConfigureAwait(false); | await TimedInvokeAsync(_joinedGuildEvent, nameof(JoinedGuild), guild).ConfigureAwait(false); | ||||
| await GuildAvailableAsync(guild).ConfigureAwait(false); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -1689,7 +1702,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? true)) | if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? true)) | ||||
| { | { | ||||
| _connection.Error(new Exception("Server missed last heartbeat")); | |||||
| _connection.Error(new GatewayReconnectException("Server missed last heartbeat")); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| @@ -29,11 +29,15 @@ namespace Discord.WebSocket | |||||
| /// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | /// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | ||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
| /// contains the sent message. | /// contains the sent message. | ||||
| /// </returns> | /// </returns> | ||||
| new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||||
| new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -135,8 +135,8 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | ||||
| @@ -235,8 +235,8 @@ namespace Discord.WebSocket | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | ||||
| => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | |||||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions) | |||||
| => await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||||
| //IChannel | //IChannel | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -163,8 +163,8 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | ||||
| @@ -299,8 +299,8 @@ namespace Discord.WebSocket | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | ||||
| => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | |||||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions) | |||||
| => await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||||
| //IAudioChannel | //IAudioChannel | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -11,7 +11,7 @@ namespace Discord.WebSocket | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// <note type="warning"> | /// <note type="warning"> | ||||
| /// Most of the properties and methods featured may not be supported due to the nature of the channel. | |||||
| /// The <see cref="SlowModeInterval"/> property is not supported for news channels. | |||||
| /// </note> | /// </note> | ||||
| /// </remarks> | /// </remarks> | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| @@ -35,53 +35,5 @@ namespace Discord.WebSocket | |||||
| /// </remarks> | /// </remarks> | ||||
| public override int SlowModeInterval | public override int SlowModeInterval | ||||
| => throw new NotSupportedException("News channels do not support Slow Mode."); | => throw new NotSupportedException("News channels do not support Slow Mode."); | ||||
| /// <inheritdoc /> | |||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// This method is not supported by this type. Attempting to use this method will result in a <see cref="NotSupportedException"/>. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) | |||||
| => throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| /// <inheritdoc /> | |||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// This method is not supported by this type. Attempting to use this method will result in a <see cref="NotSupportedException"/>. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) | |||||
| => throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| /// <inheritdoc /> | |||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// This property is not supported by this type. Attempting to use this property will result in a <see cref="NotSupportedException"/>. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public override IReadOnlyCollection<Overwrite> PermissionOverwrites | |||||
| => throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| /// <inheritdoc /> | |||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// This method is not supported by this type. Attempting to use this method will result in a <see cref="NotSupportedException"/>. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public override Task SyncPermissionsAsync(RequestOptions options = null) | |||||
| => throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| /// <inheritdoc /> | |||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// This method is not supported by this type. Attempting to use this method will result in a <see cref="NotSupportedException"/>. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) | |||||
| => throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| /// <inheritdoc /> | |||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// This method is not supported by this type. Attempting to use this method will result in a <see cref="NotSupportedException"/>. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) | |||||
| => throw new NotSupportedException("News channels do not support Overwrite Permissions."); | |||||
| } | } | ||||
| } | } | ||||
| @@ -161,8 +161,8 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||||
| public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | ||||
| @@ -308,8 +308,8 @@ namespace Discord.WebSocket | |||||
| async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | ||||
| => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | |||||
| => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions) | |||||
| => await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||||
| // INestedChannel | // INestedChannel | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -623,6 +623,13 @@ namespace Discord.WebSocket | |||||
| return state.RemoveChannel(id) as SocketGuildChannel; | return state.RemoveChannel(id) as SocketGuildChannel; | ||||
| return null; | return null; | ||||
| } | } | ||||
| internal void PurgeChannelCache(ClientState state) | |||||
| { | |||||
| foreach (var channelId in _channels) | |||||
| state.RemoveChannel(channelId); | |||||
| _channels.Clear(); | |||||
| } | |||||
| //Voice Regions | //Voice Regions | ||||
| /// <summary> | /// <summary> | ||||
| @@ -679,6 +686,10 @@ namespace Discord.WebSocket | |||||
| return null; | return null; | ||||
| } | } | ||||
| /// <inheritdoc /> | |||||
| public Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), | |||||
| bool isHoisted = false, RequestOptions options = null) | |||||
| => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, false, options); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new role with the provided name. | /// Creates a new role with the provided name. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -686,6 +697,7 @@ namespace Discord.WebSocket | |||||
| /// <param name="permissions">The guild permission that the role should possess.</param> | /// <param name="permissions">The guild permission that the role should possess.</param> | ||||
| /// <param name="color">The color of the role.</param> | /// <param name="color">The color of the role.</param> | ||||
| /// <param name="isHoisted">Whether the role is separated from others on the sidebar.</param> | /// <param name="isHoisted">Whether the role is separated from others on the sidebar.</param> | ||||
| /// <param name="isMentionable">Whether the role can be mentioned.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception> | /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception> | ||||
| /// <returns> | /// <returns> | ||||
| @@ -693,8 +705,8 @@ namespace Discord.WebSocket | |||||
| /// role. | /// role. | ||||
| /// </returns> | /// </returns> | ||||
| public Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), | public Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), | ||||
| bool isHoisted = false, RequestOptions options = null) | |||||
| => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options); | |||||
| bool isHoisted = false, bool isMentionable = false, RequestOptions options = null) | |||||
| => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, isMentionable, options); | |||||
| internal SocketRole AddRole(RoleModel model) | internal SocketRole AddRole(RoleModel model) | ||||
| { | { | ||||
| var role = SocketRole.Create(this, Discord.State, model); | var role = SocketRole.Create(this, Discord.State, model); | ||||
| @@ -792,6 +804,21 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| return null; | return null; | ||||
| } | } | ||||
| internal void PurgeGuildUserCache() | |||||
| { | |||||
| var members = Users; | |||||
| var self = CurrentUser; | |||||
| _members.Clear(); | |||||
| _members.TryAdd(self.Id, self); | |||||
| DownloadedMemberCount = _members.Count; | |||||
| foreach (var member in members) | |||||
| { | |||||
| if (member.Id != self.Id) | |||||
| member.GlobalUser.RemoveRef(Discord); | |||||
| } | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task DownloadUsersAsync() | public async Task DownloadUsersAsync() | ||||
| @@ -1151,7 +1178,10 @@ namespace Discord.WebSocket | |||||
| => GetRole(id); | => GetRole(id); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) | async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) | ||||
| => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); | |||||
| => await CreateRoleAsync(name, permissions, color, isHoisted, false, options).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | |||||
| async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, bool isMentionable, RequestOptions options) | |||||
| => await CreateRoleAsync(name, permissions, color, isHoisted, isMentionable, options).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) | ||||
| @@ -53,6 +53,9 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public MessageApplication Application { get; private set; } | public MessageApplication Application { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public MessageReference Reference { get; private set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Returns all attachments included in this message. | /// Returns all attachments included in this message. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -140,6 +143,17 @@ namespace Discord.WebSocket | |||||
| PartyId = model.Activity.Value.PartyId.Value | PartyId = model.Activity.Value.PartyId.Value | ||||
| }; | }; | ||||
| } | } | ||||
| if (model.Reference.IsSpecified) | |||||
| { | |||||
| // Creates a new Reference from the API model | |||||
| Reference = new MessageReference | |||||
| { | |||||
| GuildId = model.Reference.Value.GuildId, | |||||
| ChannelId = model.Reference.Value.ChannelId, | |||||
| MessageId = model.Reference.Value.MessageId | |||||
| }; | |||||
| } | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -40,7 +40,7 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public UserStatus Status => Presence.Status; | public UserStatus Status => Presence.Status; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public IImmutableSet<ClientType> ActiveClients => Presence.ActiveClients; | |||||
| public IImmutableSet<ClientType> ActiveClients => Presence.ActiveClients ?? ImmutableHashSet<ClientType>.Empty; | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets mutual guilds shared with this user. | /// Gets mutual guilds shared with this user. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -1,3 +1,5 @@ | |||||
| using Discord.Rest; | |||||
| using System; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Linq; | using System.Linq; | ||||
| @@ -7,6 +9,19 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| public static IActivity ToEntity(this API.Game model) | public static IActivity ToEntity(this API.Game model) | ||||
| { | { | ||||
| // Custom Status Game | |||||
| if (model.Id.IsSpecified && model.Id.Value == "custom") | |||||
| { | |||||
| return new CustomStatusGame() | |||||
| { | |||||
| Type = ActivityType.CustomStatus, | |||||
| Name = model.Name, | |||||
| State = model.State.IsSpecified ? model.State.Value : null, | |||||
| Emote = model.Emoji.IsSpecified ? model.Emoji.Value.ToIEmote() : null, | |||||
| CreatedAt = DateTimeOffset.FromUnixTimeMilliseconds(model.CreatedAt.Value), | |||||
| }; | |||||
| } | |||||
| // Spotify Game | // Spotify Game | ||||
| if (model.SyncId.IsSpecified) | if (model.SyncId.IsSpecified) | ||||
| { | { | ||||
| @@ -23,6 +38,8 @@ namespace Discord.WebSocket | |||||
| AlbumTitle = albumText, | AlbumTitle = albumText, | ||||
| TrackTitle = model.Details.GetValueOrDefault(), | TrackTitle = model.Details.GetValueOrDefault(), | ||||
| Artists = model.State.GetValueOrDefault()?.Split(';').Select(x=>x?.Trim()).ToImmutableArray(), | Artists = model.State.GetValueOrDefault()?.Split(';').Select(x=>x?.Trim()).ToImmutableArray(), | ||||
| StartedAt = timestamps?.Start, | |||||
| EndsAt = timestamps?.End, | |||||
| Duration = timestamps?.End - timestamps?.Start, | Duration = timestamps?.End - timestamps?.Start, | ||||
| AlbumArtUrl = albumArtId != null ? CDN.GetSpotifyAlbumArtUrl(albumArtId) : null, | AlbumArtUrl = albumArtId != null ? CDN.GetSpotifyAlbumArtUrl(albumArtId) : null, | ||||
| Type = ActivityType.Listening, | Type = ActivityType.Listening, | ||||
| @@ -0,0 +1,22 @@ | |||||
| using System; | |||||
| namespace Discord.WebSocket | |||||
| { | |||||
| /// <summary> | |||||
| /// An exception thrown when the gateway client has been requested to | |||||
| /// reconnect. | |||||
| /// </summary> | |||||
| public class GatewayReconnectException : Exception | |||||
| { | |||||
| /// <summary> | |||||
| /// Creates a new instance of the | |||||
| /// <see cref="GatewayReconnectException"/> type. | |||||
| /// </summary> | |||||
| /// <param name="message"> | |||||
| /// The reason why the gateway has been requested to reconnect. | |||||
| /// </param> | |||||
| public GatewayReconnectException(string message) | |||||
| : base(message) | |||||
| { } | |||||
| } | |||||
| } | |||||
| @@ -44,7 +44,7 @@ namespace Discord.Net.WebSockets | |||||
| { | { | ||||
| if (disposing) | if (disposing) | ||||
| { | { | ||||
| DisconnectInternalAsync(true).GetAwaiter().GetResult(); | |||||
| DisconnectInternalAsync(isDisposing: true).GetAwaiter().GetResult(); | |||||
| _disconnectTokenSource?.Dispose(); | _disconnectTokenSource?.Dispose(); | ||||
| _cancelTokenSource?.Dispose(); | _cancelTokenSource?.Dispose(); | ||||
| _lock?.Dispose(); | _lock?.Dispose(); | ||||
| @@ -94,19 +94,19 @@ namespace Discord.Net.WebSockets | |||||
| _task = RunAsync(_cancelToken); | _task = RunAsync(_cancelToken); | ||||
| } | } | ||||
| public async Task DisconnectAsync() | |||||
| public async Task DisconnectAsync(int closeCode = 1000) | |||||
| { | { | ||||
| await _lock.WaitAsync().ConfigureAwait(false); | await _lock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||||
| await DisconnectInternalAsync(closeCode: closeCode).ConfigureAwait(false); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _lock.Release(); | _lock.Release(); | ||||
| } | } | ||||
| } | } | ||||
| private async Task DisconnectInternalAsync(bool isDisposing = false) | |||||
| private async Task DisconnectInternalAsync(int closeCode = 1000, bool isDisposing = false) | |||||
| { | { | ||||
| try { _disconnectTokenSource.Cancel(false); } | try { _disconnectTokenSource.Cancel(false); } | ||||
| catch { } | catch { } | ||||
| @@ -117,7 +117,8 @@ namespace Discord.Net.WebSockets | |||||
| { | { | ||||
| if (!isDisposing) | if (!isDisposing) | ||||
| { | { | ||||
| try { await _client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", new CancellationToken()); } | |||||
| var status = (WebSocketCloseStatus)closeCode; | |||||
| try { await _client.CloseOutputAsync(status, "", new CancellationToken()); } | |||||
| catch { } | catch { } | ||||
| } | } | ||||
| try { _client.Dispose(); } | try { _client.Dispose(); } | ||||
| @@ -141,7 +142,7 @@ namespace Discord.Net.WebSockets | |||||
| await _lock.WaitAsync().ConfigureAwait(false); | await _lock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| await DisconnectInternalAsync(false); | |||||
| await DisconnectInternalAsync(isDisposing: false); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| @@ -85,7 +85,7 @@ namespace Discord.Webhook | |||||
| } | } | ||||
| private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | ||||
| => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); | => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); | ||||
| /// <summary> Sends a message using to the channel for this webhook. </summary> | |||||
| /// <summary> Sends a message to the channel for this webhook. </summary> | |||||
| /// <returns> Returns the ID of the created message. </returns> | /// <returns> Returns the ID of the created message. </returns> | ||||
| public Task<ulong> SendMessageAsync(string text = null, bool isTTS = false, IEnumerable<Embed> embeds = null, | public Task<ulong> SendMessageAsync(string text = null, bool isTTS = false, IEnumerable<Embed> embeds = null, | ||||
| string username = null, string avatarUrl = null, RequestOptions options = null) | string username = null, string avatarUrl = null, RequestOptions options = null) | ||||
| @@ -2,10 +2,10 @@ | |||||
| <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | ||||
| <metadata> | <metadata> | ||||
| <id>Discord.Net</id> | <id>Discord.Net</id> | ||||
| <version>2.2.0-dev$suffix$</version> | |||||
| <version>2.3.0-dev$suffix$</version> | |||||
| <title>Discord.Net</title> | <title>Discord.Net</title> | ||||
| <authors>Discord.Net Contributors</authors> | <authors>Discord.Net Contributors</authors> | ||||
| <owners>RogueException</owners> | |||||
| <owners>foxbot</owners> | |||||
| <description>An asynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components.</description> | <description>An asynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components.</description> | ||||
| <tags>discord;discordapp</tags> | <tags>discord;discordapp</tags> | ||||
| <projectUrl>https://github.com/RogueException/Discord.Net</projectUrl> | <projectUrl>https://github.com/RogueException/Discord.Net</projectUrl> | ||||
| @@ -14,25 +14,25 @@ | |||||
| <iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl> | <iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl> | ||||
| <dependencies> | <dependencies> | ||||
| <group targetFramework="net461"> | <group targetFramework="net461"> | ||||
| <dependency id="Discord.Net.Core" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Rest" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.WebSocket" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Commands" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Webhook" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Core" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Rest" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.WebSocket" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Commands" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Webhook" version="2.3.0-dev$suffix$" /> | |||||
| </group> | </group> | ||||
| <group targetFramework="netstandard2.0"> | <group targetFramework="netstandard2.0"> | ||||
| <dependency id="Discord.Net.Core" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Rest" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.WebSocket" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Commands" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Webhook" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Core" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Rest" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.WebSocket" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Commands" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Webhook" version="2.3.0-dev$suffix$" /> | |||||
| </group> | </group> | ||||
| <group targetFramework="netstandard2.1"> | <group targetFramework="netstandard2.1"> | ||||
| <dependency id="Discord.Net.Core" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Rest" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.WebSocket" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Commands" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Webhook" version="2.2.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Core" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Rest" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.WebSocket" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Commands" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Webhook" version="2.3.0-dev$suffix$" /> | |||||
| </group> | </group> | ||||
| </dependencies> | </dependencies> | ||||
| </metadata> | </metadata> | ||||
| @@ -83,7 +83,7 @@ namespace Discord | |||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||
| public Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| public Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| { | { | ||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||
| @@ -91,7 +91,7 @@ namespace Discord | |||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||
| public Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| public Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| { | { | ||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||
| @@ -177,7 +177,7 @@ namespace Discord | |||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||
| public Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
| public Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null) | |||||
| { | { | ||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||