From b88ce8c51fbbc47a42c1e1dcacf8af60dadc8a4d Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 2 Feb 2018 19:21:16 -0200 Subject: [PATCH 01/33] Remove IGuild.DownloadUsersAsync() from SocketGuild (#944) --- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index ea68a8f54..e70df8ce8 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -696,7 +696,6 @@ namespace Discord.WebSocket => Task.FromResult(CurrentUser); Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Owner); - Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) => await GetWebhookAsync(id, options); From 3b2b4342581758eaafa18ee0bc3a4a945ce1bfcb Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 10 Feb 2018 19:37:41 -0500 Subject: [PATCH 02/33] Bump version to 2.0.0-beta2 --- Discord.Net.targets | 2 +- src/Discord.Net/Discord.Net.nuspec | 32 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 3f623c619..958b2053f 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,7 +1,7 @@ 2.0.0 - beta + beta2 RogueException discord;discordapp https://github.com/RogueException/Discord.Net diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index cd57d2fcf..2bf531cb1 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 2.0.0-beta$suffix$ + 2.0.0-beta2$suffix$ Discord.Net Discord.Net Contributors RogueException @@ -13,25 +13,25 @@ false - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + From 178ea8de4d53183c30cc304781d2f7acedbd0950 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sat, 17 Feb 2018 18:38:14 -0500 Subject: [PATCH 03/33] Change GameParty size types to longs. (#955) --- src/Discord.Net.Core/Entities/Activities/GameParty.cs | 8 ++++---- src/Discord.Net.Rest/API/Common/GameParty.cs | 6 +++--- src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Activities/GameParty.cs b/src/Discord.Net.Core/Entities/Activities/GameParty.cs index dbfe5b6ce..54e6deef4 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameParty.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameParty.cs @@ -1,11 +1,11 @@ -namespace Discord +namespace Discord { public class GameParty { internal GameParty() { } public string Id { get; internal set; } - public int Members { get; internal set; } - public int Capacity { get; internal set; } + public long Members { get; internal set; } + public long Capacity { get; internal set; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Rest/API/Common/GameParty.cs b/src/Discord.Net.Rest/API/Common/GameParty.cs index e0da4a098..4f8ce2654 100644 --- a/src/Discord.Net.Rest/API/Common/GameParty.cs +++ b/src/Discord.Net.Rest/API/Common/GameParty.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API { @@ -7,6 +7,6 @@ namespace Discord.API [JsonProperty("id")] public string Id { get; set; } [JsonProperty("size")] - public int[] Size { get; set; } + public long[] Size { get; set; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs index f85c89c71..181a837e4 100644 --- a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs @@ -1,4 +1,4 @@ -namespace Discord.WebSocket +namespace Discord.WebSocket { internal static class EntityExtensions { @@ -56,7 +56,7 @@ public static GameParty ToEntity(this API.GameParty model) { // Discord will probably send bad data since they don't validate anything - int current = 0, cap = 0; + long current = 0, cap = 0; if (model.Size?.Length == 2) { current = model.Size[0]; From bb8ebc13d282270a59d79910ae7b16c05e766b5c Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 18 Feb 2018 19:15:52 -0500 Subject: [PATCH 04/33] Add callback method for when a module class has been added (#934) commit 5b047bf02b4299f34172cac05dc7e4a84ecc108c Author: Joe4evr Date: Fri Feb 2 22:22:00 2018 +0100 [feature/OnModuleAdded] Quickstart fixes (#946) * Quickstart: fix minor derp * Other overdue fixes commit bd3e9eee943b9092cc45217b19ff95bae359f888 Author: Christopher F Date: Sat Jan 27 16:51:18 2018 -0500 Resort usings in ModuleBase commit 8042767579b337fdae7fe48e0a6ea2f007aef440 Author: Christopher F Date: Sat Jan 27 16:41:39 2018 -0500 Clean up removed owned IServiceProvider commit 30066cb102ffbd65906ead72a377811aa501abba Author: Christopher F Date: Sat Jan 27 16:37:22 2018 -0500 Remove redundant try-catch around OnModuleBuilding invocation If this exception is going to be rethrown, there's no reason to include a try-catch. commit 60c7c31d4476c498a97ae0536ec5792f08efb89b Author: Christopher F Date: Sat Jan 27 16:36:27 2018 -0500 Include the ModuleBuilder in OnModuleBuilding This allows modules hooking into OnModuleBuilding method to mutate theirselves at runtime. commit b6a9ff57860ff3bddbad7ca850fd331529cb8e6e Author: Joe4evr Date: Mon Jan 22 13:17:14 2018 +0100 #DERP commit f623d19c68c5642a44898a561f77ed82d53fd103 Author: Joe4evr Date: Mon Jan 22 13:15:31 2018 +0100 Resolution for #937 because it's literally 4 lines of code commit 8272c9675b0d63b4100aaf57f5067d635b68f5e6 Author: Joe4evr Date: Mon Jan 22 11:39:28 2018 +0100 Re-adjust quickstart commit e30b9071351b69baa30a93a4851516dca9ea43cf Author: Joe4evr Date: Mon Jan 22 11:35:08 2018 +0100 Undo experimental changes, request IServiceProvider instance everywhere instead commit ad7e0a46c8709e845dfacdc298a893e22dc11567 Author: Joe4evr Date: Fri Jan 19 03:40:27 2018 +0100 Fix quickstart leftover from previous draft commit e3349ef3d400bb3ad8cb28dd4234d5316a80bcc4 Author: Joe4evr Date: Fri Jan 19 03:33:46 2018 +0100 Doc comment on items commit 81bd9111faaf98a52679daae863ab04dce96e63e Author: Joe4evr Date: Fri Jan 19 03:16:44 2018 +0100 Add comment about the ServiceProviderFactory in the quickstart commit 72b5e6c8a149d8e989b46351965daa14f8ca318c Author: Joe4evr Date: Fri Jan 19 03:10:40 2018 +0100 Remove superfluous comments, provide simpler alternative for setting the ServiceProvider. commit 74b17b0e04e2c413397a2e1b66ff814615326205 Author: Joe4evr Date: Tue Jan 16 18:06:28 2018 +0100 Experimental change for feedback commit 7b100e99bb119be190006d1cd8e403776930e401 Author: Joe4evr Date: Mon Jan 15 23:34:06 2018 +0100 * Make the service provider parameters required * Adjust quickstart guide to reflect changes commit 7f1b792946ac6b950922b06178aa5cc37d9f4144 Author: Joe4evr Date: Mon Jan 15 20:04:37 2018 +0100 I..... missed one. commit 031b289d80604666dde62619e521af303203d48d Author: Joe4evr Date: Mon Jan 15 20:02:20 2018 +0100 Rename method to more intuitive 'OnModuleBuilding' commit 9a166ef1d0baecd21e4e5b965e2ac364feddbe2e Author: Joe4evr Date: Mon Jan 15 19:09:10 2018 +0100 Add callback method for when a module class has been added to the CommandService. --- .../samples/intro/structure.cs | 68 +++++--- .../Builders/ModuleBuilder.cs | 20 ++- .../Builders/ModuleClassBuilder.cs | 34 ++-- src/Discord.Net.Commands/CommandService.cs | 156 +++++++++--------- .../CommandServiceConfig.cs | 10 +- src/Discord.Net.Commands/IModuleBase.cs | 6 +- src/Discord.Net.Commands/Info/ModuleInfo.cs | 16 +- src/Discord.Net.Commands/ModuleBase.cs | 10 +- 8 files changed, 190 insertions(+), 130 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index bdfc12b67..a9a018c3a 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -19,10 +19,10 @@ class Program private readonly DiscordSocketClient _client; - // Keep the CommandService and IServiceCollection around for use with commands. + // Keep the CommandService and DI container around for use with commands. // These two types require you install the Discord.Net.Commands package. - private readonly IServiceCollection _map = new ServiceCollection(); - private readonly CommandService _commands = new CommandService(); + private readonly CommandService _commands; + private readonly IServiceProvider _services; private Program() { @@ -41,14 +41,45 @@ class Program // add the `using` at the top, and uncomment this line: //WebSocketProvider = WS4NetProvider.Instance }); + + _commands = new CommandService(new CommandServiceConfig + { + // Again, log level: + LogLevel = LogSeverity.Info, + + // There's a few more properties you can set, + // for example, case-insensitive commands. + CaseSensitiveCommands = false, + }); + // Subscribe the logging handler to both the client and the CommandService. - _client.Log += Logger; - _commands.Log += Logger; + _client.Log += Log; + _commands.Log += Log; + + // Setup your DI container. + _services = ConfigureServices(), + + } + + // If any services require the client, or the CommandService, or something else you keep on hand, + // pass them as parameters into this method as needed. + // If this method is getting pretty long, you can seperate it out into another file using partials. + private static IServiceProvider ConfigureServices() + { + var map = new ServiceCollection() + // Repeat this for all the service classes + // and other dependencies that your commands might need. + .AddSingleton(new SomeServiceClass()); + + // When all your required services are in the collection, build the container. + // Tip: There's an overload taking in a 'validateScopes' bool to make sure + // you haven't made any mistakes in your dependency graph. + return map.BuildServiceProvider(); } // Example of a logging handler. This can be re-used by addons // that ask for a Func. - private static Task Logger(LogMessage message) + private static Task Log(LogMessage message) { switch (message.Severity) { @@ -92,24 +123,15 @@ class Program await Task.Delay(Timeout.Infinite); } - private IServiceProvider _services; - private async Task InitCommands() { - // Repeat this for all the service classes - // and other dependencies that your commands might need. - _map.AddSingleton(new SomeServiceClass()); - - // When all your required services are in the collection, build the container. - // Tip: There's an overload taking in a 'validateScopes' bool to make sure - // you haven't made any mistakes in your dependency graph. - _services = _map.BuildServiceProvider(); - // Either search the program and add all Module classes that can be found. // Module classes MUST be marked 'public' or they will be ignored. - await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); + // You also need to pass your 'IServiceProvider' instance now, + // so make sure that's done before you get here. + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); // Or add Modules manually if you prefer to be a little more explicit: - await _commands.AddModuleAsync(); + await _commands.AddModuleAsync(_services); // Note that the first one is 'Modules' (plural) and the second is 'Module' (singular). // Subscribe a handler to see if a message invokes a command. @@ -123,8 +145,6 @@ class Program if (msg == null) return; // We don't want the bot to respond to itself or other bots. - // NOTE: Selfbots should invert this first check and remove the second - // as they should ONLY be allowed to respond to messages from the same account. if (msg.Author.Id == _client.CurrentUser.Id || msg.Author.IsBot) return; // Create a number to track where the prefix ends and the command begins @@ -140,10 +160,12 @@ class Program // Execute the command. (result does not indicate a return value, // rather an object stating if the command executed successfully). - var result = await _commands.ExecuteAsync(context, pos, _services); + var result = await _commands.ExecuteAsync(context, pos); // Uncomment the following lines if you want the bot - // to send a message if it failed (not advised for most situations). + // to send a message if it failed. + // This does not catch errors from commands with 'RunMode.Async', + // subscribe a handler for '_commands.CommandExecuted' to see those. //if (!result.IsSuccess && result.Error != CommandError.UnknownCommand) // await msg.Channel.SendMessageAsync(result.ErrorReason); } diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 0a33c9e26..1809c2c63 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Reflection; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -18,6 +19,7 @@ namespace Discord.Commands.Builders public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } + public string Group { get; set; } public IReadOnlyList Commands => _commands; public IReadOnlyList Modules => _submodules; @@ -25,6 +27,8 @@ namespace Discord.Commands.Builders public IReadOnlyList Attributes => _attributes; public IReadOnlyList Aliases => _aliases; + internal TypeInfo TypeInfo { get; set; } + //Automatic internal ModuleBuilder(CommandService service, ModuleBuilder parent) { @@ -111,17 +115,23 @@ namespace Discord.Commands.Builders return this; } - private ModuleInfo BuildImpl(CommandService service, ModuleInfo parent = null) + private ModuleInfo BuildImpl(CommandService service, IServiceProvider services, ModuleInfo parent = null) { //Default name to first alias if (Name == null) Name = _aliases[0]; - return new ModuleInfo(this, service, parent); + if (TypeInfo != null) + { + var moduleInstance = ReflectionUtils.CreateObject(TypeInfo, service, services); + moduleInstance.OnModuleBuilding(service, this); + } + + return new ModuleInfo(this, service, services, parent); } - public ModuleInfo Build(CommandService service) => BuildImpl(service); + public ModuleInfo Build(CommandService service, IServiceProvider services) => BuildImpl(service, services); - internal ModuleInfo Build(CommandService service, ModuleInfo parent) => BuildImpl(service, parent); + internal ModuleInfo Build(CommandService service, IServiceProvider services, ModuleInfo parent) => BuildImpl(service, services, parent); } } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 5a3a1f25a..c0a7e9aca 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -42,8 +42,8 @@ namespace Discord.Commands } - public static Task> BuildAsync(CommandService service, params TypeInfo[] validTypes) => BuildAsync(validTypes, service); - public static async Task> BuildAsync(IEnumerable validTypes, CommandService service) + public static Task> BuildAsync(CommandService service, IServiceProvider services, params TypeInfo[] validTypes) => BuildAsync(validTypes, service, services); + public static async Task> BuildAsync(IEnumerable validTypes, CommandService service, IServiceProvider services) { /*if (!validTypes.Any()) throw new InvalidOperationException("Could not find any valid modules from the given selection");*/ @@ -63,11 +63,11 @@ namespace Discord.Commands var module = new ModuleBuilder(service, null); - BuildModule(module, typeInfo, service); - BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); + BuildModule(module, typeInfo, service, services); + BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service, services); builtTypes.Add(typeInfo); - result[typeInfo.AsType()] = module.Build(service); + result[typeInfo.AsType()] = module.Build(service, services); } await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} modules.").ConfigureAwait(false); @@ -75,7 +75,7 @@ namespace Discord.Commands return result; } - private static void BuildSubTypes(ModuleBuilder builder, IEnumerable subTypes, List builtTypes, CommandService service) + private static void BuildSubTypes(ModuleBuilder builder, IEnumerable subTypes, List builtTypes, CommandService service, IServiceProvider services) { foreach (var typeInfo in subTypes) { @@ -87,17 +87,18 @@ namespace Discord.Commands builder.AddModule((module) => { - BuildModule(module, typeInfo, service); - BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); + BuildModule(module, typeInfo, service, services); + BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service, services); }); builtTypes.Add(typeInfo); } } - private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service) + private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service, IServiceProvider services) { var attributes = typeInfo.GetCustomAttributes(); + builder.TypeInfo = typeInfo; foreach (var attribute in attributes) { @@ -117,6 +118,7 @@ namespace Discord.Commands break; case GroupAttribute group: builder.Name = builder.Name ?? group.Prefix; + builder.Group = group.Prefix; builder.AddAliases(group.Prefix); break; case PreconditionAttribute precondition: @@ -140,12 +142,12 @@ namespace Discord.Commands { builder.AddCommand((command) => { - BuildCommand(command, typeInfo, method, service); + BuildCommand(command, typeInfo, method, service, services); }); } } - private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service) + private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service, IServiceProvider serviceprovider) { var attributes = method.GetCustomAttributes(); @@ -191,7 +193,7 @@ namespace Discord.Commands { builder.AddParameter((parameter) => { - BuildParameter(parameter, paramInfo, pos++, count, service); + BuildParameter(parameter, paramInfo, pos++, count, service, serviceprovider); }); } @@ -227,7 +229,7 @@ namespace Discord.Commands builder.Callback = ExecuteCallback; } - private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service) + private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service, IServiceProvider services) { var attributes = paramInfo.GetCustomAttributes(); var paramType = paramInfo.ParameterType; @@ -245,7 +247,7 @@ namespace Discord.Commands builder.Summary = summary.Text; break; case OverrideTypeReaderAttribute typeReader: - builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader); + builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader, services); break; case ParamArrayAttribute _: builder.IsMultiple = true; @@ -285,7 +287,7 @@ namespace Discord.Commands } } - private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType) + private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType, IServiceProvider services) { var readers = service.GetTypeReaders(paramType); TypeReader reader = null; @@ -296,7 +298,7 @@ namespace Discord.Commands } //We dont have a cached type reader, create one - reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, EmptyServiceProvider.Instance); + reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, services); service.AddTypeReader(paramType, reader); return reader; diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 8e7dab898..7efc1bc62 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -1,5 +1,3 @@ -using Discord.Commands.Builders; -using Discord.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,6 +6,9 @@ using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Discord.Commands.Builders; +using Discord.Logging; namespace Discord.Commands { @@ -85,7 +86,8 @@ namespace Discord.Commands var builder = new ModuleBuilder(this, null, primaryAlias); buildFunc(builder); - var module = builder.Build(this); + var module = builder.Build(this, null); + return LoadModuleInternal(module); } finally @@ -93,8 +95,8 @@ namespace Discord.Commands _moduleLock.Release(); } } - public Task AddModuleAsync() => AddModuleAsync(typeof(T)); - public async Task AddModuleAsync(Type type) + public Task AddModuleAsync(IServiceProvider services) => AddModuleAsync(typeof(T), services); + public async Task AddModuleAsync(Type type, IServiceProvider services) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -104,7 +106,7 @@ namespace Discord.Commands if (_typedModuleDefs.ContainsKey(type)) throw new ArgumentException($"This module has already been added."); - var module = (await ModuleClassBuilder.BuildAsync(this, typeInfo).ConfigureAwait(false)).FirstOrDefault(); + var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); if (module.Value == default(ModuleInfo)) throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?"); @@ -118,13 +120,13 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task> AddModulesAsync(Assembly assembly) + public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services) { await _moduleLock.WaitAsync().ConfigureAwait(false); try { var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false); - var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this).ConfigureAwait(false); + var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this, services).ConfigureAwait(false); foreach (var info in moduleDefs) { @@ -224,7 +226,7 @@ namespace Discord.Commands var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary()); var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader); readers[nullableReader.GetType()] = nullableReader; - } + } internal IDictionary GetTypeReaders(Type type) { if (_typeReaders.TryGetValue(type, out var definedTypeReaders)) @@ -277,92 +279,94 @@ namespace Discord.Commands public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { services = services ?? EmptyServiceProvider.Instance; - - var searchResult = Search(context, input); - if (!searchResult.IsSuccess) - return searchResult; - - var commands = searchResult.Commands; - var preconditionResults = new Dictionary(); - - foreach (var match in commands) + using (var scope = services.CreateScope()) { - preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false); - } + var searchResult = Search(context, input); + if (!searchResult.IsSuccess) + return searchResult; - var successfulPreconditions = preconditionResults - .Where(x => x.Value.IsSuccess) - .ToArray(); + var commands = searchResult.Commands; + var preconditionResults = new Dictionary(); - if (successfulPreconditions.Length == 0) - { - //All preconditions failed, return the one from the highest priority command - var bestCandidate = preconditionResults - .OrderByDescending(x => x.Key.Command.Priority) - .FirstOrDefault(x => !x.Value.IsSuccess); - return bestCandidate.Value; - } + foreach (var match in commands) + { + preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, scope.ServiceProvider).ConfigureAwait(false); + } - //If we get this far, at least one precondition was successful. + var successfulPreconditions = preconditionResults + .Where(x => x.Value.IsSuccess) + .ToArray(); - var parseResultsDict = new Dictionary(); - foreach (var pair in successfulPreconditions) - { - var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false); + if (successfulPreconditions.Length == 0) + { + //All preconditions failed, return the one from the highest priority command + var bestCandidate = preconditionResults + .OrderByDescending(x => x.Key.Command.Priority) + .FirstOrDefault(x => !x.Value.IsSuccess); + return bestCandidate.Value; + } - if (parseResult.Error == CommandError.MultipleMatches) + //If we get this far, at least one precondition was successful. + + var parseResultsDict = new Dictionary(); + foreach (var pair in successfulPreconditions) { - IReadOnlyList argList, paramList; - switch (multiMatchHandling) + var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, scope.ServiceProvider).ConfigureAwait(false); + + if (parseResult.Error == CommandError.MultipleMatches) { - case MultiMatchHandling.Best: - argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - parseResult = ParseResult.FromSuccess(argList, paramList); - break; + IReadOnlyList argList, paramList; + switch (multiMatchHandling) + { + case MultiMatchHandling.Best: + argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + parseResult = ParseResult.FromSuccess(argList, paramList); + break; + } } - } - parseResultsDict[pair.Key] = parseResult; - } + parseResultsDict[pair.Key] = parseResult; + } - // Calculates the 'score' of a command given a parse result - float CalculateScore(CommandMatch match, ParseResult parseResult) - { - float argValuesScore = 0, paramValuesScore = 0; - - if (match.Command.Parameters.Count > 0) + // Calculates the 'score' of a command given a parse result + float CalculateScore(CommandMatch match, ParseResult parseResult) { - var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; - var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + float argValuesScore = 0, paramValuesScore = 0; + + if (match.Command.Parameters.Count > 0) + { + var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; - argValuesScore = argValuesSum / match.Command.Parameters.Count; - paramValuesScore = paramValuesSum / match.Command.Parameters.Count; + argValuesScore = argValuesSum / match.Command.Parameters.Count; + paramValuesScore = paramValuesSum / match.Command.Parameters.Count; + } + + var totalArgsScore = (argValuesScore + paramValuesScore) / 2; + return match.Command.Priority + totalArgsScore * 0.99f; } - var totalArgsScore = (argValuesScore + paramValuesScore) / 2; - return match.Command.Priority + totalArgsScore * 0.99f; - } + //Order the parse results by their score so that we choose the most likely result to execute + var parseResults = parseResultsDict + .OrderByDescending(x => CalculateScore(x.Key, x.Value)); - //Order the parse results by their score so that we choose the most likely result to execute - var parseResults = parseResultsDict - .OrderByDescending(x => CalculateScore(x.Key, x.Value)); + var successfulParses = parseResults + .Where(x => x.Value.IsSuccess) + .ToArray(); - var successfulParses = parseResults - .Where(x => x.Value.IsSuccess) - .ToArray(); + if (successfulParses.Length == 0) + { + //All parses failed, return the one from the highest priority command, using score as a tie breaker + var bestMatch = parseResults + .FirstOrDefault(x => !x.Value.IsSuccess); + return bestMatch.Value; + } - if (successfulParses.Length == 0) - { - //All parses failed, return the one from the highest priority command, using score as a tie breaker - var bestMatch = parseResults - .FirstOrDefault(x => !x.Value.IsSuccess); - return bestMatch.Value; + //If we get this far, at least one parse was successful. Execute the most likely overload. + var chosenOverload = successfulParses[0]; + return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, scope.ServiceProvider).ConfigureAwait(false); } - - //If we get this far, at least one parse was successful. Execute the most likely overload. - var chosenOverload = successfulParses[0]; - return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index 7fdbe368b..77c5b2262 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -1,4 +1,6 @@ -namespace Discord.Commands +using System; + +namespace Discord.Commands { public class CommandServiceConfig { @@ -18,5 +20,11 @@ /// Determines whether extra parameters should be ignored. public bool IgnoreExtraArgs { get; set; } = false; + + ///// Gets or sets the to use. + //public IServiceProvider ServiceProvider { get; set; } = null; + + ///// Gets or sets a factory function for the to use. + //public Func ServiceProviderFactory { get; set; } = null; } } diff --git a/src/Discord.Net.Commands/IModuleBase.cs b/src/Discord.Net.Commands/IModuleBase.cs index 479724ae3..3b641ec5f 100644 --- a/src/Discord.Net.Commands/IModuleBase.cs +++ b/src/Discord.Net.Commands/IModuleBase.cs @@ -1,4 +1,6 @@ -namespace Discord.Commands +using Discord.Commands.Builders; + +namespace Discord.Commands { internal interface IModuleBase { @@ -7,5 +9,7 @@ void BeforeExecute(CommandInfo command); void AfterExecute(CommandInfo command); + + void OnModuleBuilding(CommandService commandService, ModuleBuilder builder); } } diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 97b90bf4e..5a7f9208e 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -2,7 +2,7 @@ using System; using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; - +using System.Reflection; using Discord.Commands.Builders; namespace Discord.Commands @@ -13,6 +13,7 @@ namespace Discord.Commands public string Name { get; } public string Summary { get; } public string Remarks { get; } + public string Group { get; } public IReadOnlyList Aliases { get; } public IReadOnlyList Commands { get; } @@ -22,21 +23,26 @@ namespace Discord.Commands public ModuleInfo Parent { get; } public bool IsSubmodule => Parent != null; - internal ModuleInfo(ModuleBuilder builder, CommandService service, ModuleInfo parent = null) + //public TypeInfo TypeInfo { get; } + + internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, ModuleInfo parent = null) { Service = service; Name = builder.Name; Summary = builder.Summary; Remarks = builder.Remarks; + Group = builder.Group; Parent = parent; + //TypeInfo = builder.TypeInfo; + Aliases = BuildAliases(builder, service).ToImmutableArray(); Commands = builder.Commands.Select(x => x.Build(this, service)).ToImmutableArray(); Preconditions = BuildPreconditions(builder).ToImmutableArray(); Attributes = BuildAttributes(builder).ToImmutableArray(); - Submodules = BuildSubmodules(builder, service).ToImmutableArray(); + Submodules = BuildSubmodules(builder, service, services).ToImmutableArray(); } private static IEnumerable BuildAliases(ModuleBuilder builder, CommandService service) @@ -66,12 +72,12 @@ namespace Discord.Commands return result; } - private List BuildSubmodules(ModuleBuilder parent, CommandService service) + private List BuildSubmodules(ModuleBuilder parent, CommandService service, IServiceProvider services) { var result = new List(); foreach (var submodule in parent.Modules) - result.Add(submodule.Build(service, this)); + result.Add(submodule.Build(service, services, this)); return result; } diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index f51656e40..c35a3cf67 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Threading.Tasks; +using Discord.Commands.Builders; namespace Discord.Commands { @@ -23,15 +24,18 @@ namespace Discord.Commands { } + protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder) + { + } + //IModuleBase void IModuleBase.SetContext(ICommandContext context) { var newValue = context as T; Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}"); } - void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command); - void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); + void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder); } } From 500f5f434a0aa7e0eb027c6da80f96f9d4c06123 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sun, 18 Feb 2018 19:19:10 -0500 Subject: [PATCH 05/33] Add request info to HttpException & RateLimitedException (#957) * Add request info to RateLimitedException * Remove Promise from interface. * Add Request to HttpException. --- src/Discord.Net.Core/Net/HttpException.cs | 6 ++++-- src/Discord.Net.Core/Net/IRequest.cs | 10 ++++++++++ src/Discord.Net.Core/Net/RateLimitedException.cs | 7 +++++-- .../Net/Queue/RequestQueueBucket.cs | 14 +++++++------- .../Net/Queue/Requests/RestRequest.cs | 4 ++-- .../Net/Queue/Requests/WebSocketRequest.cs | 4 ++-- 6 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 src/Discord.Net.Core/Net/IRequest.cs diff --git a/src/Discord.Net.Core/Net/HttpException.cs b/src/Discord.Net.Core/Net/HttpException.cs index 1c872245c..d0ee65b23 100644 --- a/src/Discord.Net.Core/Net/HttpException.cs +++ b/src/Discord.Net.Core/Net/HttpException.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; namespace Discord.Net @@ -8,11 +8,13 @@ namespace Discord.Net public HttpStatusCode HttpCode { get; } public int? DiscordCode { get; } public string Reason { get; } + public IRequest Request { get; } - public HttpException(HttpStatusCode httpCode, int? discordCode = null, string reason = null) + public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null) : base(CreateMessage(httpCode, discordCode, reason)) { HttpCode = httpCode; + Request = request; DiscordCode = discordCode; Reason = reason; } diff --git a/src/Discord.Net.Core/Net/IRequest.cs b/src/Discord.Net.Core/Net/IRequest.cs new file mode 100644 index 000000000..d3c708dd5 --- /dev/null +++ b/src/Discord.Net.Core/Net/IRequest.cs @@ -0,0 +1,10 @@ +using System; + +namespace Discord.Net +{ + public interface IRequest + { + DateTimeOffset? TimeoutAt { get; } + RequestOptions Options { get; } + } +} diff --git a/src/Discord.Net.Core/Net/RateLimitedException.cs b/src/Discord.Net.Core/Net/RateLimitedException.cs index e8572f911..2d34d7bc2 100644 --- a/src/Discord.Net.Core/Net/RateLimitedException.cs +++ b/src/Discord.Net.Core/Net/RateLimitedException.cs @@ -1,12 +1,15 @@ -using System; +using System; namespace Discord.Net { public class RateLimitedException : TimeoutException { - public RateLimitedException() + public IRequest Request { get; } + + public RateLimitedException(IRequest request) : base("You are being rate limited.") { + Request = request; } } } diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index 2cc4b8a10..2d96ca796 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; #if DEBUG_LIMITS @@ -86,7 +86,7 @@ namespace Discord.Net.Queue Debug.WriteLine($"[{id}] (!) 502"); #endif if ((request.Options.RetryMode & RetryMode.Retry502) == 0) - throw new HttpException(HttpStatusCode.BadGateway, null); + throw new HttpException(HttpStatusCode.BadGateway, request, null); continue; //Retry default: @@ -106,7 +106,7 @@ namespace Discord.Net.Queue } catch { } } - throw new HttpException(response.StatusCode, code, reason); + throw new HttpException(response.StatusCode, request, code, reason); } } else @@ -163,7 +163,7 @@ namespace Discord.Net.Queue if (!isRateLimited) throw new TimeoutException(); else - throw new RateLimitedException(); + throw new RateLimitedException(request); } lock (_lock) @@ -182,12 +182,12 @@ namespace Discord.Net.Queue } if ((request.Options.RetryMode & RetryMode.RetryRatelimit) == 0) - throw new RateLimitedException(); + throw new RateLimitedException(request); if (resetAt.HasValue) { if (resetAt > timeoutAt) - throw new RateLimitedException(); + throw new RateLimitedException(request); int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)"); @@ -198,7 +198,7 @@ namespace Discord.Net.Queue else { if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0) - throw new RateLimitedException(); + throw new RateLimitedException(request); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)"); #endif diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs index 8f160273a..bb5840ce2 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs @@ -1,11 +1,11 @@ -using Discord.Net.Rest; +using Discord.Net.Rest; using System; using System.IO; using System.Threading.Tasks; namespace Discord.Net.Queue { - public class RestRequest + public class RestRequest : IRequest { public IRestClient Client { get; } public string Method { get; } diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs index 478289b59..81eb40b31 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs @@ -1,4 +1,4 @@ -using Discord.Net.WebSockets; +using Discord.Net.WebSockets; using System; using System.IO; using System.Threading; @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Discord.Net.Queue { - public class WebSocketRequest + public class WebSocketRequest : IRequest { public IWebSocketClient Client { get; } public string BucketId { get; } From 88765970ec80bcce8cf8dc59fd6812f7005dad50 Mon Sep 17 00:00:00 2001 From: Anu6is Date: Thu, 22 Feb 2018 16:02:47 -0500 Subject: [PATCH 06/33] Incorrect variable assignment (#959) The username parameter was being used to set args.AvatarUrl as opposed to the actual avatarUrl parameter provided --- src/Discord.Net.Webhook/WebhookClientHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index f3a3984cf..1116662a6 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -49,7 +49,7 @@ namespace Discord.Webhook if (username != null) args.Username = username; if (avatarUrl != null) - args.AvatarUrl = username; + args.AvatarUrl = avatarUrl; if (embeds != null) args.Embeds = embeds.Select(x => x.ToModel()).ToArray(); var msg = await client.ApiClient.UploadWebhookFileAsync(client.Webhook.Id, args, options).ConfigureAwait(false); From fda19b5a8f05b5aa1b37e4149450864501c49bad Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 24 Feb 2018 22:01:28 +0100 Subject: [PATCH 07/33] [docs] Change 'Echos' to 'Echoes' (#964) --- docs/guides/commands/samples/module.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/commands/samples/module.cs b/docs/guides/commands/samples/module.cs index 5014619da..1e3555501 100644 --- a/docs/guides/commands/samples/module.cs +++ b/docs/guides/commands/samples/module.cs @@ -3,7 +3,7 @@ public class Info : ModuleBase { // ~say hello -> hello [Command("say")] - [Summary("Echos a message.")] + [Summary("Echoes a message.")] public async Task SayAsync([Remainder] [Summary("The text to echo")] string echo) { // ReplyAsync is a method on ModuleBase @@ -38,4 +38,4 @@ public class Sample : ModuleBase var userInfo = user ?? Context.Client.CurrentUser; await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}"); } -} \ No newline at end of file +} From b1eaa44021e334c70fbe08dd9f92baf41968f699 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 26 Feb 2018 19:32:26 -0500 Subject: [PATCH 08/33] Don't attempt to load types with generic parameters as a module This fixes an issue where custom ModuleBases that contained a generic parameter would be loaded as a module - only to fail when trying to be built. Realistically, ModuleBases _should_ be abstract - but it was still a bug that we allowed them to be included as a module. --- src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index c0a7e9aca..cf0f82474 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -307,7 +307,8 @@ namespace Discord.Commands private static bool IsValidModuleDefinition(TypeInfo typeInfo) { return _moduleTypeInfo.IsAssignableFrom(typeInfo) && - !typeInfo.IsAbstract; + !typeInfo.IsAbstract && + !typeInfo.ContainsGenericParameters; } private static bool IsValidCommandDefinition(MethodInfo methodInfo) From 32ebdd51f77e2c6428059663e21585c737a2e53e Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Wed, 28 Feb 2018 22:46:01 +0000 Subject: [PATCH 09/33] Correct impl. of HasFlag and ResolveChannel (#966) HasFlag was checking if any of the flags were set, not the ones specified, and ResolveChannel was still treating the ChannelPermission enum as before it was changed to a bitflag. --- src/Discord.Net.Core/Utils/Permissions.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Core/Utils/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs index 7b92c9d3e..04e6784c3 100644 --- a/src/Discord.Net.Core/Utils/Permissions.cs +++ b/src/Discord.Net.Core/Utils/Permissions.cs @@ -80,7 +80,7 @@ namespace Discord } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool HasFlag(ulong value, ulong flag) => (value & flag) != 0; + private static bool HasFlag(ulong value, ulong flag) => (value & flag) == flag; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetFlag(ref ulong value, ulong flag) => value |= flag; [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -161,10 +161,10 @@ namespace Discord else if (!GetValue(resolvedPermissions, ChannelPermission.SendMessages)) { //No send permissions on a text channel removes all send-related permissions - resolvedPermissions &= ~(1UL << (int)ChannelPermission.SendTTSMessages); - resolvedPermissions &= ~(1UL << (int)ChannelPermission.MentionEveryone); - resolvedPermissions &= ~(1UL << (int)ChannelPermission.EmbedLinks); - resolvedPermissions &= ~(1UL << (int)ChannelPermission.AttachFiles); + resolvedPermissions &= ~(ulong)ChannelPermission.SendTTSMessages; + resolvedPermissions &= ~(ulong)ChannelPermission.MentionEveryone; + resolvedPermissions &= ~(ulong)ChannelPermission.EmbedLinks; + resolvedPermissions &= ~(ulong)ChannelPermission.AttachFiles; } } resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) @@ -173,4 +173,4 @@ namespace Discord return resolvedPermissions; } } -} \ No newline at end of file +} From 63e670464fd9416137d308ac805c0cac7d0aba74 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Thu, 1 Mar 2018 17:06:48 -0800 Subject: [PATCH 10/33] Add more tests for Permissions class (#967) * Add tests for more Permissions code coverage * Add guild tests * Add more in-depth covering of permissions methods * Add tests for OverwritePermissions * Remove unknown ItemGroup tag from csproj * Add missing Fact attributes, separate class so that it is not dependant on main test fixture * Separate out GuildPermissions and ChannelPermissions tests from main partial Tests class because they do not need to have access to the main test fixture --- .../Discord.Net.Tests.csproj | 3 + .../Tests.ChannelPermissions.cs | 4 +- .../Tests.GuildPermissions.cs | 4 +- test/Discord.Net.Tests/Tests.Permissions.cs | 706 ++++++++++++++++++ 4 files changed, 713 insertions(+), 4 deletions(-) create mode 100644 test/Discord.Net.Tests/Tests.Permissions.cs diff --git a/test/Discord.Net.Tests/Discord.Net.Tests.csproj b/test/Discord.Net.Tests/Discord.Net.Tests.csproj index bf2457187..204dca5c4 100644 --- a/test/Discord.Net.Tests/Discord.Net.Tests.csproj +++ b/test/Discord.Net.Tests/Discord.Net.Tests.csproj @@ -10,6 +10,9 @@ PreserveNewest + + + diff --git a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs index ac8ede4e4..b37a1195e 100644 --- a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs +++ b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Threading.Tasks; using Xunit; namespace Discord { - public partial class Tests + public class ChannelPermissionsTests { [Fact] public Task TestChannelPermission() diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs index bb113d221..a562f4afb 100644 --- a/test/Discord.Net.Tests/Tests.GuildPermissions.cs +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Threading.Tasks; using Xunit; namespace Discord { - public partial class Tests + public class GuidPermissionsTests { [Fact] public Task TestGuildPermission() diff --git a/test/Discord.Net.Tests/Tests.Permissions.cs b/test/Discord.Net.Tests/Tests.Permissions.cs new file mode 100644 index 000000000..e22659d15 --- /dev/null +++ b/test/Discord.Net.Tests/Tests.Permissions.cs @@ -0,0 +1,706 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Discord +{ + public class PermissionsTests + { + private void TestHelper(ChannelPermissions value, ChannelPermission permission, bool expected = false) + => TestHelper(value.RawValue, (ulong)permission, expected); + + private void TestHelper(GuildPermissions value, GuildPermission permission, bool expected = false) + => TestHelper(value.RawValue, (ulong)permission, expected); + + /// + /// Tests the flag of the given permissions value to the expected output + /// and then tries to toggle the flag on and off + /// + /// + /// + /// + private void TestHelper(ulong rawValue, ulong flagValue, bool expected) + { + Assert.Equal(expected, Permissions.GetValue(rawValue, flagValue)); + + // check that toggling the bit works + Permissions.UnsetFlag(ref rawValue, flagValue); + Assert.Equal(false, Permissions.GetValue(rawValue, flagValue)); + Permissions.SetFlag(ref rawValue, flagValue); + Assert.Equal(true, Permissions.GetValue(rawValue, flagValue)); + + // do the same, but with the SetValue method + Permissions.SetValue(ref rawValue, true, flagValue); + Assert.Equal(true, Permissions.GetValue(rawValue, flagValue)); + Permissions.SetValue(ref rawValue, false, flagValue); + Assert.Equal(false, Permissions.GetValue(rawValue, flagValue)); + } + + /// + /// Tests that flag of the given permissions value to be the expected output + /// and then tries cycling through the states of the allow and deny values + /// for that flag + /// + /// + /// + /// + private void TestHelper(OverwritePermissions value, ChannelPermission flag, PermValue expected) + { + // check that the value matches + Assert.Equal(expected, Permissions.GetValue(value.AllowValue, value.DenyValue, flag)); + + // check toggling bits for both allow and deny + // have to make copies to get around read only property + ulong allow = value.AllowValue; + ulong deny = value.DenyValue; + + // both unset should be inherit + Permissions.UnsetFlag(ref allow, (ulong)flag); + Permissions.UnsetFlag(ref deny, (ulong)flag); + Assert.Equal(PermValue.Inherit, Permissions.GetValue(allow, deny, flag)); + + // allow set should be allow + Permissions.SetFlag(ref allow, (ulong)flag); + Permissions.UnsetFlag(ref deny, (ulong)flag); + Assert.Equal(PermValue.Allow, Permissions.GetValue(allow, deny, flag)); + + // deny should be deny + Permissions.UnsetFlag(ref allow, (ulong)flag); + Permissions.SetFlag(ref deny, (ulong)flag); + Assert.Equal(PermValue.Deny, Permissions.GetValue(allow, deny, flag)); + + // allow takes precedence + Permissions.SetFlag(ref allow, (ulong)flag); + Permissions.SetFlag(ref deny, (ulong)flag); + Assert.Equal(PermValue.Allow, Permissions.GetValue(allow, deny, flag)); + } + + /// + /// Tests for the class. + /// + /// Tests that text channel permissions get the right value + /// from the Has method. + /// + /// + [Fact] + public Task TestPermissionsHasChannelPermissionText() + { + var value = ChannelPermissions.Text; + // check that the result of GetValue matches for all properties of text channel + TestHelper(value, ChannelPermission.CreateInstantInvite, true); + TestHelper(value, ChannelPermission.ManageChannels, true); + TestHelper(value, ChannelPermission.AddReactions, true); + TestHelper(value, ChannelPermission.ViewChannel, true); + TestHelper(value, ChannelPermission.SendMessages, true); + TestHelper(value, ChannelPermission.SendTTSMessages, true); + TestHelper(value, ChannelPermission.ManageMessages, true); + TestHelper(value, ChannelPermission.EmbedLinks, true); + TestHelper(value, ChannelPermission.AttachFiles, true); + TestHelper(value, ChannelPermission.ReadMessageHistory, true); + TestHelper(value, ChannelPermission.MentionEveryone, true); + TestHelper(value, ChannelPermission.UseExternalEmojis, true); + TestHelper(value, ChannelPermission.ManageRoles, true); + TestHelper(value, ChannelPermission.ManageWebhooks, true); + + TestHelper(value, ChannelPermission.Connect, false); + TestHelper(value, ChannelPermission.Speak, false); + TestHelper(value, ChannelPermission.MuteMembers, false); + TestHelper(value, ChannelPermission.DeafenMembers, false); + TestHelper(value, ChannelPermission.MoveMembers, false); + TestHelper(value, ChannelPermission.UseVAD, false); + + return Task.CompletedTask; + } + + /// + /// Tests for the class. + /// + /// Tests that no channel permissions get the right value + /// from the Has method. + /// + /// + [Fact] + public Task TestPermissionsHasChannelPermissionNone() + { + // check that none will fail all + var value = ChannelPermissions.None; + + TestHelper(value, ChannelPermission.CreateInstantInvite, false); + TestHelper(value, ChannelPermission.ManageChannels, false); + TestHelper(value, ChannelPermission.AddReactions, false); + TestHelper(value, ChannelPermission.ViewChannel, false); + TestHelper(value, ChannelPermission.SendMessages, false); + TestHelper(value, ChannelPermission.SendTTSMessages, false); + TestHelper(value, ChannelPermission.ManageMessages, false); + TestHelper(value, ChannelPermission.EmbedLinks, false); + TestHelper(value, ChannelPermission.AttachFiles, false); + TestHelper(value, ChannelPermission.ReadMessageHistory, false); + TestHelper(value, ChannelPermission.MentionEveryone, false); + TestHelper(value, ChannelPermission.UseExternalEmojis, false); + TestHelper(value, ChannelPermission.ManageRoles, false); + TestHelper(value, ChannelPermission.ManageWebhooks, false); + TestHelper(value, ChannelPermission.Connect, false); + TestHelper(value, ChannelPermission.Speak, false); + TestHelper(value, ChannelPermission.MuteMembers, false); + TestHelper(value, ChannelPermission.DeafenMembers, false); + TestHelper(value, ChannelPermission.MoveMembers, false); + TestHelper(value, ChannelPermission.UseVAD, false); + + return Task.CompletedTask; + } + + /// + /// Tests for the class. + /// + /// Tests that the dm channel permissions get the right value + /// from the Has method. + /// + /// + [Fact] + public Task TestPermissionsHasChannelPermissionDM() + { + // check that none will fail all + var value = ChannelPermissions.DM; + + TestHelper(value, ChannelPermission.CreateInstantInvite, false); + TestHelper(value, ChannelPermission.ManageChannels, false); + TestHelper(value, ChannelPermission.AddReactions, false); + TestHelper(value, ChannelPermission.ViewChannel, true); + TestHelper(value, ChannelPermission.SendMessages, true); + TestHelper(value, ChannelPermission.SendTTSMessages, false); + TestHelper(value, ChannelPermission.ManageMessages, false); + TestHelper(value, ChannelPermission.EmbedLinks, true); + TestHelper(value, ChannelPermission.AttachFiles, true); + TestHelper(value, ChannelPermission.ReadMessageHistory, true); + TestHelper(value, ChannelPermission.MentionEveryone, false); + TestHelper(value, ChannelPermission.UseExternalEmojis, true); + TestHelper(value, ChannelPermission.ManageRoles, false); + TestHelper(value, ChannelPermission.ManageWebhooks, false); + TestHelper(value, ChannelPermission.Connect, true); + TestHelper(value, ChannelPermission.Speak, true); + TestHelper(value, ChannelPermission.MuteMembers, false); + TestHelper(value, ChannelPermission.DeafenMembers, false); + TestHelper(value, ChannelPermission.MoveMembers, false); + TestHelper(value, ChannelPermission.UseVAD, true); + + return Task.CompletedTask; + } + + /// + /// Tests for the class. + /// + /// Tests that the group channel permissions get the right value + /// from the Has method. + /// + /// + [Fact] + public Task TestPermissionsHasChannelPermissionGroup() + { + var value = ChannelPermissions.Group; + + TestHelper(value, ChannelPermission.CreateInstantInvite, false); + TestHelper(value, ChannelPermission.ManageChannels, false); + TestHelper(value, ChannelPermission.AddReactions, false); + TestHelper(value, ChannelPermission.ViewChannel, false); + TestHelper(value, ChannelPermission.SendMessages, true); + TestHelper(value, ChannelPermission.SendTTSMessages, true); + TestHelper(value, ChannelPermission.ManageMessages, false); + TestHelper(value, ChannelPermission.EmbedLinks, true); + TestHelper(value, ChannelPermission.AttachFiles, true); + TestHelper(value, ChannelPermission.ReadMessageHistory, false); + TestHelper(value, ChannelPermission.MentionEveryone, false); + TestHelper(value, ChannelPermission.UseExternalEmojis, false); + TestHelper(value, ChannelPermission.ManageRoles, false); + TestHelper(value, ChannelPermission.ManageWebhooks, false); + TestHelper(value, ChannelPermission.Connect, true); + TestHelper(value, ChannelPermission.Speak, true); + TestHelper(value, ChannelPermission.MuteMembers, false); + TestHelper(value, ChannelPermission.DeafenMembers, false); + TestHelper(value, ChannelPermission.MoveMembers, false); + TestHelper(value, ChannelPermission.UseVAD, true); + + return Task.CompletedTask; + } + + + /// + /// Tests for the class. + /// + /// Tests that the voice channel permissions get the right value + /// from the Has method. + /// + /// + [Fact] + public Task TestPermissionsHasChannelPermissionVoice() + { + // make a flag with all possible values for Voice channel permissions + var value = ChannelPermissions.Voice; + + TestHelper(value, ChannelPermission.CreateInstantInvite, true); + TestHelper(value, ChannelPermission.ManageChannels, true); + TestHelper(value, ChannelPermission.AddReactions, false); + TestHelper(value, ChannelPermission.ViewChannel, false); + TestHelper(value, ChannelPermission.SendMessages, false); + TestHelper(value, ChannelPermission.SendTTSMessages, false); + TestHelper(value, ChannelPermission.ManageMessages, false); + TestHelper(value, ChannelPermission.EmbedLinks, false); + TestHelper(value, ChannelPermission.AttachFiles, false); + TestHelper(value, ChannelPermission.ReadMessageHistory, false); + TestHelper(value, ChannelPermission.MentionEveryone, false); + TestHelper(value, ChannelPermission.UseExternalEmojis, false); + TestHelper(value, ChannelPermission.ManageRoles, true); + TestHelper(value, ChannelPermission.ManageWebhooks, false); + TestHelper(value, ChannelPermission.Connect, true); + TestHelper(value, ChannelPermission.Speak, true); + TestHelper(value, ChannelPermission.MuteMembers, true); + TestHelper(value, ChannelPermission.DeafenMembers, true); + TestHelper(value, ChannelPermission.MoveMembers, true); + TestHelper(value, ChannelPermission.UseVAD, true); + + return Task.CompletedTask; + } + + /// + /// Tests for the class. + /// + /// Test that that the Has method of + /// returns the correct value when no permissions are set. + /// + /// + [Fact] + public Task TestPermissionsHasGuildPermissionNone() + { + var value = GuildPermissions.None; + + TestHelper(value, GuildPermission.CreateInstantInvite, false); + TestHelper(value, GuildPermission.KickMembers, false); + TestHelper(value, GuildPermission.BanMembers, false); + TestHelper(value, GuildPermission.Administrator, false); + TestHelper(value, GuildPermission.ManageChannels, false); + TestHelper(value, GuildPermission.ManageGuild, false); + TestHelper(value, GuildPermission.AddReactions, false); + TestHelper(value, GuildPermission.ViewAuditLog, false); + TestHelper(value, GuildPermission.ReadMessages, false); + TestHelper(value, GuildPermission.SendMessages, false); + TestHelper(value, GuildPermission.SendTTSMessages, false); + TestHelper(value, GuildPermission.ManageMessages, false); + TestHelper(value, GuildPermission.EmbedLinks, false); + TestHelper(value, GuildPermission.AttachFiles, false); + TestHelper(value, GuildPermission.ReadMessageHistory, false); + TestHelper(value, GuildPermission.MentionEveryone, false); + TestHelper(value, GuildPermission.UseExternalEmojis, false); + TestHelper(value, GuildPermission.Connect, false); + TestHelper(value, GuildPermission.Speak, false); + TestHelper(value, GuildPermission.MuteMembers, false); + TestHelper(value, GuildPermission.MoveMembers, false); + TestHelper(value, GuildPermission.UseVAD, false); + TestHelper(value, GuildPermission.ChangeNickname, false); + TestHelper(value, GuildPermission.ManageNicknames, false); + TestHelper(value, GuildPermission.ManageRoles, false); + TestHelper(value, GuildPermission.ManageWebhooks, false); + TestHelper(value, GuildPermission.ManageEmojis, false); + + return Task.CompletedTask; + } + + /// + /// Tests for the class. + /// + /// Test that that the Has method of + /// returns the correct value when all permissions are set. + /// + /// + [Fact] + public Task TestPermissionsHasGuildPermissionAll() + { + var value = GuildPermissions.All; + + TestHelper(value, GuildPermission.CreateInstantInvite, true); + TestHelper(value, GuildPermission.KickMembers, true); + TestHelper(value, GuildPermission.BanMembers, true); + TestHelper(value, GuildPermission.Administrator, true); + TestHelper(value, GuildPermission.ManageChannels, true); + TestHelper(value, GuildPermission.ManageGuild, true); + TestHelper(value, GuildPermission.AddReactions, true); + TestHelper(value, GuildPermission.ViewAuditLog, true); + TestHelper(value, GuildPermission.ReadMessages, true); + TestHelper(value, GuildPermission.SendMessages, true); + TestHelper(value, GuildPermission.SendTTSMessages, true); + TestHelper(value, GuildPermission.ManageMessages, true); + TestHelper(value, GuildPermission.EmbedLinks, true); + TestHelper(value, GuildPermission.AttachFiles, true); + TestHelper(value, GuildPermission.ReadMessageHistory, true); + TestHelper(value, GuildPermission.MentionEveryone, true); + TestHelper(value, GuildPermission.UseExternalEmojis, true); + TestHelper(value, GuildPermission.Connect, true); + TestHelper(value, GuildPermission.Speak, true); + TestHelper(value, GuildPermission.MuteMembers, true); + TestHelper(value, GuildPermission.MoveMembers, true); + TestHelper(value, GuildPermission.UseVAD, true); + TestHelper(value, GuildPermission.ChangeNickname, true); + TestHelper(value, GuildPermission.ManageNicknames, true); + TestHelper(value, GuildPermission.ManageRoles, true); + TestHelper(value, GuildPermission.ManageWebhooks, true); + TestHelper(value, GuildPermission.ManageEmojis, true); + + + return Task.CompletedTask; + } + + /// + /// Tests for the class. + /// + /// Test that that the Has method of + /// returns the correct value when webhook permissions are set. + /// + /// + [Fact] + public Task TestPermissionsHasGuildPermissionWebhook() + { + var value = GuildPermissions.Webhook; + + TestHelper(value, GuildPermission.CreateInstantInvite, false); + TestHelper(value, GuildPermission.KickMembers, false); + TestHelper(value, GuildPermission.BanMembers, false); + TestHelper(value, GuildPermission.Administrator, false); + TestHelper(value, GuildPermission.ManageChannels, false); + TestHelper(value, GuildPermission.ManageGuild, false); + TestHelper(value, GuildPermission.AddReactions, false); + TestHelper(value, GuildPermission.ViewAuditLog, false); + TestHelper(value, GuildPermission.ReadMessages, false); + TestHelper(value, GuildPermission.SendMessages, true); + TestHelper(value, GuildPermission.SendTTSMessages, true); + TestHelper(value, GuildPermission.ManageMessages, false); + TestHelper(value, GuildPermission.EmbedLinks, true); + TestHelper(value, GuildPermission.AttachFiles, true); + TestHelper(value, GuildPermission.ReadMessageHistory, false); + TestHelper(value, GuildPermission.MentionEveryone, false); + TestHelper(value, GuildPermission.UseExternalEmojis, false); + TestHelper(value, GuildPermission.Connect, false); + TestHelper(value, GuildPermission.Speak, false); + TestHelper(value, GuildPermission.MuteMembers, false); + TestHelper(value, GuildPermission.MoveMembers, false); + TestHelper(value, GuildPermission.UseVAD, false); + TestHelper(value, GuildPermission.ChangeNickname, false); + TestHelper(value, GuildPermission.ManageNicknames, false); + TestHelper(value, GuildPermission.ManageRoles, false); + TestHelper(value, GuildPermission.ManageWebhooks, false); + TestHelper(value, GuildPermission.ManageEmojis, false); + + return Task.CompletedTask; + } + + /// + /// Test + /// for when all text permissions are allowed and denied + /// + /// + [Fact] + public Task TestOverwritePermissionsText() + { + // allow all for text channel + var value = new OverwritePermissions(ChannelPermissions.Text.RawValue, ChannelPermissions.None.RawValue); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Allow); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Allow); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Allow); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Allow); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Allow); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Allow); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Allow); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Allow); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Allow); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Allow); + TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); + TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); + + value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Text.RawValue); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Deny); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Deny); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Deny); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Deny); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Deny); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Deny); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Deny); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Deny); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Deny); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Deny); + TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); + TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); + + return Task.CompletedTask; + } + + /// + /// Test + /// for when none of the permissions are set. + /// + /// + [Fact] + public Task TestOverwritePermissionsNone() + { + // allow all for text channel + var value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.None.RawValue); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Inherit); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); + TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); + + value = new OverwritePermissions(); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Inherit); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); + TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); + + value = OverwritePermissions.InheritAll; + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Inherit); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); + TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); + + return Task.CompletedTask; + } + + /// + /// Test + /// for when all dm permissions are allowed and denied + /// + /// + [Fact] + public Task TestOverwritePermissionsDM() + { + // allow all for text channel + var value = new OverwritePermissions(ChannelPermissions.DM.RawValue, ChannelPermissions.None.RawValue); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Allow); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Allow); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Allow); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Allow); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Allow); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Allow); + TestHelper(value, ChannelPermission.Speak, PermValue.Allow); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Allow); + + value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.DM.RawValue); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Deny); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Deny); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Deny); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Deny); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Deny); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Deny); + TestHelper(value, ChannelPermission.Speak, PermValue.Deny); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Deny); + + return Task.CompletedTask; + } + + /// + /// Test + /// for when all group permissions are allowed and denied + /// + /// + [Fact] + public Task TestOverwritePermissionsGroup() + { + // allow all for group channels + var value = new OverwritePermissions(ChannelPermissions.Group.RawValue, ChannelPermissions.None.RawValue); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Allow); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Allow); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Allow); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Allow); + TestHelper(value, ChannelPermission.Speak, PermValue.Allow); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Allow); + + value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Group.RawValue); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Deny); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Deny); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Deny); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Deny); + TestHelper(value, ChannelPermission.Speak, PermValue.Deny); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Deny); + + return Task.CompletedTask; + } + + /// + /// Test + /// for when all group permissions are allowed and denied + /// + /// + [Fact] + public Task TestOverwritePermissionsVoice() + { + // allow all for group channels + var value = new OverwritePermissions(ChannelPermissions.Voice.RawValue, ChannelPermissions.None.RawValue); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Allow); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Inherit); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Allow); + TestHelper(value, ChannelPermission.Speak, PermValue.Allow); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Allow); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Allow); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Allow); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Allow); + + value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Voice.RawValue); + + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Deny); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Inherit); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Deny); + TestHelper(value, ChannelPermission.Speak, PermValue.Deny); + TestHelper(value, ChannelPermission.MuteMembers, PermValue.Deny); + TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Deny); + TestHelper(value, ChannelPermission.MoveMembers, PermValue.Deny); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Deny); + + return Task.CompletedTask; + } + } +} From f19730e43385c2e07b19b9677a67d061b51661be Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 3 Mar 2018 20:36:06 -0500 Subject: [PATCH 11/33] AddModuleAsync/AddModulesAsync should not require an IServiceProvider If one is not passed, they will default to an EmptyServiceProvider This resolves issues where since the merging of OnModuleBuilding, users were effectively forced to use the DI pattern, where before they were not. While this isn't necessarily a bad idea, we shouldn't just change this behavior for no reason (though I assume making the IServiceProvider argument required was a mistake) --- src/Discord.Net.Commands/CommandService.cs | 10 +++++++--- src/Discord.Net.Commands/Utilities/ReflectionUtils.cs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 7efc1bc62..d79a3a03b 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -95,9 +95,11 @@ namespace Discord.Commands _moduleLock.Release(); } } - public Task AddModuleAsync(IServiceProvider services) => AddModuleAsync(typeof(T), services); - public async Task AddModuleAsync(Type type, IServiceProvider services) + public Task AddModuleAsync(IServiceProvider services = null) => AddModuleAsync(typeof(T), services); + public async Task AddModuleAsync(Type type, IServiceProvider services = null) { + services = services ?? EmptyServiceProvider.Instance; + await _moduleLock.WaitAsync().ConfigureAwait(false); try { @@ -120,8 +122,10 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services) + public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services = null) { + services = services ?? EmptyServiceProvider.Instance; + await _moduleLock.WaitAsync().ConfigureAwait(false); try { diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index ab88f66ae..30dd7c36b 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; From 4edbd8d4b9cc46a48dbf02ad4bbd04fccc67ea27 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sun, 4 Mar 2018 13:15:00 -0500 Subject: [PATCH 12/33] Allow nested ModuleBase classes to be built when declared from non-module classes. (#969) * Allow modules to be built regardless of their declaring type. * Need to exclude submodules. --- src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index cf0f82474..996706a7c 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -48,8 +48,7 @@ namespace Discord.Commands /*if (!validTypes.Any()) throw new InvalidOperationException("Could not find any valid modules from the given selection");*/ - var topLevelGroups = validTypes.Where(x => x.DeclaringType == null); - var subGroups = validTypes.Intersect(topLevelGroups); + var topLevelGroups = validTypes.Where(x => x.DeclaringType == null || !IsValidModuleDefinition(x.DeclaringType.GetTypeInfo())); var builtTypes = new List(); From 8537924d9b48f4a5942fff2c8d9595456e15fb5b Mon Sep 17 00:00:00 2001 From: Michael Flaherty Date: Sun, 4 Mar 2018 10:24:09 -0800 Subject: [PATCH 13/33] Add Missing Parameter For WebSocket4Net Constructor (#968) * Add missing param to constructor This should fix `15:02:44 Gateway System.MissingMethodException: Method not found: 'Void WebSocket4Net.WebSocket..ctor(System.String, System.String, System.Collections.Generic.List`1>, System.Collections.Generic.List`1>, System.String, System.String, WebSocket4Net.WebSocketVersion, System.Net.EndPoint)'.` * Bump WS4N to latest stable --- .../Discord.Net.Providers.WS4Net.csproj | 2 +- src/Discord.Net.Providers.WS4Net/WS4NetClient.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj b/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj index 78987e739..bfd0983ce 100644 --- a/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj +++ b/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj @@ -10,6 +10,6 @@ - + diff --git a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs index 93d6a83d6..1894a8906 100644 --- a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs +++ b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs @@ -66,7 +66,7 @@ namespace Discord.Net.Providers.WS4Net _cancelTokenSource = new CancellationTokenSource(); _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; - _client = new WS4NetSocket(host, customHeaderItems: _headers.ToList()) + _client = new WS4NetSocket(host, "", customHeaderItems: _headers.ToList()) { EnableAutoSendPing = false, NoDelay = true, @@ -163,4 +163,4 @@ namespace Discord.Net.Providers.WS4Net Closed(ex).GetAwaiter().GetResult(); } } -} \ No newline at end of file +} From 170a2e00bdba9bdb2370040ee6f3015d05a11a0a Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Thu, 8 Mar 2018 22:09:34 +0100 Subject: [PATCH 14/33] Resolve #936 (#941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Resolve #936 * Remember to not search *only* typereaders for primitives 😒 --- .../Builders/ModuleClassBuilder.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 996706a7c..e9ce9eb86 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -274,15 +274,8 @@ namespace Discord.Commands if (builder.TypeReader == null) { - var readers = service.GetTypeReaders(paramType); - TypeReader reader = null; - - if (readers != null) - reader = readers.FirstOrDefault().Value; - else - reader = service.GetDefaultTypeReader(paramType); - - builder.TypeReader = reader; + builder.TypeReader = service.GetDefaultTypeReader(paramType) + ?? service.GetTypeReaders(paramType)?.FirstOrDefault().Value; } } From 2fd4f5670edf9e7db72bb71f19efd1e1502b015e Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 12 Mar 2018 18:59:22 +0100 Subject: [PATCH 15/33] Remove support for TokenType.User (#958) * Set usage of TokenType.User as an error rather than a warning. * Remove commented sections and #pragma's Additionally, changes use of ReadMessages to ViewChannel since that Obsolete was also suppressed by the pragma --- .../Preconditions/RequireOwnerAttribute.cs | 5 ----- src/Discord.Net.Core/TokenType.cs | 4 ++-- src/Discord.Net.Rest/DiscordRestApiClient.cs | 22 +++++-------------- .../DiscordSocketClient.cs | 8 +------ .../Entities/Guilds/SocketGuild.cs | 12 ++++------ 5 files changed, 13 insertions(+), 38 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs index 7a8a009be..93e3cbe18 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS0618 using System; using System.Threading.Tasks; @@ -20,10 +19,6 @@ namespace Discord.Commands if (context.User.Id != application.Owner.Id) return PreconditionResult.FromError("Command can only be run by the owner of the bot"); return PreconditionResult.FromSuccess(); - case TokenType.User: - if (context.User.Id != context.Client.CurrentUser.Id) - return PreconditionResult.FromError("Command can only be run by the owner of the bot"); - return PreconditionResult.FromSuccess(); default: return PreconditionResult.FromError($"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}."); } diff --git a/src/Discord.Net.Core/TokenType.cs b/src/Discord.Net.Core/TokenType.cs index c351b1c19..62181420a 100644 --- a/src/Discord.Net.Core/TokenType.cs +++ b/src/Discord.Net.Core/TokenType.cs @@ -1,10 +1,10 @@ -using System; +using System; namespace Discord { public enum TokenType { - [Obsolete("User logins are being deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827")] + [Obsolete("User logins are deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827", error: true)] User, Bearer, Bot, diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 689cba9c3..c93ed628b 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable CS0618 using Discord.API.Rest; using Discord.Net; using Discord.Net.Converters; @@ -74,8 +73,6 @@ namespace Discord.API return $"Bot {token}"; case TokenType.Bearer: return $"Bearer {token}"; - case TokenType.User: - return token; default: throw new ArgumentException("Unknown OAuth token type", nameof(tokenType)); } @@ -113,7 +110,6 @@ namespace Discord.API { _loginCancelToken = new CancellationTokenSource(); - AuthTokenType = TokenType.User; AuthToken = null; await RequestQueue.SetCancelTokenAsync(_loginCancelToken.Token).ConfigureAwait(false); RestClient.SetCancelToken(_loginCancelToken.Token); @@ -172,8 +168,7 @@ namespace Discord.API { options = options ?? new RequestOptions(); options.HeaderOnly = true; - options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; - options.IsClientBucket = AuthTokenType == TokenType.User; + options.BucketId = bucketId; var request = new RestRequest(RestClient, method, endpoint, options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); @@ -187,8 +182,7 @@ namespace Discord.API { options = options ?? new RequestOptions(); options.HeaderOnly = true; - options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; - options.IsClientBucket = AuthTokenType == TokenType.User; + options.BucketId = bucketId; string json = payload != null ? SerializeJson(payload) : null; var request = new JsonRestRequest(RestClient, method, endpoint, json, options); @@ -203,8 +197,7 @@ namespace Discord.API { options = options ?? new RequestOptions(); options.HeaderOnly = true; - options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; - options.IsClientBucket = AuthTokenType == TokenType.User; + options.BucketId = bucketId; var request = new MultipartRestRequest(RestClient, method, endpoint, multipartArgs, options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); @@ -217,8 +210,7 @@ namespace Discord.API string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class { options = options ?? new RequestOptions(); - options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; - options.IsClientBucket = AuthTokenType == TokenType.User; + options.BucketId = bucketId; var request = new RestRequest(RestClient, method, endpoint, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); @@ -231,8 +223,7 @@ namespace Discord.API string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class { options = options ?? new RequestOptions(); - options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; - options.IsClientBucket = AuthTokenType == TokenType.User; + options.BucketId = bucketId; string json = payload != null ? SerializeJson(payload) : null; var request = new JsonRestRequest(RestClient, method, endpoint, json, options); @@ -246,8 +237,7 @@ namespace Discord.API string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { options = options ?? new RequestOptions(); - options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; - options.IsClientBucket = AuthTokenType == TokenType.User; + options.BucketId = bucketId; var request = new MultipartRestRequest(RestClient, method, endpoint, multipartArgs, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 142f24417..593f796c2 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS0618 using Discord.API; using Discord.API.Gateway; using Discord.Logging; @@ -446,7 +445,7 @@ namespace Discord.WebSocket { var model = data.Guilds[i]; var guild = AddGuild(model, state); - if (!guild.IsAvailable || ApiClient.AuthTokenType == TokenType.User) + if (!guild.IsAvailable) unavailableGuilds++; else await GuildAvailableAsync(guild).ConfigureAwait(false); @@ -465,9 +464,6 @@ namespace Discord.WebSocket return; } - if (ApiClient.AuthTokenType == TokenType.User) - await SyncGuildsAsync().ConfigureAwait(false); - _lastGuildAvailableTime = Environment.TickCount; _guildDownloadTask = WaitForGuildsAsync(_connection.CancelToken, _gatewayLogger) .ContinueWith(async x => @@ -542,8 +538,6 @@ namespace Discord.WebSocket var guild = AddGuild(data, State); if (guild != null) { - if (ApiClient.AuthTokenType == TokenType.User) - await SyncGuildsAsync().ConfigureAwait(false); await TimedInvokeAsync(_joinedGuildEvent, nameof(JoinedGuild), guild).ConfigureAwait(false); } else diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index e70df8ce8..259dae5a8 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS0618 using Discord.Audio; using Discord.Rest; using System; @@ -64,7 +63,7 @@ namespace Discord.WebSocket public Task DownloaderPromise => _downloaderPromise.Task; public IAudioClient AudioClient => _audioClient; public SocketTextChannel DefaultChannel => TextChannels - .Where(c => CurrentUser.GetPermissions(c).ReadMessages) + .Where(c => CurrentUser.GetPermissions(c).ViewChannel) .OrderBy(c => c.Position) .FirstOrDefault(); public SocketVoiceChannel AFKChannel @@ -192,12 +191,9 @@ namespace Discord.WebSocket _syncPromise = new TaskCompletionSource(); _downloaderPromise = new TaskCompletionSource(); - if (Discord.ApiClient.AuthTokenType != TokenType.User) - { - var _ = _syncPromise.TrySetResultAsync(true); - /*if (!model.Large) - _ = _downloaderPromise.TrySetResultAsync(true);*/ - } + var _ = _syncPromise.TrySetResultAsync(true); + /*if (!model.Large) + _ = _downloaderPromise.TrySetResultAsync(true);*/ } internal void Update(ClientState state, Model model) { From e68ef63bc6ffb70eb4b43cfe9197e4ac20fac7b0 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 12 Mar 2018 20:46:49 -0400 Subject: [PATCH 16/33] Allow GetPrefixedToken to handle the default TokenType This prevents an issue where no clients could be constructed. In 2fd4f56, the case for user tokens was removed from GetPrefixedToken, which means that the default value for TokenType would now fallthrough to the default case, which throws an error. --- src/Discord.Net.Rest/DiscordRestApiClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index c93ed628b..c2176ca60 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -69,6 +69,8 @@ namespace Discord.API { switch (tokenType) { + case default(TokenType): + return token; case TokenType.Bot: return $"Bot {token}"; case TokenType.Bearer: From f9ac190e9a54b24196f67155d0f856d24760ff8e Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 13 Mar 2018 18:23:42 -0400 Subject: [PATCH 17/33] Don't create a service scope in CommandService#ExecuteAsync anymore IServiceProvider does not support scopes by itself - this is a behavior introduced by Microsoft's DI container. As such, not all DI containers may support an IScopeFactory the way that Microsoft's DI is expecting them to. This also means that our builtin EmptyServiceProvider does not support scopes - meaning that users who do not use a DI container can not invoke commands. --- src/Discord.Net.Commands/CommandService.cs | 134 ++++++++++----------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index d79a3a03b..de990ab47 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -283,94 +283,92 @@ namespace Discord.Commands public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { services = services ?? EmptyServiceProvider.Instance; - using (var scope = services.CreateScope()) - { - var searchResult = Search(context, input); - if (!searchResult.IsSuccess) - return searchResult; - var commands = searchResult.Commands; - var preconditionResults = new Dictionary(); + var searchResult = Search(context, input); + if (!searchResult.IsSuccess) + return searchResult; - foreach (var match in commands) - { - preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, scope.ServiceProvider).ConfigureAwait(false); - } + var commands = searchResult.Commands; + var preconditionResults = new Dictionary(); - var successfulPreconditions = preconditionResults - .Where(x => x.Value.IsSuccess) - .ToArray(); + foreach (var match in commands) + { + preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false); + } - if (successfulPreconditions.Length == 0) - { - //All preconditions failed, return the one from the highest priority command - var bestCandidate = preconditionResults - .OrderByDescending(x => x.Key.Command.Priority) - .FirstOrDefault(x => !x.Value.IsSuccess); - return bestCandidate.Value; - } + var successfulPreconditions = preconditionResults + .Where(x => x.Value.IsSuccess) + .ToArray(); - //If we get this far, at least one precondition was successful. + if (successfulPreconditions.Length == 0) + { + //All preconditions failed, return the one from the highest priority command + var bestCandidate = preconditionResults + .OrderByDescending(x => x.Key.Command.Priority) + .FirstOrDefault(x => !x.Value.IsSuccess); + return bestCandidate.Value; + } - var parseResultsDict = new Dictionary(); - foreach (var pair in successfulPreconditions) - { - var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, scope.ServiceProvider).ConfigureAwait(false); + //If we get this far, at least one precondition was successful. - if (parseResult.Error == CommandError.MultipleMatches) + var parseResultsDict = new Dictionary(); + foreach (var pair in successfulPreconditions) + { + var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false); + + if (parseResult.Error == CommandError.MultipleMatches) + { + IReadOnlyList argList, paramList; + switch (multiMatchHandling) { - IReadOnlyList argList, paramList; - switch (multiMatchHandling) - { - case MultiMatchHandling.Best: - argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - parseResult = ParseResult.FromSuccess(argList, paramList); - break; - } + case MultiMatchHandling.Best: + argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + parseResult = ParseResult.FromSuccess(argList, paramList); + break; } - - parseResultsDict[pair.Key] = parseResult; } - // Calculates the 'score' of a command given a parse result - float CalculateScore(CommandMatch match, ParseResult parseResult) - { - float argValuesScore = 0, paramValuesScore = 0; + parseResultsDict[pair.Key] = parseResult; + } - if (match.Command.Parameters.Count > 0) - { - var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; - var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + // Calculates the 'score' of a command given a parse result + float CalculateScore(CommandMatch match, ParseResult parseResult) + { + float argValuesScore = 0, paramValuesScore = 0; - argValuesScore = argValuesSum / match.Command.Parameters.Count; - paramValuesScore = paramValuesSum / match.Command.Parameters.Count; - } + if (match.Command.Parameters.Count > 0) + { + var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; - var totalArgsScore = (argValuesScore + paramValuesScore) / 2; - return match.Command.Priority + totalArgsScore * 0.99f; + argValuesScore = argValuesSum / match.Command.Parameters.Count; + paramValuesScore = paramValuesSum / match.Command.Parameters.Count; } - //Order the parse results by their score so that we choose the most likely result to execute - var parseResults = parseResultsDict - .OrderByDescending(x => CalculateScore(x.Key, x.Value)); + var totalArgsScore = (argValuesScore + paramValuesScore) / 2; + return match.Command.Priority + totalArgsScore * 0.99f; + } - var successfulParses = parseResults - .Where(x => x.Value.IsSuccess) - .ToArray(); + //Order the parse results by their score so that we choose the most likely result to execute + var parseResults = parseResultsDict + .OrderByDescending(x => CalculateScore(x.Key, x.Value)); - if (successfulParses.Length == 0) - { - //All parses failed, return the one from the highest priority command, using score as a tie breaker - var bestMatch = parseResults - .FirstOrDefault(x => !x.Value.IsSuccess); - return bestMatch.Value; - } + var successfulParses = parseResults + .Where(x => x.Value.IsSuccess) + .ToArray(); - //If we get this far, at least one parse was successful. Execute the most likely overload. - var chosenOverload = successfulParses[0]; - return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, scope.ServiceProvider).ConfigureAwait(false); + if (successfulParses.Length == 0) + { + //All parses failed, return the one from the highest priority command, using score as a tie breaker + var bestMatch = parseResults + .FirstOrDefault(x => !x.Value.IsSuccess); + return bestMatch.Value; } + + //If we get this far, at least one parse was successful. Execute the most likely overload. + var chosenOverload = successfulParses[0]; + return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false); } } } From e9f9b484b660bc682239c8de2afb3efd84f60ff1 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 13 Mar 2018 18:28:49 -0400 Subject: [PATCH 18/33] Allow attaching embeds alongside a file upload. (#978) commit a8bafb90cd1c5ea12abaa1aa01d0929833c999a8 Merge: f38dd4c4 7e04285e Author: WamWooWam Date: Mon Mar 12 08:05:52 2018 +0000 Merge branch 'dev' of https://github.com/WamWooWam/Discord.Net into dev commit f38dd4c42149380f3f7f86c21c86cb76d0f104b7 Author: WamWooWam Date: Mon Mar 12 08:05:49 2018 +0000 Cleaned up & fixed code style. commit 7e04285e5dcc0102c9b958e75155834ca35724b9 Author: Thomas May Date: Sun Mar 11 14:11:28 2018 +0000 Revert changes to DefaultRestClient Didn't actually need to change this, whoops. commit 3f5b2c8ef16d356e7b9588c4b36af03162ab7089 Author: WamWooWam Date: Sat Mar 10 19:30:44 2018 +0000 Enabled embeds alongside uploaded files. God damn Discord is a mess. Co-authored-by: WamWooWam --- .../Entities/Channels/IMessageChannel.cs | 6 ++--- .../Extensions/UserExtensions.cs | 22 +++++++++------- .../API/Rest/UploadFileParams.cs | 22 +++++++++++++++- .../Entities/Channels/ChannelHelper.cs | 10 +++---- .../Entities/Channels/IRestMessageChannel.cs | 6 ++--- .../Entities/Channels/RestDMChannel.cs | 20 +++++++------- .../Entities/Channels/RestGroupChannel.cs | 20 +++++++------- .../Entities/Channels/RestTextChannel.cs | 24 ++++++++--------- .../Channels/RpcVirtualMessageChannel.cs | 20 +++++++------- src/Discord.Net.Rest/Net/DefaultRestClient.cs | 3 ++- .../Channels/ISocketMessageChannel.cs | 6 ++--- .../Entities/Channels/SocketDMChannel.cs | 20 +++++++------- .../Entities/Channels/SocketGroupChannel.cs | 20 +++++++------- .../Entities/Channels/SocketTextChannel.cs | 26 +++++++++---------- 14 files changed, 124 insertions(+), 101 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index a1df5b4c7..5a6e5df59 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -11,10 +11,10 @@ namespace Discord Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); #if FILESYSTEM /// Sends a file to this text channel, with an optional caption. - Task SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); + Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); #endif /// Sends a file to this text channel, with an optional caption. - Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, RequestOptions options = null); + Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); /// Gets a message from this message channel with the given id, or null if not found. Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index eac25391e..863201cfe 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using System.IO; namespace Discord @@ -8,10 +8,10 @@ namespace Discord /// /// Sends a message to the user via DM. /// - public static async Task SendMessageAsync(this IUser user, - string text, + public static async Task SendMessageAsync(this IUser user, + string text, bool isTTS = false, - Embed embed = null, + Embed embed = null, RequestOptions options = null) { return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); @@ -25,23 +25,25 @@ namespace Discord string filename, string text = null, bool isTTS = false, + Embed embed = null, RequestOptions options = null ) { - return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); + return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); } #if FILESYSTEM /// /// Sends a file to the user via DM. /// - public static async Task SendFileAsync(this IUser user, - string filePath, - string text = null, - bool isTTS = false, + public static async Task SendFileAsync(this IUser user, + string filePath, + string text = null, + bool isTTS = false, + Embed embed = null, RequestOptions options = null) { - return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); + return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); } #endif } diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs index 30bfc7f9a..d6e629b5d 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs @@ -1,18 +1,25 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 +using Discord.Net.Converters; using Discord.Net.Rest; +using Newtonsoft.Json; using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Text; namespace Discord.API.Rest { internal class UploadFileParams { + private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + public Stream File { get; } public Optional Filename { get; set; } public Optional Content { get; set; } public Optional Nonce { get; set; } public Optional IsTTS { get; set; } + public Optional Embed { get; set; } public UploadFileParams(Stream file) { @@ -29,6 +36,19 @@ namespace Discord.API.Rest d["tts"] = IsTTS.Value.ToString(); if (Nonce.IsSpecified) d["nonce"] = Nonce.Value; + if (Embed.IsSpecified) + { + var sb = new StringBuilder(256); + using (TextWriter text = new StringWriter(sb, CultureInfo.InvariantCulture)) + using (JsonWriter writer = new JsonTextWriter(text)) + { + Dictionary dictionary = new Dictionary(); + dictionary["embed"] = Embed.Value; + + _serializer.Serialize(writer, dictionary); + } + d["payload_json"] = sb.ToString(); + } return d; } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index f4b6c7f23..710746896 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -1,4 +1,4 @@ -using Discord.API.Rest; +using Discord.API.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -170,17 +170,17 @@ namespace Discord.Rest #if FILESYSTEM public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, - string filePath, string text, bool isTTS, RequestOptions options) + string filePath, string text, bool isTTS, Embed embed, RequestOptions options) { string filename = Path.GetFileName(filePath); using (var file = File.OpenRead(filePath)) - return await SendFileAsync(channel, client, file, filename, text, isTTS, options).ConfigureAwait(false); + return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options).ConfigureAwait(false); } #endif public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, - Stream stream, string filename, string text, bool isTTS, RequestOptions options) + Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) { - var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; + var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() }; var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); } diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index 3a104dd9c..8ab6e9819 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -10,10 +10,10 @@ namespace Discord.Rest new Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); #if FILESYSTEM /// Sends a file to this text channel, with an optional caption. - new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); #endif /// Sends a file to this text channel, with an optional caption. - new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, RequestOptions options = null); + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); /// Gets a message from this message channel with the given id, or null if not found. Task GetMessageAsync(ulong id, RequestOptions options = null); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index d41441967..08acdf32b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -37,7 +37,7 @@ namespace Discord.Rest public override async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); - Update(model); + Update(model); } public Task CloseAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); @@ -66,11 +66,11 @@ namespace Discord.Rest public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM - public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); #endif - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); @@ -122,11 +122,11 @@ namespace Discord.Rest => await GetPinnedMessagesAsync(options).ConfigureAwait(false); #if FILESYSTEM - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); #endif - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index aa5c0f7dc..a1868573e 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -1,4 +1,4 @@ -using Discord.Audio; +using Discord.Audio; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -19,7 +19,7 @@ namespace Discord.Rest public string Name { get; private set; } public IReadOnlyCollection Users => _users.ToReadOnlyCollection(); - public IReadOnlyCollection Recipients + public IReadOnlyCollection Recipients => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); internal RestGroupChannel(BaseDiscordClient discord, ulong id) @@ -79,11 +79,11 @@ namespace Discord.Rest public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM - public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); #endif - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); @@ -132,11 +132,11 @@ namespace Discord.Rest => await GetPinnedMessagesAsync(options).ConfigureAwait(false); #if FILESYSTEM - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); #endif - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 9c29624c1..600b197d6 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -61,11 +61,11 @@ namespace Discord.Rest public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM - public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); #endif - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); @@ -123,18 +123,18 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } - async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); #if FILESYSTEM - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); #endif - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); - IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); //IGuildChannel diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index eb807423f..3b6a68193 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -21,7 +21,7 @@ namespace Discord.Rest { return new RestVirtualMessageChannel(discord, id); } - + public Task GetMessageAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) @@ -36,11 +36,11 @@ namespace Discord.Rest public Task SendMessageAsync(string text, bool isTTS, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM - public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); #endif - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); @@ -82,11 +82,11 @@ namespace Discord.Rest => await GetPinnedMessagesAsync(options); #if FILESYSTEM - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, embed, options); #endif - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, embed, options); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index 637099fd6..ec789be59 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.Net.Converters; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Globalization; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index e2119e7a2..026bd8378 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -14,10 +14,10 @@ namespace Discord.WebSocket new Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); #if FILESYSTEM /// Sends a file to this text channel, with an optional caption. - new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); #endif /// Sends a file to this text channel, with an optional caption. - new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, RequestOptions options = null); + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); SocketMessage GetCachedMessage(ulong id); /// Gets the last N messages from this message channel. diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 00cef60f8..451240e66 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -70,11 +70,11 @@ namespace Discord.WebSocket public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM - public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); #endif - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); @@ -113,7 +113,7 @@ namespace Discord.WebSocket //IPrivateChannel IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); - + //IMessageChannel async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -131,11 +131,11 @@ namespace Discord.WebSocket async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); #if FILESYSTEM - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); #endif - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 92a93a903..d46c5fc59 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -1,4 +1,4 @@ -using Discord.Audio; +using Discord.Audio; using Discord.Rest; using System; using System.Collections.Concurrent; @@ -61,7 +61,7 @@ namespace Discord.WebSocket users[models[i].Id] = SocketGroupUser.Create(this, state, models[i]); _users = users; } - + public Task LeaveAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); @@ -98,11 +98,11 @@ namespace Discord.WebSocket public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM - public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); #endif - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); @@ -195,11 +195,11 @@ namespace Discord.WebSocket async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); #if FILESYSTEM - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); #endif - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 7b8f572d2..ec7615b55 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -16,7 +16,7 @@ namespace Discord.WebSocket private readonly MessageCache _messages; public string Topic { get; private set; } - + private bool _nsfw; public bool IsNsfw => _nsfw || ChannelHelper.IsNsfw(this); @@ -24,9 +24,9 @@ namespace Discord.WebSocket public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); public override IReadOnlyCollection Users => Guild.Users.Where(x => Permissions.GetValue( - Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), + Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), ChannelPermission.ViewChannel)).ToImmutableArray(); - + internal SocketTextChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) { @@ -78,11 +78,11 @@ namespace Discord.WebSocket public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM - public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); #endif - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); @@ -155,14 +155,14 @@ namespace Discord.WebSocket async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); #if FILESYSTEM - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); #endif - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); } -} \ No newline at end of file +} From f175dde2b3197201392e2356baa7fbaabb0928a3 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 13 Mar 2018 19:16:12 -0400 Subject: [PATCH 19/33] Clean embed serialization up slightly --- .../API/Rest/UploadFileParams.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs index d6e629b5d..5c06a033e 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs @@ -38,16 +38,19 @@ namespace Discord.API.Rest d["nonce"] = Nonce.Value; if (Embed.IsSpecified) { - var sb = new StringBuilder(256); - using (TextWriter text = new StringWriter(sb, CultureInfo.InvariantCulture)) - using (JsonWriter writer = new JsonTextWriter(text)) + var payload = new StringBuilder(); + using (var text = new StringWriter(payload)) + using (var writer = new JsonTextWriter(text)) { - Dictionary dictionary = new Dictionary(); - dictionary["embed"] = Embed.Value; + var map = new Dictionary() + { + ["embed"] = Embed.Value, + }; - _serializer.Serialize(writer, dictionary); + _serializer.Serialize(writer, map); } - d["payload_json"] = sb.ToString(); + d["payload_json"] = payload.ToString(); + } return d; } From fc5e70c9dde57cde7f9321414f62bd9281909d20 Mon Sep 17 00:00:00 2001 From: Darnell Williams Date: Thu, 15 Mar 2018 18:49:25 -0400 Subject: [PATCH 20/33] Attempts to resolve #961 (#962) * Move REST requests to appropiate class * Add call to ClientHelper and expose to public API * Expose shard count request in public api * Expose method from interface * Update sharded client to utilize the new method * Method is already implemented in a base class * Refactor name to fit pattern for methods returning a `Task` * Adds missing ConfigureAwait * Corrects unnecessary whitespace * Removes unneeded whitespace --- src/Discord.Net.Core/IDiscordClient.cs | 2 ++ src/Discord.Net.Rest/BaseDiscordClient.cs | 6 +++++- src/Discord.Net.Rest/ClientHelper.cs | 8 +++++++- src/Discord.Net.Rest/DiscordRestApiClient.cs | 12 ++++++++++++ src/Discord.Net.Rest/DiscordRestClient.cs | 2 +- src/Discord.Net.WebSocket/BaseSocketClient.cs | 2 +- src/Discord.Net.WebSocket/DiscordShardedClient.cs | 6 +++--- .../DiscordSocketApiClient.cs | 15 ++------------- 8 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index 9abb959b5..a383c37da 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -36,5 +36,7 @@ namespace Discord Task GetVoiceRegionAsync(string id, RequestOptions options = null); Task GetWebhookAsync(ulong id, RequestOptions options = null); + + Task GetRecommendedShardCountAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 269dedd71..f8642b96c 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -1,4 +1,4 @@ -using Discord.Logging; +using Discord.Logging; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -125,6 +125,10 @@ namespace Discord.Rest /// public void Dispose() => Dispose(true); + /// + public Task GetRecommendedShardCountAsync(RequestOptions options = null) + => ClientHelper.GetRecommendShardCountAsync(this, options); + //IDiscordClient ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; ISelfUser IDiscordClient.CurrentUser => CurrentUser; diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 5c9e26433..08305f857 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -1,4 +1,4 @@ -using Discord.API.Rest; +using Discord.API.Rest; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; @@ -163,5 +163,11 @@ namespace Discord.Rest var models = await client.ApiClient.GetVoiceRegionsAsync(options).ConfigureAwait(false); return models.Select(x => RestVoiceRegion.Create(client, x)).FirstOrDefault(x => x.Id == id); } + + public static async Task GetRecommendShardCountAsync(BaseDiscordClient client, RequestOptions options) + { + var response = await client.ApiClient.GetBotGatewayAsync(options).ConfigureAwait(false); + return response.Shards; + } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index c2176ca60..556d6fbe6 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -269,6 +269,18 @@ namespace Discord.API await SendAsync("GET", () => "auth/login", new BucketIds(), options: options).ConfigureAwait(false); } + //Gateway + public async Task GetGatewayAsync(RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + return await SendAsync("GET", () => "gateway", new BucketIds(), options: options).ConfigureAwait(false); + } + public async Task GetBotGatewayAsync(RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + return await SendAsync("GET", () => "gateway/bot", new BucketIds(), options: options).ConfigureAwait(false); + } + //Channels public async Task GetChannelAsync(ulong channelId, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 3d90b6c00..8850da3a5 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Threading.Tasks; diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 5fa3cbff8..923b2c23b 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Discord.API; diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index fb78a201f..ad89067df 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -1,4 +1,4 @@ -using Discord.API; +using Discord.API; using Discord.Rest; using System; using System.Collections.Generic; @@ -75,8 +75,8 @@ namespace Discord.WebSocket { if (_automaticShards) { - var response = await ApiClient.GetBotGatewayAsync().ConfigureAwait(false); - _shardIds = Enumerable.Range(0, response.Shards).ToArray(); + var shardCount = await GetRecommendedShardCountAsync().ConfigureAwait(false); + _shardIds = Enumerable.Range(0, shardCount).ToArray(); _totalShards = _shardIds.Length; _shards = new DiscordSocketClient[_shardIds.Length]; for (int i = 0; i < _shardIds.Length; i++) diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 72781204c..8ae41cc59 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Discord.API.Gateway; using Discord.API.Rest; using Discord.Net.Queue; @@ -207,18 +207,7 @@ namespace Discord.API await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, bytes, true, options)).ConfigureAwait(false); await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); } - - //Gateway - public async Task GetGatewayAsync(RequestOptions options = null) - { - options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", () => "gateway", new BucketIds(), options: options).ConfigureAwait(false); - } - public async Task GetBotGatewayAsync(RequestOptions options = null) - { - options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", () => "gateway/bot", new BucketIds(), options: options).ConfigureAwait(false); - } + public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); From b70ae4128599b6c7f3fa9890d6765e5c8fd505e5 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 18 Mar 2018 15:29:23 -0400 Subject: [PATCH 21/33] AddModule(s)Async should be explicit about IServiceProvider In f19730e4, AddModule(s)Async was changed so that the IServiceProvider was optional, both at compile time and runtime. This had the side effect of meaning that there was no longer a compile-time hint that users would need to pass an IServiceProvider to AddModulesAsync. I assumed this would not be an issue - users would recognize the runtime exception here and self correct - but activity in our Discord support channel would indicate otherwise. We now require the user to explicitly opt-out of dependency injection - they are still free to pass null in place of an IServiceProvider if they do not intend to use one, and the library will handle this at runtime. --- src/Discord.Net.Commands/CommandService.cs | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index de990ab47..f4fbcf8b2 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -95,8 +95,15 @@ namespace Discord.Commands _moduleLock.Release(); } } - public Task AddModuleAsync(IServiceProvider services = null) => AddModuleAsync(typeof(T), services); - public async Task AddModuleAsync(Type type, IServiceProvider services = null) + + /// + /// Add a command module from a type + /// + /// The type of module + /// An IServiceProvider for your dependency injection solution, if using one - otherwise, pass null + /// A built module + public Task AddModuleAsync(IServiceProvider services) => AddModuleAsync(typeof(T), services); + public async Task AddModuleAsync(Type type, IServiceProvider services) { services = services ?? EmptyServiceProvider.Instance; @@ -122,7 +129,13 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services = null) + /// + /// Add command modules from an assembly + /// + /// The assembly containing command modules + /// An IServiceProvider for your dependency injection solution, if using one - otherwise, pass null + /// A collection of built modules + public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services) { services = services ?? EmptyServiceProvider.Instance; @@ -278,9 +291,9 @@ namespace Discord.Commands return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } - public Task ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public Task ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) => ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling); - public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { services = services ?? EmptyServiceProvider.Instance; From b38dca7803806a46f51395b2c0673500eda25755 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 18 Mar 2018 15:48:34 -0400 Subject: [PATCH 22/33] All arguments in ReplyAsync should be optional To reply with just a rich embed, users have to invoke ReplyAsync with `ReplyAsync("", embed: embed)`, which seems wasteful, when they only need to specify the embed. --- src/Discord.Net.Commands/ModuleBase.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index c35a3cf67..3e6fbbd9b 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -11,7 +11,13 @@ namespace Discord.Commands { public T Context { get; private set; } - protected virtual async Task ReplyAsync(string message, bool isTTS = false, Embed embed = null, RequestOptions options = null) + /// + /// Sends a message to the source channel + /// + /// Contents of the message; optional only if is specified + /// Specifies if Discord should read this message aloud using TTS + /// An embed to be displayed alongside the message + protected virtual async Task ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) { return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); } From b9be6deb4f07adca13d33f23cfc9c8d21f0297e9 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 18 Mar 2018 15:59:45 -0400 Subject: [PATCH 23/33] Move EmbedBuilder+Extensions to Discord.Net.Core Previously it was implemented under Discord.Net.Rest, which seems inconsistent and unnecessary. This also allows Commands docstrings to reference EmbedBuilder, since Commands only has a dependency on Core. --- .../Entities/Messages/EmbedBuilder.cs | 0 .../Extensions/EmbedBuilderExtensions.cs | 0 src/Discord.Net.Rest/AssemblyInfo.cs | 7 +++++-- 3 files changed, 5 insertions(+), 2 deletions(-) rename src/{Discord.Net.Rest => Discord.Net.Core}/Entities/Messages/EmbedBuilder.cs (100%) rename src/{Discord.Net.Rest => Discord.Net.Core}/Extensions/EmbedBuilderExtensions.cs (100%) diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs similarity index 100% rename from src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs rename to src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs diff --git a/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs b/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs similarity index 100% rename from src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs rename to src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs diff --git a/src/Discord.Net.Rest/AssemblyInfo.cs b/src/Discord.Net.Rest/AssemblyInfo.cs index a4f045ab5..126365e47 100644 --- a/src/Discord.Net.Rest/AssemblyInfo.cs +++ b/src/Discord.Net.Rest/AssemblyInfo.cs @@ -1,7 +1,10 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Discord.Net.Rpc")] [assembly: InternalsVisibleTo("Discord.Net.WebSocket")] [assembly: InternalsVisibleTo("Discord.Net.Webhook")] [assembly: InternalsVisibleTo("Discord.Net.Commands")] -[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Discord.Net.Tests")] + +[assembly: TypeForwardedTo(typeof(Discord.EmbedBuilder))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedBuilderExtensions))] From 64b9cc7a538195040cd60779b68c0d11b77ca3c0 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Mon, 19 Mar 2018 04:06:53 +0800 Subject: [PATCH 24/33] Add Spotify track support (#970) * Initial Spotify support * Remove GameAsset#ToEntity - appId doesn't seem to be necessary, and Spotify Game doesn't return appId either. * Implement SpotifyGame details * Implement song Duration prop * Add album art CDN * Fix ActivityType * Remove payload debug * Add changes according to review + Make `ApplicationId` nullable + Move ctor after props --- .../Entities/Activities/Game.cs | 2 +- .../Entities/Activities/GameAsset.cs | 8 +++---- .../Entities/Activities/RichGame.cs | 8 +++---- .../Entities/Activities/SpotifyGame.cs | 23 +++++++++++++++++++ src/Discord.Net.Rest/API/Common/Game.cs | 6 ++++- .../Extensions/EntityExtensions.cs | 23 ++++++++++++++++++- 6 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs diff --git a/src/Discord.Net.Core/Entities/Activities/Game.cs b/src/Discord.Net.Core/Entities/Activities/Game.cs index fe32470ee..179ad4eaa 100644 --- a/src/Discord.Net.Core/Entities/Activities/Game.cs +++ b/src/Discord.Net.Core/Entities/Activities/Game.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Discord { diff --git a/src/Discord.Net.Core/Entities/Activities/GameAsset.cs b/src/Discord.Net.Core/Entities/Activities/GameAsset.cs index 385f37214..02c29ba41 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameAsset.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameAsset.cs @@ -1,15 +1,15 @@ -namespace Discord +namespace Discord { public class GameAsset { internal GameAsset() { } - internal ulong ApplicationId { get; set; } + internal ulong? ApplicationId { get; set; } public string Text { get; internal set; } public string ImageId { get; internal set; } public string GetImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) - => CDN.GetRichAssetUrl(ApplicationId, ImageId, size, format); + => ApplicationId.HasValue ? CDN.GetRichAssetUrl(ApplicationId.Value, ImageId, size, format) : null; } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Activities/RichGame.cs b/src/Discord.Net.Core/Entities/Activities/RichGame.cs index e66eac1d2..fc3f68cf0 100644 --- a/src/Discord.Net.Core/Entities/Activities/RichGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/RichGame.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Discord { @@ -7,8 +7,8 @@ namespace Discord { internal RichGame() { } - public string Details { get; internal set;} - public string State { get; internal set;} + public string Details { get; internal set; } + public string State { get; internal set; } public ulong ApplicationId { get; internal set; } public GameAsset SmallAsset { get; internal set; } public GameAsset LargeAsset { get; internal set; } @@ -19,4 +19,4 @@ namespace Discord public override string ToString() => Name; private string DebuggerDisplay => $"{Name} (Rich)"; } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs new file mode 100644 index 000000000..b8a4b8043 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Discord +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SpotifyGame : Game + { + public string[] Artists { get; internal set; } + public string AlbumArt { get; internal set; } + public string AlbumTitle { get; internal set; } + public string TrackTitle { get; internal set; } + public string SyncId { get; internal set; } + public string SessionId { get; internal set; } + public TimeSpan? Duration { get; internal set; } + + internal SpotifyGame() { } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} (Spotify)"; + } +} diff --git a/src/Discord.Net.Rest/API/Common/Game.cs b/src/Discord.Net.Rest/API/Common/Game.cs index 29e0d987d..4cde8444a 100644 --- a/src/Discord.Net.Rest/API/Common/Game.cs +++ b/src/Discord.Net.Rest/API/Common/Game.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System.Runtime.Serialization; @@ -29,6 +29,10 @@ namespace Discord.API public Optional Timestamps { get; set; } [JsonProperty("instance")] public Optional Instance { get; set; } + [JsonProperty("sync_id")] + public Optional SyncId { get; set; } + [JsonProperty("session_id")] + public Optional SessionId { get; set; } [OnError] internal void OnError(StreamingContext context, ErrorContext errorContext) diff --git a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs index 181a837e4..f268a7ff2 100644 --- a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs @@ -4,6 +4,27 @@ namespace Discord.WebSocket { public static IActivity ToEntity(this API.Game model) { + // Spotify Game + if (model.SyncId.IsSpecified) + { + var assets = model.Assets.GetValueOrDefault()?.ToEntity(); + string albumText = assets?[1]?.Text; + string albumArtId = assets?[1]?.ImageId?.Replace("spotify:",""); + var timestamps = model.Timestamps.IsSpecified ? model.Timestamps.Value.ToEntity() : null; + return new SpotifyGame + { + Name = model.Name, + SessionId = model.SessionId.GetValueOrDefault(), + SyncId = model.SyncId.Value, + AlbumTitle = albumText, + TrackTitle = model.Details.GetValueOrDefault(), + Artists = model.State.GetValueOrDefault()?.Split(';'), + Duration = timestamps?.End - timestamps?.Start, + AlbumArt = albumArtId != null ? $"https://i.scdn.co/image/{albumArtId}" : null, + Type = ActivityType.Listening + }; + } + // Rich Game if (model.ApplicationId.IsSpecified) { @@ -34,7 +55,7 @@ namespace Discord.WebSocket } // (Small, Large) - public static GameAsset[] ToEntity(this API.GameAssets model, ulong appId) + public static GameAsset[] ToEntity(this API.GameAssets model, ulong? appId = null) { return new GameAsset[] { From 02c650773d657a8a9d0ee95d8c5d4af7d6157418 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 18 Mar 2018 16:18:40 -0400 Subject: [PATCH 25/33] Clean up SpotifyGame PR - Add a helper under CDN for cover art URLs It would be bad practice of us to leave CDN urls hardcoded in the deserializer, would be harder to change down the line should Spotify ever change their CDN. I'm not entirely supportive of leaving Spotify's CDN hardcoded in our lib either, but there's no better alternative. - Change SpotifyGame#Artists to an IEnumerable Seems pretty common to prefer IEnumerables in place of Arrays. --- src/Discord.Net.Core/CDN.cs | 5 ++++- src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs | 2 +- src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index 070b965ee..f23f55238 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord { @@ -28,6 +28,9 @@ namespace Discord return $"{DiscordConfig.CDNUrl}app-assets/{appId}/{assetId}.{extension}?size={size}"; } + public static string GetSpotifyAlbumArtUrl(string albumArtId) + => $"https://i.scdn.co/image/{albumArtId}"; + private static string FormatToExtension(ImageFormat format, string imageId) { if (format == ImageFormat.Auto) diff --git a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs index b8a4b8043..a20384242 100644 --- a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs @@ -7,7 +7,7 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SpotifyGame : Game { - public string[] Artists { get; internal set; } + public IEnumerable Artists { get; internal set; } public string AlbumArt { get; internal set; } public string AlbumTitle { get; internal set; } public string TrackTitle { get; internal set; } diff --git a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs index f268a7ff2..ff395a932 100644 --- a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs @@ -20,7 +20,7 @@ namespace Discord.WebSocket TrackTitle = model.Details.GetValueOrDefault(), Artists = model.State.GetValueOrDefault()?.Split(';'), Duration = timestamps?.End - timestamps?.Start, - AlbumArt = albumArtId != null ? $"https://i.scdn.co/image/{albumArtId}" : null, + AlbumArt = albumArtId != null ? CDN.GetSpotifyAlbumArtUrl(albumArtId) : null, Type = ActivityType.Listening }; } From bfaa6fc97a392867fd7c5251cdf6e396196b5f46 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 18 Mar 2018 16:22:27 -0400 Subject: [PATCH 26/33] Enforce a maximum value when parsing unix timestamps (#981) * UnixTimestampConverter should now obey a maximum value This change prevents an issue where the converter would be unable to handle obscenely large timestamp values - which are actually quite common on Discord. OptionalConverter had to be rewritten to support checking whether or not an InnerConverter returned an Optional. The perf impacts from this _shouldn't_ be too bad, as types without a custom parser (which should be the majority of Optionals in the lib) will bypass the type-check. * optimizations on OptionalConverter --- .../Net/Converters/OptionalConverter.cs | 12 ++++++++++-- .../Net/Converters/UnixTimestampConverter.cs | 17 +++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs b/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs index 18b2a9e1c..d3d6191c0 100644 --- a/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System; namespace Discord.Net.Converters @@ -19,10 +19,18 @@ namespace Discord.Net.Converters public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { T obj; + // custom converters need to be able to safely fail; move this check in here to prevent wasteful casting when parsing primitives if (_innerConverter != null) - obj = (T)_innerConverter.ReadJson(reader, typeof(T), null, serializer); + { + object o = _innerConverter.ReadJson(reader, typeof(T), null, serializer); + if (o is Optional) + return o; + + obj = (T)o; + } else obj = serializer.Deserialize(reader); + return new Optional(obj); } diff --git a/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs b/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs index d4660dc44..0b50cb166 100644 --- a/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using Newtonsoft.Json; namespace Discord.Net.Converters @@ -11,13 +11,18 @@ namespace Discord.Net.Converters public override bool CanRead => true; public override bool CanWrite => true; + // 1e13 unix ms = year 2286 + // necessary to prevent discord.js from sending values in the e15 and overflowing a DTO + private const long MaxSaneMs = 1_000_000_000_000_0; + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - // Discord doesn't validate if timestamps contain decimals or not - if (reader.Value is double d) + // Discord doesn't validate if timestamps contain decimals or not, and they also don't validate if timestamps are reasonably sized + if (reader.Value is double d && d < MaxSaneMs) return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(d); - long offset = (long)reader.Value; - return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(offset); + else if (reader.Value is long l && l < MaxSaneMs) + return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(l); + return Optional.Unspecified; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) @@ -25,4 +30,4 @@ namespace Discord.Net.Converters throw new NotImplementedException(); } } -} \ No newline at end of file +} From ac5ecd365d05281c68ec574d8b13d9e145f5af50 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 19 Mar 2018 16:29:51 -0400 Subject: [PATCH 27/33] Include the content in `payload_json` for file uploads This resolves #987 Previous behavior was that even if `null` was passed for an embed in UploadFileAsync, the Embed property on UploadFileArgs was still specified - this meant we were always sending a payload_json. If a payload_json is specified, it seems like Discord will only read from the payload_json, and will ignore properties set outside of it. To prevent unnecessary code duplication, this commit always specifies parameters in the payload_json, and also will only include the embed if one was actually specified with real data (not null). --- .../API/Rest/UploadFileParams.cs | 32 ++++++++----------- .../Entities/Channels/ChannelHelper.cs | 2 +- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs index 5c06a033e..9e909b50c 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs @@ -30,28 +30,24 @@ namespace Discord.API.Rest { var d = new Dictionary(); d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat")); + + var payload = new Dictionary(); if (Content.IsSpecified) - d["content"] = Content.Value; + payload["content"] = Content.Value; if (IsTTS.IsSpecified) - d["tts"] = IsTTS.Value.ToString(); + payload["tts"] = IsTTS.Value.ToString(); if (Nonce.IsSpecified) - d["nonce"] = Nonce.Value; + payload["nonce"] = Nonce.Value; if (Embed.IsSpecified) - { - var payload = new StringBuilder(); - using (var text = new StringWriter(payload)) - using (var writer = new JsonTextWriter(text)) - { - var map = new Dictionary() - { - ["embed"] = Embed.Value, - }; - - _serializer.Serialize(writer, map); - } - d["payload_json"] = payload.ToString(); - - } + payload["embed"] = Embed.Value; + + var json = new StringBuilder(); + using (var text = new StringWriter(json)) + using (var writer = new JsonTextWriter(text)) + _serializer.Serialize(writer, payload); + + d["payload_json"] = json.ToString(); + return d; } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 710746896..6784f7f6a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -180,7 +180,7 @@ namespace Discord.Rest public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) { - var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() }; + var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed != null ? embed.ToModel() : Optional.Unspecified }; var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); } From 1905fdec04152e1fd63e26ebf7707a1d11a4d0f3 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 20 Mar 2018 16:44:30 -0400 Subject: [PATCH 28/33] Add BanAsync extension to IGuildUser --- src/Discord.Net.Core/Extensions/UserExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index 863201cfe..d3e968e39 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -46,5 +46,8 @@ namespace Discord return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); } #endif + + public static Task BanAsync(this IGuildUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) + => user.Guild.AddBanAsync(user, pruneDays, reason, options); } } From 55299ff14f4af6e9f046b2a33e78e32eb0523ade Mon Sep 17 00:00:00 2001 From: Quahu Date: Sat, 24 Mar 2018 00:49:45 +0100 Subject: [PATCH 29/33] Prevents NREs when sending/modifying messages (#993) --- src/Discord.Net.Rest/DiscordRestApiClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 556d6fbe6..f0c4358ad 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -470,7 +470,7 @@ namespace Discord.API if (!args.Embed.IsSpecified || args.Embed.Value == null) Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); - if (args.Content.Length > DiscordConfig.MaxMessageSize) + if (args.Content?.Length > DiscordConfig.MaxMessageSize) throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); options = RequestOptions.CreateOrClone(options); @@ -487,7 +487,7 @@ namespace Discord.API if (!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); - if (args.Content.Length > DiscordConfig.MaxMessageSize) + if (args.Content?.Length > DiscordConfig.MaxMessageSize) throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); options = RequestOptions.CreateOrClone(options); @@ -568,7 +568,7 @@ namespace Discord.API { if (!args.Embed.IsSpecified) Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); - if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) + 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)); } options = RequestOptions.CreateOrClone(options); From 88e62440758c6a5d498be0a55180a77d9cd01bdd Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Sat, 24 Mar 2018 09:28:50 -0700 Subject: [PATCH 30/33] Add release version to docs footer, Add doc build instructions (#963) * Add guide for building the docs * Add version to the footer of the docs * fix links for readme * change formatting of doc build readme * proper capitalization of DocFX in readme * Remove code tags around version --- docs/README.md | 16 ++++++++++++++++ docs/docfx.json | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..a672330d4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,16 @@ +# Instructions for Building Documentation + +The documentation for the Discord.NET library uses [DocFX][docfx-main]. [Instructions for installing this tool can be found here.][docfx-installing] + +1. Navigate to the root of the repository. +2. (Optional) If you intend to target a specific version, ensure that you +have the correct version checked out. +3. Build the library. Run `dotnet build` in the root of this repository. + Ensure that the build passes without errors. +4. Build the docs using `docfx .\docs\docfx.json`. Add the `--serve` parameter +to preview the site locally. Some elements of the page may appear incorrect +when not hosted by a server. + - Remarks: According to the docfx website, this tool does work on Linux under mono. + +[docfx-main]: https://dotnet.github.io/docfx/ +[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html diff --git a/docs/docfx.json b/docs/docfx.json index 3c0b0611e..50ae39092 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -67,8 +67,8 @@ "default" ], "globalMetadata": { - "_appFooter": "Discord.Net (c) 2015-2017" + "_appFooter": "Discord.Net (c) 2015-2018 2.0.0-beta" }, "noLangKeyword": false } -} \ No newline at end of file +} From 6d58796f2dae251afbbc6175354d27ed69118cfb Mon Sep 17 00:00:00 2001 From: advorange Date: Sat, 24 Mar 2018 10:11:43 -0700 Subject: [PATCH 31/33] Added in an all value for category channels. (#952) --- .../Entities/Permissions/ChannelPermissions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 1a8aad53c..ef10ee106 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; @@ -13,6 +13,8 @@ namespace Discord public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); /// Gets a ChannelPermissions that grants all permissions for voice channels. public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001); + /// Gets a ChannelPermissions that grants all permissions for category channels. + public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); /// Gets a ChannelPermissions that grants all permissions for direct message channels. public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000); /// Gets a ChannelPermissions that grants all permissions for group channels. @@ -24,6 +26,7 @@ namespace Discord { case ITextChannel _: return Text; case IVoiceChannel _: return Voice; + case ICategoryChannel _: return Category; case IDMChannel _: return DM; case IGroupChannel _: return Group; default: throw new ArgumentException("Unknown channel type", nameof(channel)); @@ -157,4 +160,4 @@ namespace Discord public override string ToString() => RawValue.ToString(); private string DebuggerDisplay => $"{string.Join(", ", ToList())}"; } -} \ No newline at end of file +} From 810f6d610eb2f27db777a1e408a7954efb2cc1a3 Mon Sep 17 00:00:00 2001 From: Paulo Date: Sat, 24 Mar 2018 14:11:55 -0300 Subject: [PATCH 32/33] Fix SocketCategoryChannel properties (#945) * Update Users property for category channels * Wrong property being used for Channels property CategoryId is the category that "owns" this channel. That is actually impossible right now for category channels, so it returns null and get all channels wrongly. * Resolve permissions for category * Remove spaces * Small fix for IChannel.GetUsersAsync --- .../Channels/SocketCategoryChannel.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs index d5a183b1e..e7a165c2f 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -15,10 +15,12 @@ namespace Discord.WebSocket public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel { public override IReadOnlyCollection Users - => Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray(); + => Guild.Users.Where(x => Permissions.GetValue( + Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), + ChannelPermission.ViewChannel)).ToImmutableArray(); public IReadOnlyCollection Channels - => Guild.Channels.Where(x => x.CategoryId == CategoryId).ToImmutableArray(); + => Guild.Channels.Where(x => x.CategoryId == Id).ToImmutableArray(); internal SocketCategoryChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) @@ -31,14 +33,28 @@ namespace Discord.WebSocket return entity; } + //Users + public override SocketGuildUser GetUser(ulong id) + { + var user = Guild.GetUser(id); + if (user != null) + { + var guildPerms = Permissions.ResolveGuild(Guild, user); + var channelPerms = Permissions.ResolveChannel(Guild, user, this, guildPerms); + if (Permissions.GetValue(channelPerms, ChannelPermission.ViewChannel)) + return user; + } + return null; + } + private string DebuggerDisplay => $"{Name} ({Id}, Category)"; internal new SocketCategoryChannel Clone() => MemberwiseClone() as SocketCategoryChannel; // IGuildChannel IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => throw new NotSupportedException(); + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => throw new NotSupportedException(); + => Task.FromResult(GetUser(id)); Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) => throw new NotSupportedException(); Task> IGuildChannel.GetInvitesAsync(RequestOptions options) @@ -46,8 +62,8 @@ namespace Discord.WebSocket //IChannel IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => throw new NotSupportedException(); + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => throw new NotSupportedException(); + => Task.FromResult(GetUser(id)); } } From d50fc3b4e1ebc42762d085a92adfb34e118ee0ee Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Sat, 24 Mar 2018 18:12:34 +0100 Subject: [PATCH 33/33] Throw when attempting to modify a message not made by the current user (#992) * Throw when attempting to modify a message not made by the current user * Didn't realize the client is passed into the MessageHelper function * Respond to feedback --- src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs | 5 ++++- src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs | 2 +- .../Entities/Messages/SocketUserMessage.cs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 47bb6f926..8ae41cc37 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -1,4 +1,4 @@ -using Discord.API.Rest; +using Discord.API.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -13,6 +13,9 @@ namespace Discord.Rest public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, RequestOptions options) { + if (msg.Author.Id != client.CurrentUser.Id) + throw new InvalidOperationException("Only the author of a message may change it."); + var args = new MessageProperties(); func(args); var apiArgs = new API.Rest.ModifyMessageParams diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index e5eed874e..0d1f3be2b 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index b240645e5..5489ad2bb 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable;