From 8f59d4423fc1001520015d5ebe127b59eeb8cfb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20M=C3=B6ller?= Date: Thu, 4 May 2017 18:29:04 +0200 Subject: [PATCH 01/62] Fixed exemple calling old non existing function. --- docs/guides/commands/samples/command_handler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/commands/samples/command_handler.cs b/docs/guides/commands/samples/command_handler.cs index 71869415b..9adfcc71d 100644 --- a/docs/guides/commands/samples/command_handler.cs +++ b/docs/guides/commands/samples/command_handler.cs @@ -24,7 +24,7 @@ public class Program await InstallCommands(); await client.LoginAsync(TokenType.Bot, token); - await client.ConnectAsync(); + await client.StartAsync(); await Task.Delay(-1); } From a1a90ae46e02ec09d96896da83800a043404c4bf Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 6 May 2017 11:33:33 +0200 Subject: [PATCH 02/62] Update the example precondition to use IServiceProvider --- docs/guides/commands/samples/require_owner.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/guides/commands/samples/require_owner.cs b/docs/guides/commands/samples/require_owner.cs index 137446553..3611afab8 100644 --- a/docs/guides/commands/samples/require_owner.cs +++ b/docs/guides/commands/samples/require_owner.cs @@ -2,16 +2,18 @@ using Discord.Commands; using Discord.WebSocket; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Threading.Tasks; // Inherit from PreconditionAttribute public class RequireOwnerAttribute : PreconditionAttribute { // Override the CheckPermissions method - public async override Task CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map) + public async override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) { // Get the ID of the bot's owner - var ownerId = (await map.Get().GetApplicationInfoAsync()).Owner.Id; + var ownerId = (await services.GetService().GetApplicationInfoAsync()).Owner.Id; // If this command was executed by that user, return a success if (context.User.Id == ownerId) return PreconditionResult.FromSuccess(); From a92c27da3bb119691a2ba094cd6c79a0a2b5a264 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 11 May 2017 18:01:39 +0200 Subject: [PATCH 03/62] Remove wrong parameter from FFMPEG audio example This parameter was samples per frame but changed to bitrate. (1920 is a way to low bitrate :) ) --- docs/guides/voice/samples/audio_ffmpeg.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/voice/samples/audio_ffmpeg.cs b/docs/guides/voice/samples/audio_ffmpeg.cs index 716ec3d6c..b9430ac11 100644 --- a/docs/guides/voice/samples/audio_ffmpeg.cs +++ b/docs/guides/voice/samples/audio_ffmpeg.cs @@ -3,7 +3,7 @@ private async Task SendAsync(IAudioClient client, string path) // Create FFmpeg using the previous example var ffmpeg = CreateStream(path); var output = ffmpeg.StandardOutput.BaseStream; - var discord = client.CreatePCMStream(AudioApplication.Mixed, 1920); + var discord = client.CreatePCMStream(AudioApplication.Mixed); await output.CopyToAsync(discord); await discord.FlushAsync(); } From 6fed78025c8c2cfe76917cd3078f9e5312fb1b53 Mon Sep 17 00:00:00 2001 From: AntiTcb Date: Tue, 16 May 2017 20:02:32 -0400 Subject: [PATCH 04/62] Create DM channel if one does not exist. --- src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 1b599bf7e..7575309cb 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -66,8 +66,8 @@ namespace Discord.WebSocket internal SocketUser Clone() => MemberwiseClone() as SocketUser; //IUser - Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(GlobalUser.DMChannel); + async Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) + => await Task.FromResult(GlobalUser.DMChannel ?? await CreateDMChannelAsync(options) as IDMChannel); async Task IUser.CreateDMChannelAsync(RequestOptions options) => await CreateDMChannelAsync(options).ConfigureAwait(false); } From aeef5d08935544c10233c8741eca03ea1be67a11 Mon Sep 17 00:00:00 2001 From: AntiTcb Date: Tue, 16 May 2017 20:03:17 -0400 Subject: [PATCH 05/62] Update DM channel on entity updates. --- src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 0cd5f749e..3117eb14c 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Linq; using Model = Discord.API.User; using PresenceModel = Discord.API.Presence; @@ -51,6 +52,7 @@ namespace Discord.WebSocket internal void Update(ClientState state, PresenceModel model) { Presence = SocketPresence.Create(model); + DMChannel = state.DMChannels.FirstOrDefault(x => x.Recipient.Id == Id); } internal new SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser; From 33a91ba3dee4b1dd68ebd895b5408584a22ec398 Mon Sep 17 00:00:00 2001 From: AntiTcb Date: Tue, 16 May 2017 20:03:38 -0400 Subject: [PATCH 06/62] Remove redundant explicit interface definition. --- src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index b92559a40..05aa132a5 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -146,11 +146,7 @@ namespace Discord.WebSocket IGuild IGuildUser.Guild => Guild; ulong IGuildUser.GuildId => Guild.Id; IReadOnlyCollection IGuildUser.RoleIds => _roleIds; - - //IUser - Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(GlobalUser.DMChannel); - + //IVoiceState IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; } From 7db38f32bb481d790362ea6a5bc998045373113a Mon Sep 17 00:00:00 2001 From: AntiTcb Date: Tue, 16 May 2017 20:04:25 -0400 Subject: [PATCH 07/62] Attach/Remove DMChannel to SocketGlobalUser.DMChannel property --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 4476b78c4..d42df7b55 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1634,6 +1634,9 @@ namespace Discord.WebSocket { var channel = SocketChannel.CreatePrivate(this, state, model); state.AddChannel(channel as SocketChannel); + if (channel is SocketDMChannel dm) + dm.Recipient.GlobalUser.DMChannel = dm; + return channel; } internal ISocketPrivateChannel RemovePrivateChannel(ulong id) @@ -1641,6 +1644,9 @@ namespace Discord.WebSocket var channel = State.RemoveChannel(id) as ISocketPrivateChannel; if (channel != null) { + if (channel is SocketDMChannel dmChannel) + dmChannel.Recipient.GlobalUser.DMChannel = null; + foreach (var recipient in channel.Recipients) recipient.GlobalUser.RemoveRef(this); } From 3fb661a33abb36ac6a59e72e66797959181ca82b Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 21 May 2017 14:55:47 -0400 Subject: [PATCH 08/62] fix docs compile issue --- docs/guides/migrating/migrating.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/migrating/migrating.md b/docs/guides/migrating/migrating.md index 8f96dff98..bc628a5f8 100644 --- a/docs/guides/migrating/migrating.md +++ b/docs/guides/migrating/migrating.md @@ -42,7 +42,7 @@ events are delegates, but are still registered the same. For example, let's look at [DiscordSocketClient.MessageReceived](xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_MessageReceived) To hook an event into MessageReceived, we now use the following code: -[!code-csharp[Event Registration](guides/samples/migrating/event.cs)] +[!code-csharp[Event Registration](samples/event.cs)] > **All Event Handlers in 1.0 MUST return Task!** @@ -50,7 +50,7 @@ If your event handler is marked as `async`, it will automatically return `Task`. if you do not need to execute asynchronus code, do _not_ mark your handler as `async`, and instead, stick a `return Task.CompletedTask` at the bottom. -[!code-csharp[Sync Event Registration](guides/samples/migrating/sync_event.cs)] +[!code-csharp[Sync Event Registration](samples/sync_event.cs)] **Event handlers no longer require a sender.** The only arguments your event handler needs to accept are the parameters used by the event. It is recommended to look at the event in IntelliSense or on the From 892eca39fd90c43825a58bb4c080be762fe631c4 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 24 May 2017 21:05:06 +0200 Subject: [PATCH 09/62] Update cmd docs to use IServiceProvider --- docs/guides/commands/commands.md | 21 +++++++------ .../commands/samples/command_handler.cs | 30 +++++++++++-------- .../commands/samples/dependency_map_setup.cs | 9 +++--- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/docs/guides/commands/commands.md b/docs/guides/commands/commands.md index 6dd595861..e021b1eb3 100644 --- a/docs/guides/commands/commands.md +++ b/docs/guides/commands/commands.md @@ -45,7 +45,7 @@ Discord.Net's implementation of Modules is influenced heavily from ASP.Net Core's Controller pattern. This means that the lifetime of a module instance is only as long as the command being invoked. -**Avoid using long-running code** in your modules whereever possible. +**Avoid using long-running code** in your modules wherever possible. You should **not** be implementing very much logic into your modules; outsource to a service for that. @@ -167,8 +167,8 @@ a dependency map. Modules are constructed using Dependency Injection. Any parameters that are placed in the constructor must be injected into an -@Discord.Commands.IDependencyMap. Alternatively, you may accept an -IDependencyMap as an argument and extract services yourself. +@System.IServiceProvider. Alternatively, you may accept an +IServiceProvider as an argument and extract services yourself. ### Module Properties @@ -205,21 +205,20 @@ you use DI when writing your modules. ### Setup -First, you need to create an @Discord.Commands.IDependencyMap. -The library includes @Discord.Commands.DependencyMap to help with -this, however you may create your own IDependencyMap if you wish. +First, you need to create an @System.IServiceProvider +You may create your own IServiceProvider if you wish. Next, add the dependencies your modules will use to the map. Finally, pass the map into the `LoadAssembly` method. Your modules will automatically be loaded with this dependency map. -[!code-csharp[DependencyMap Setup](samples/dependency_map_setup.cs)] +[!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] ### Usage in Modules In the constructor of your module, any parameters will be filled in by -the @Discord.Commands.IDependencyMap you pass into `LoadAssembly`. +the @System.IServiceProvider you pass into `LoadAssembly`. Any publicly settable properties will also be filled in the same manner. @@ -228,12 +227,12 @@ Any publicly settable properties will also be filled in the same manner. being injected. >[!NOTE] ->If you accept `CommandService` or `IDependencyMap` as a parameter in +>If you accept `CommandService` or `IServiceProvider` as a parameter in your constructor or as an injectable property, these entries will be filled -by the CommandService the module was loaded from, and the DependencyMap passed +by the CommandService the module was loaded from, and the ServiceProvider passed into it, respectively. -[!code-csharp[DependencyMap in Modules](samples/dependency_module.cs)] +[!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] # Preconditions diff --git a/docs/guides/commands/samples/command_handler.cs b/docs/guides/commands/samples/command_handler.cs index 71869415b..6b5d4ad2b 100644 --- a/docs/guides/commands/samples/command_handler.cs +++ b/docs/guides/commands/samples/command_handler.cs @@ -1,14 +1,16 @@ +using System; using System.Threading.Tasks; using System.Reflection; using Discord; using Discord.WebSocket; using Discord.Commands; +using Microsoft.Extensions.DependencyInjection; public class Program { private CommandService commands; private DiscordSocketClient client; - private DependencyMap map; + private IServiceProvider services; static void Main(string[] args) => new Program().Start().GetAwaiter().GetResult(); @@ -19,38 +21,40 @@ public class Program string token = "bot token here"; - map = new DependencyMap(); + services = new ServiceCollection() + .BuildServiceProvider(); await InstallCommands(); await client.LoginAsync(TokenType.Bot, token); - await client.ConnectAsync(); + await client.StartAsync(); await Task.Delay(-1); } + public async Task InstallCommands() { // Hook the MessageReceived Event into our Command Handler client.MessageReceived += HandleCommand; - // Discover all of the commands in this assembly and load them. + // Discover all of the commands in this assembly and load them. await commands.AddModulesAsync(Assembly.GetEntryAssembly()); } + public async Task HandleCommand(SocketMessage messageParam) - { + { // Don't process the command if it was a System Message var message = messageParam as SocketUserMessage; if (message == null) return; - // Create a number to track where the prefix ends and the command begins - int argPos = 0; - // Determine if the message is a command, based on if it starts with '!' or a mention prefix - if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(client.CurrentUser, ref argPos))) return; + // Create a number to track where the prefix ends and the command begins + int argPos = 0; + // Determine if the message is a command, based on if it starts with '!' or a mention prefix + if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(client.CurrentUser, ref argPos))) return; // Create a Command Context var context = new CommandContext(client, message); // Execute the command. (result does not indicate a return value, - // rather an object stating if the command executed succesfully) - var result = await commands.ExecuteAsync(context, argPos, map); + // rather an object stating if the command executed successfully) + var result = await commands.ExecuteAsync(context, argPos, service); if (!result.IsSuccess) await context.Channel.SendMessageAsync(result.ErrorReason); - } - + } } diff --git a/docs/guides/commands/samples/dependency_map_setup.cs b/docs/guides/commands/samples/dependency_map_setup.cs index aa39150e7..e205d891d 100644 --- a/docs/guides/commands/samples/dependency_map_setup.cs +++ b/docs/guides/commands/samples/dependency_map_setup.cs @@ -7,12 +7,11 @@ public class Commands { public async Task Install(DiscordSocketClient client) { - // Here, we will inject the Dependency Map with + // Here, we will inject the ServiceProvider with // all of the services our client will use. - _map.Add(client); - _map.Add(commands); - _map.Add(new NotificationService(_map)); - _map.Add(new DatabaseService(_map)); + _serviceCollection.AddSingleton(client) + _serviceCollection.AddSingleton(new NotificationService()) + _serviceCollection.AddSingleton(new DatabaseService()) // ... await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); } From 333881a7115a3d9d2d241d4e7c34d1312b9190ac Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 25 May 2017 13:54:57 -0300 Subject: [PATCH 10/62] Expose audio header more often --- src/Discord.Net.Core/Audio/AudioStream.cs | 5 ++- .../Audio/AudioClient.cs | 40 +++++++++---------- .../Audio/Streams/BufferedWriteStream.cs | 7 +++- .../Audio/Streams/JitterBuffer.cs | 4 +- .../Audio/Streams/OpusDecodeStream.cs | 17 ++++---- .../Audio/Streams/OpusEncodeStream.cs | 20 +++++++--- .../Audio/Streams/OutputStream.cs | 3 +- .../Audio/Streams/RTPWriteStream.cs | 7 ++-- .../Audio/Streams/SodiumEncryptStream.cs | 15 +++++++ 9 files changed, 75 insertions(+), 43 deletions(-) diff --git a/src/Discord.Net.Core/Audio/AudioStream.cs b/src/Discord.Net.Core/Audio/AudioStream.cs index d39bcc48a..97820ea73 100644 --- a/src/Discord.Net.Core/Audio/AudioStream.cs +++ b/src/Discord.Net.Core/Audio/AudioStream.cs @@ -11,7 +11,10 @@ namespace Discord.Audio public override bool CanSeek => false; public override bool CanWrite => false; - public virtual void WriteHeader(ushort seq, uint timestamp, bool missed) { } + public virtual void WriteHeader(ushort seq, uint timestamp, bool missed) + { + throw new InvalidOperationException("This stream does not accept headers"); + } public override void Write(byte[] buffer, int offset, int count) { WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 19639a418..0ca45a557 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -142,31 +142,31 @@ namespace Discord.Audio public AudioOutStream CreateOpusStream(int bufferMillis) { - var outputStream = new OutputStream(ApiClient); - var sodiumEncrypter = new SodiumEncryptStream( outputStream, this); - var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); - return new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, _audioLogger); + var outputStream = new OutputStream(ApiClient); //Ignores header + var sodiumEncrypter = new SodiumEncryptStream( outputStream, this); //Passes header + var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes + return new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, _audioLogger); //Generates header } public AudioOutStream CreateDirectOpusStream() { - var outputStream = new OutputStream(ApiClient); - var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); - return new RTPWriteStream(sodiumEncrypter, _ssrc); + var outputStream = new OutputStream(ApiClient); //Ignores header + var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header + return new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header (external input), passes } public AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate, int bufferMillis) { - var outputStream = new OutputStream(ApiClient); - var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); - var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); - var bufferedStream = new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, _audioLogger); - return new OpusEncodeStream(bufferedStream, bitrate ?? (96 * 1024), application); + var outputStream = new OutputStream(ApiClient); //Ignores header + var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header + var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes + var bufferedStream = new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, _audioLogger); //Ignores header, generates header + return new OpusEncodeStream(bufferedStream, bitrate ?? (96 * 1024), application); //Generates header } public AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate) { - var outputStream = new OutputStream(ApiClient); - var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); - var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); - return new OpusEncodeStream(rtpWriter, bitrate ?? (96 * 1024), application); + var outputStream = new OutputStream(ApiClient); //Ignores header + var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header + var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes + return new OpusEncodeStream(rtpWriter, bitrate ?? (96 * 1024), application); //Generates header } internal async Task CreateInputStreamAsync(ulong userId) @@ -174,11 +174,11 @@ namespace Discord.Audio //Assume Thread-safe if (!_streams.ContainsKey(userId)) { - var readerStream = new InputStream(); - var opusDecoder = new OpusDecodeStream(readerStream); + var readerStream = new InputStream(); //Consumes header + var opusDecoder = new OpusDecodeStream(readerStream); //Passes header //var jitterBuffer = new JitterBuffer(opusDecoder, _audioLogger); - var rtpReader = new RTPReadStream(opusDecoder); - var decryptStream = new SodiumDecryptStream(rtpReader, this); + var rtpReader = new RTPReadStream(opusDecoder); //Generates header + var decryptStream = new SodiumDecryptStream(rtpReader, this); //No header _streams.TryAdd(userId, new StreamPair(readerStream, decryptStream)); await _streamCreatedEvent.InvokeAsync(userId, readerStream); } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs index 29586389c..fb302f132 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs @@ -88,11 +88,12 @@ namespace Discord.Audio.Streams if (_queuedFrames.TryDequeue(out Frame frame)) { await _client.SetSpeakingAsync(true).ConfigureAwait(false); - _next.WriteHeader(seq++, timestamp, false); + _next.WriteHeader(seq, timestamp, false); await _next.WriteAsync(frame.Buffer, 0, frame.Bytes).ConfigureAwait(false); _bufferPool.Enqueue(frame.Buffer); _queueLock.Release(); nextTick += _ticksPerFrame; + seq++; timestamp += OpusEncoder.FrameSamplesPerChannel; _silenceFrames = 0; #if DEBUG @@ -105,12 +106,13 @@ namespace Discord.Audio.Streams { if (_silenceFrames++ < MaxSilenceFrames) { - _next.WriteHeader(seq++, timestamp, false); + _next.WriteHeader(seq, timestamp, false); await _next.WriteAsync(_silenceFrame, 0, _silenceFrame.Length).ConfigureAwait(false); } else await _client.SetSpeakingAsync(false).ConfigureAwait(false); nextTick += _ticksPerFrame; + seq++; timestamp += OpusEncoder.FrameSamplesPerChannel; } #if DEBUG @@ -126,6 +128,7 @@ namespace Discord.Audio.Streams }); } + public override void WriteHeader(ushort seq, uint timestamp, bool missed) { } //Ignore, we use our own timing public override async Task WriteAsync(byte[] data, int offset, int count, CancellationToken cancelToken) { if (cancelToken.CanBeCanceled) diff --git a/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs b/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs index a5ecdea6f..10f842a9d 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs @@ -1,4 +1,4 @@ -using Discord.Logging; +/*using Discord.Logging; using System; using System.Collections.Concurrent; using System.Threading; @@ -243,4 +243,4 @@ namespace Discord.Audio.Streams return Task.Delay(0); } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs index 43289c60e..58c4f4c70 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs @@ -25,12 +25,13 @@ namespace Discord.Audio.Streams public override void WriteHeader(ushort seq, uint timestamp, bool missed) { if (_hasHeader) - throw new InvalidOperationException("Header received with no payload"); - _nextMissed = missed; + throw new InvalidOperationException("Header received with no payload"); _hasHeader = true; + + _nextMissed = missed; _next.WriteHeader(seq, timestamp, missed); } - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { if (!_hasHeader) throw new InvalidOperationException("Received payload without an RTP header"); @@ -39,17 +40,17 @@ namespace Discord.Audio.Streams if (!_nextMissed) { count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0, false); - await _next.WriteAsync(_buffer, 0, count, cancellationToken).ConfigureAwait(false); + await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false); } else if (count > 0) { - count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0, true); - await _next.WriteAsync(_buffer, 0, count, cancellationToken).ConfigureAwait(false); + count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0, true); + await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false); } else { - count = _decoder.DecodeFrame(null, 0, 0, _buffer, 0, true); - await _next.WriteAsync(_buffer, 0, count, cancellationToken).ConfigureAwait(false); + count = _decoder.DecodeFrame(null, 0, 0, _buffer, 0, true); + await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false); } } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs index 2a3c03a47..a7779a84c 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs @@ -13,6 +13,8 @@ namespace Discord.Audio.Streams private readonly OpusEncoder _encoder; private readonly byte[] _buffer; private int _partialFramePos; + private ushort _seq; + private uint _timestamp; public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application) { @@ -21,7 +23,7 @@ namespace Discord.Audio.Streams _buffer = new byte[OpusConverter.FrameBytes]; } - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { //Assume threadsafe while (count > 0) @@ -30,10 +32,13 @@ namespace Discord.Audio.Streams { //We have enough data and no partial frames. Pass the buffer directly to the encoder int encFrameSize = _encoder.EncodeFrame(buffer, offset, _buffer, 0); - await _next.WriteAsync(_buffer, 0, encFrameSize, cancellationToken).ConfigureAwait(false); + _next.WriteHeader(_seq, _timestamp, false); + await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false); offset += OpusConverter.FrameBytes; count -= OpusConverter.FrameBytes; + _seq++; + _timestamp += OpusConverter.FrameBytes; } else if (_partialFramePos + count >= OpusConverter.FrameBytes) { @@ -41,11 +46,14 @@ namespace Discord.Audio.Streams int partialSize = OpusConverter.FrameBytes - _partialFramePos; Buffer.BlockCopy(buffer, offset, _buffer, _partialFramePos, partialSize); int encFrameSize = _encoder.EncodeFrame(_buffer, 0, _buffer, 0); - await _next.WriteAsync(_buffer, 0, encFrameSize, cancellationToken).ConfigureAwait(false); + _next.WriteHeader(_seq, _timestamp, false); + await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false); offset += partialSize; count -= partialSize; _partialFramePos = 0; + _seq++; + _timestamp += OpusConverter.FrameBytes; } else { @@ -57,8 +65,8 @@ namespace Discord.Audio.Streams } } - /* - public override async Task FlushAsync(CancellationToken cancellationToken) + /* //Opus throws memory errors on bad frames + public override async Task FlushAsync(CancellationToken cancelToken) { try { @@ -67,7 +75,7 @@ namespace Discord.Audio.Streams } catch (Exception) { } //Incomplete frame _partialFramePos = 0; - await base.FlushAsync(cancellationToken).ConfigureAwait(false); + await base.FlushAsync(cancelToken).ConfigureAwait(false); }*/ public override async Task FlushAsync(CancellationToken cancelToken) diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs index 6238e93b4..cba4e3cb6 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs @@ -13,7 +13,8 @@ namespace Discord.Audio.Streams { _client = client; } - + + public override void WriteHeader(ushort seq, uint timestamp, bool missed) { } //Ignore public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); diff --git a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs index 78f895381..ce407eada 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs @@ -33,14 +33,14 @@ namespace Discord.Audio.Streams { if (_hasHeader) throw new InvalidOperationException("Header received with no payload"); + _hasHeader = true; _nextSeq = seq; _nextTimestamp = timestamp; } - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { - cancellationToken.ThrowIfCancellationRequested(); - + cancelToken.ThrowIfCancellationRequested(); if (!_hasHeader) throw new InvalidOperationException("Received payload without an RTP header"); _hasHeader = false; @@ -57,6 +57,7 @@ namespace Discord.Audio.Streams Buffer.BlockCopy(_header, 0, _buffer, 0, 12); //Copy RTP header from to the buffer Buffer.BlockCopy(buffer, offset, _buffer, 12, count); + _next.WriteHeader(_nextSeq, _nextTimestamp, false); await _next.WriteAsync(_buffer, 0, count + 12).ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs index b00a7f403..2e7a7e276 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs @@ -10,6 +10,9 @@ namespace Discord.Audio.Streams private readonly AudioClient _client; private readonly AudioStream _next; private readonly byte[] _nonce; + private bool _hasHeader; + private ushort _nextSeq; + private uint _nextTimestamp; public SodiumEncryptStream(AudioStream next, IAudioClient client) { @@ -17,16 +20,28 @@ namespace Discord.Audio.Streams _client = (AudioClient)client; _nonce = new byte[24]; } + + public override void WriteHeader(ushort seq, uint timestamp, bool missed) + { + if (_hasHeader) + throw new InvalidOperationException("Header received with no payload"); + _nextSeq = seq; + _nextTimestamp = timestamp; + } public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); + if (!_hasHeader) + throw new InvalidOperationException("Received payload without an RTP header"); + _hasHeader = false; if (_client.SecretKey == null) return; Buffer.BlockCopy(buffer, offset, _nonce, 0, 12); //Copy nonce from RTP header count = SecretBox.Encrypt(buffer, offset + 12, count - 12, buffer, 12, _nonce, _client.SecretKey); + _next.WriteHeader(_nextSeq, _nextTimestamp, false); await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); } From 8eb9b2071cbb0f4d0efaff4b578c484a68f1dc41 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 25 May 2017 21:45:41 -0300 Subject: [PATCH 11/62] Set hasHeader in SodiumEncrypt --- src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs index 2e7a7e276..bacc9be47 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs @@ -28,6 +28,7 @@ namespace Discord.Audio.Streams _nextSeq = seq; _nextTimestamp = timestamp; + _hasHeader = true; } public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { From 73611d1fab6c55ca21d0115040dc2fd72dcf07c7 Mon Sep 17 00:00:00 2001 From: AntiTcb Date: Sat, 27 May 2017 14:47:12 -0400 Subject: [PATCH 12/62] Remove IUser.CreateDMChannelAsync, implicitly implement IUser.GetDMChannelAsync --- src/Discord.Net.Core/Entities/Users/IUser.cs | 4 +--- src/Discord.Net.Rest/Entities/Users/RestUser.cs | 8 +++----- src/Discord.Net.Rpc/Entities/Users/RpcUser.cs | 8 +++----- .../Entities/Users/SocketUser.cs | 12 +++++------- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index 45d8862f1..249100d37 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -20,8 +20,6 @@ namespace Discord string Username { get; } /// Returns a private message channel to this user, creating one if it does not already exist. - Task GetDMChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// Returns a private message channel to this user, creating one if it does not already exist. - Task CreateDMChannelAsync(RequestOptions options = null); + Task GetDMChannelAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index cded876c8..36ca242d8 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -54,7 +54,7 @@ namespace Discord.Rest Update(model); } - public Task CreateDMChannelAsync(RequestOptions options = null) + public Task GetDMChannelAsync(RequestOptions options = null) => UserHelper.CreateDMChannelAsync(this, Discord, options); public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) @@ -64,9 +64,7 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; //IUser - Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(null); - async Task IUser.CreateDMChannelAsync(RequestOptions options) - => await CreateDMChannelAsync(options).ConfigureAwait(false); + async Task IUser.GetDMChannelAsync(RequestOptions options) + => await GetDMChannelAsync(options); } } diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs index 7ed11e57d..71de1f804 100644 --- a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs +++ b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs @@ -49,7 +49,7 @@ namespace Discord.Rpc Username = model.Username.Value; } - public Task CreateDMChannelAsync(RequestOptions options = null) + public Task GetDMChannelAsync(RequestOptions options = null) => UserHelper.CreateDMChannelAsync(this, Discord, options); public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) @@ -59,9 +59,7 @@ namespace Discord.Rpc private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; //IUser - Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(null); - async Task IUser.CreateDMChannelAsync(RequestOptions options) - => await CreateDMChannelAsync(options).ConfigureAwait(false); + async Task IUser.GetDMChannelAsync(RequestOptions options) + => await GetDMChannelAsync(options); } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 7575309cb..60fca73b2 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -53,10 +53,10 @@ namespace Discord.WebSocket hasChanges = true; } return hasChanges; - } + } - public Task CreateDMChannelAsync(RequestOptions options = null) - => UserHelper.CreateDMChannelAsync(this, Discord, options); + public async Task GetDMChannelAsync(RequestOptions options = null) + => GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options) as IDMChannel; public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); @@ -66,9 +66,7 @@ namespace Discord.WebSocket internal SocketUser Clone() => MemberwiseClone() as SocketUser; //IUser - async Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) - => await Task.FromResult(GlobalUser.DMChannel ?? await CreateDMChannelAsync(options) as IDMChannel); - async Task IUser.CreateDMChannelAsync(RequestOptions options) - => await CreateDMChannelAsync(options).ConfigureAwait(false); + Task IUser.GetDMChannelAsync(RequestOptions options) + => GetDMChannelAsync(options); } } From 2ef53330fb8b19b5a150ec1b3a2b16f6896835a5 Mon Sep 17 00:00:00 2001 From: BlockBuilder57 Date: Mon, 29 May 2017 13:33:28 -0500 Subject: [PATCH 13/62] Add newest verification level Users must have a verified phone on their Discord account. http://i.imgur.com/BexDgzS.png --- src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs b/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs index d6828b5c9..ac51fe927 100644 --- a/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs +++ b/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs @@ -9,6 +9,8 @@ /// Users must fulfill the requirements of Low, and be registered on Discord for at least 5 minutes. Medium = 2, /// Users must fulfill the requirements of Medium, and be a member of this guild for at least 10 minutes. - High = 3 + High = 3, + /// Users must fulfill the requirements of High, and must have a verified phone on their Discord account. + Extreme = 4 } } From d05191ed051aa269bd8ae30f97ca3ecbd258e1f4 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Tue, 30 May 2017 17:54:32 +0200 Subject: [PATCH 14/62] Added/clarified some comments in structure.cs --- .../getting_started/samples/intro/structure.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index 9e783bb9b..d611e1240 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -30,8 +30,8 @@ class Program LogLevel = LogSeverity.Info, // If you or another service needs to do anything with messages - // (eg. checking Reactions), you should probably - // set the MessageCacheSize here. + // (eg. checking Reactions, checking the content of edited/deleted messages), + // you must set the MessageCacheSize. You may adjust the number as needed. //MessageCacheSize = 50, // If your platform doesn't have native websockets, @@ -41,7 +41,7 @@ class Program }); } - // Create a named logging handler, so it can be re-used by addons + // Example of a logging handler. This can be re-used by addons // that ask for a Func. private static Task Logger(LogMessage message) { @@ -65,6 +65,11 @@ class Program } Console.WriteLine($"{DateTime.Now,-19} [{message.Severity,8}] {message.Source}: {message.Message}"); Console.ForegroundColor = cc; + + // If you get an error saying 'CompletedTask' doesn't exist, + // your project is targeting .NET 4.5.2 or lower. You'll need + // to adjust your project's target framework to 4.6 or higher + // (instructions for this are easily Googled). return Task.CompletedTask; } @@ -120,7 +125,7 @@ class Program // commands to be invoked by mentioning the bot instead. if (msg.HasCharPrefix('!', ref pos) /* || msg.HasMentionPrefix(_client.CurrentUser, ref pos) */) { - // Create a Command Context + // Create a Command Context. var context = new SocketCommandContext(_client, msg); // Execute the command. (result does not indicate a return value, From 12acfec1dbdedfa79fde4e3a5d11c26ebe35ffb0 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Sun, 4 Jun 2017 23:44:39 +0200 Subject: [PATCH 15/62] Respond to feedback --- docs/guides/getting_started/samples/intro/structure.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index d611e1240..bdbdf8fe4 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -70,6 +70,8 @@ class Program // your project is targeting .NET 4.5.2 or lower. You'll need // to adjust your project's target framework to 4.6 or higher // (instructions for this are easily Googled). + // If you *need* to run on .NET 4.5 for compat/other reasons, + // the alternative is to 'return Task.Delay(0);' instead. return Task.CompletedTask; } From 6cdc48bfa66bd48108e9931778ad4fbb95607ea1 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Wed, 7 Jun 2017 04:32:59 +0200 Subject: [PATCH 16/62] Move instructions about BuildServiceProvider() up --- docs/guides/getting_started/samples/intro/structure.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index bdbdf8fe4..000ee2d02 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -99,16 +99,16 @@ class Program // and other dependencies that your commands might need. _map.AddSingleton(new SomeServiceClass()); - // Either search the program and add all Module classes that can be found: - await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); - // Or add Modules manually if you prefer to be a little more explicit: - await _commands.AddModuleAsync(); - // 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: + await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); + // Or add Modules manually if you prefer to be a little more explicit: + await _commands.AddModuleAsync(); + // Subscribe a handler to see if a message invokes a command. _client.MessageReceived += HandleCommandAsync; } From 1d096a7fc519f96251bc005e9c2fc64202c524e6 Mon Sep 17 00:00:00 2001 From: Izumemori Date: Tue, 13 Jun 2017 01:58:54 +0200 Subject: [PATCH 17/62] Fix spelling --- src/Discord.Net.Core/Entities/Emotes/Emoji.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs index 96226c715..5c1969613 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs @@ -7,7 +7,7 @@ { // TODO: need to constrain this to unicode-only emojis somehow /// - /// Creates a unciode emoji. + /// Creates a unicode emoji. /// /// The pure UTF-8 encoding of an emoji public Emoji(string unicode) From b0a3ce5e7cd600ce32f7c8cd98e9ae59bc10e5bc Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Tue, 13 Jun 2017 10:58:06 +0200 Subject: [PATCH 18/62] Respond to feedback. --- docs/guides/getting_started/samples/intro/structure.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index 000ee2d02..706d0a38d 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -104,7 +104,8 @@ class Program // 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: + // 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()); // Or add Modules manually if you prefer to be a little more explicit: await _commands.AddModuleAsync(); From fb01e16b36aa44bdf877b9b4efe574862632e526 Mon Sep 17 00:00:00 2001 From: Drew Date: Thu, 15 Jun 2017 10:43:06 -0400 Subject: [PATCH 19/62] Fixed dead link (#662) * Update intro.md * Update intro.md * Update intro.md * Update intro.md * Update intro.md * Update intro.md * Update intro.md --- docs/guides/getting_started/intro.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/getting_started/intro.md b/docs/guides/getting_started/intro.md index 8bcfa9086..837814511 100644 --- a/docs/guides/getting_started/intro.md +++ b/docs/guides/getting_started/intro.md @@ -211,7 +211,7 @@ For your reference, you may view the [completed program]. # Building a bot with commands This section will show you how to write a program that is ready for -[commands](commands.md). Note that this will not be explaining _how_ +[commands](commands/commands.md). Note that this will not be explaining _how_ to write commands or services, it will only be covering the general structure. @@ -224,4 +224,4 @@ should be to separate the program (initialization and command handler), the modules (handle commands), and the services (persistent storage, pure functions, data manipulation). -**todo:** diagram of bot structure \ No newline at end of file +**todo:** diagram of bot structure From 8c2a46e9e751e927d7bce7581e20e71069a8c3c4 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Thu, 15 Jun 2017 11:05:41 -0400 Subject: [PATCH 20/62] Add ulong overload to IMessageChannel.DeleteMessagesAsync (#649) --- src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs | 4 +++- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 8 ++++---- src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs | 4 +++- .../Entities/Channels/RestGroupChannel.cs | 4 +++- src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 4 +++- .../Entities/Channels/RpcVirtualMessageChannel.cs | 4 +++- src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs | 4 +++- src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs | 4 +++- src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs | 4 +++- .../Entities/Channels/SocketDMChannel.cs | 4 +++- .../Entities/Channels/SocketGroupChannel.cs | 4 +++- .../Entities/Channels/SocketTextChannel.cs | 4 +++- 12 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index 7fce1e855..a465b3ad8 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -30,7 +30,9 @@ namespace Discord /// Gets a collection of pinned messages in this channel. Task> GetPinnedMessagesAsync(RequestOptions options = null); /// Bulk deletes multiple messages. - Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); + Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); + /// Bulk deletes multiple messages. + Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null); /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. Task TriggerTypingAsync(RequestOptions options = null); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 284decd8c..6b7dca3a9 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -178,12 +178,12 @@ namespace Discord.Rest var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); - } + } - public static async Task DeleteMessagesAsync(IMessageChannel channel, BaseDiscordClient client, - IEnumerable messages, RequestOptions options) + public static async Task DeleteMessagesAsync(IMessageChannel channel, BaseDiscordClient client, + IEnumerable messageIds, RequestOptions options) { - var msgs = messages.Select(x => x.Id).ToArray(); + var msgs = messageIds.ToArray(); if (msgs.Length < 100) { var args = new DeleteMessagesParams(msgs); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 0a4bc9522..8a31da3f1 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -73,7 +73,9 @@ namespace Discord.Rest => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index b324422a5..44c118fee 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -86,7 +86,9 @@ namespace Discord.Rest => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 2fe9feb91..d7405fb4a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -64,7 +64,9 @@ namespace Discord.Rest => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index dfd996ee1..775f2ea82 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -43,7 +43,9 @@ namespace Discord.Rest => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs index b2c3daaa2..da9bce700 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -54,7 +54,9 @@ namespace Discord.Rpc => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs index b5effacc6..d449688a4 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -57,7 +57,9 @@ namespace Discord.Rpc => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs index bdfafa561..72b45e466 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs @@ -58,7 +58,9 @@ namespace Discord.Rpc => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 8e9272ff0..322a99496 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -77,7 +77,9 @@ namespace Discord.WebSocket => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index d7d80214f..dc1853e73 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -105,7 +105,9 @@ namespace Discord.WebSocket => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index e75b6a4f1..c22523e00 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -81,7 +81,9 @@ namespace Discord.WebSocket => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); From fb57a61432d3e800c881067983fcceaf6eac2339 Mon Sep 17 00:00:00 2001 From: AntiTcb Date: Fri, 16 Jun 2017 20:43:50 -0400 Subject: [PATCH 21/62] Rename to GetOrCreateDMChannelAsync --- src/Discord.Net.Core/Entities/Users/IUser.cs | 2 +- src/Discord.Net.Rest/Entities/Users/RestUser.cs | 6 +++--- src/Discord.Net.Rpc/Entities/Users/RpcUser.cs | 6 +++--- src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs | 6 +----- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index 249100d37..e3f270f6f 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -20,6 +20,6 @@ namespace Discord string Username { get; } /// Returns a private message channel to this user, creating one if it does not already exist. - Task GetDMChannelAsync(RequestOptions options = null); + Task GetOrCreateDMChannelAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 36ca242d8..d8ade3a6b 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -54,7 +54,7 @@ namespace Discord.Rest Update(model); } - public Task GetDMChannelAsync(RequestOptions options = null) + public Task GetOrCreateDMChannelAsync(RequestOptions options = null) => UserHelper.CreateDMChannelAsync(this, Discord, options); public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) @@ -64,7 +64,7 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; //IUser - async Task IUser.GetDMChannelAsync(RequestOptions options) - => await GetDMChannelAsync(options); + async Task IUser.GetOrCreateDMChannelAsync(RequestOptions options) + => await GetOrCreateDMChannelAsync(options); } } diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs index 71de1f804..c6b0b2fd8 100644 --- a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs +++ b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs @@ -49,7 +49,7 @@ namespace Discord.Rpc Username = model.Username.Value; } - public Task GetDMChannelAsync(RequestOptions options = null) + public Task GetOrCreateDMChannelAsync(RequestOptions options = null) => UserHelper.CreateDMChannelAsync(this, Discord, options); public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) @@ -59,7 +59,7 @@ namespace Discord.Rpc private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; //IUser - async Task IUser.GetDMChannelAsync(RequestOptions options) - => await GetDMChannelAsync(options); + async Task IUser.GetOrCreateDMChannelAsync(RequestOptions options) + => await GetOrCreateDMChannelAsync(options); } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 60fca73b2..a0c78b93f 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -55,7 +55,7 @@ namespace Discord.WebSocket return hasChanges; } - public async Task GetDMChannelAsync(RequestOptions options = null) + public async Task GetOrCreateDMChannelAsync(RequestOptions options = null) => GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options) as IDMChannel; public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) @@ -64,9 +64,5 @@ namespace Discord.WebSocket public override string ToString() => $"{Username}#{Discriminator}"; private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; internal SocketUser Clone() => MemberwiseClone() as SocketUser; - - //IUser - Task IUser.GetDMChannelAsync(RequestOptions options) - => GetDMChannelAsync(options); } } From 0708bc5d48dee30c398a5ff13c5884788b85dc59 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 16 Jun 2017 22:39:40 -0400 Subject: [PATCH 22/62] Add EmbedType enum --- src/Discord.Net.Core/Entities/Messages/Embed.cs | 6 +++--- src/Discord.Net.Core/Entities/Messages/EmbedType.cs | 11 +++++++++++ src/Discord.Net.Core/Entities/Messages/IEmbed.cs | 2 +- src/Discord.Net.Rest/API/Common/Embed.cs | 4 ++-- .../Entities/Messages/EmbedBuilder.cs | 2 +- 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/EmbedType.cs diff --git a/src/Discord.Net.Core/Entities/Messages/Embed.cs b/src/Discord.Net.Core/Entities/Messages/Embed.cs index ebde05d4c..c80151375 100644 --- a/src/Discord.Net.Core/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Core/Entities/Messages/Embed.cs @@ -7,7 +7,7 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Embed : IEmbed { - public string Type { get; } + public EmbedType Type { get; } public string Description { get; internal set; } public string Url { get; internal set; } @@ -22,12 +22,12 @@ namespace Discord public EmbedThumbnail? Thumbnail { get; internal set; } public ImmutableArray Fields { get; internal set; } - internal Embed(string type) + internal Embed(EmbedType type) { Type = type; Fields = ImmutableArray.Create(); } - internal Embed(string type, + internal Embed(EmbedType type, string title, string description, string url, diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedType.cs b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs new file mode 100644 index 000000000..e071e7dc8 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs @@ -0,0 +1,11 @@ +namespace Discord +{ + public enum EmbedType + { + Rich, + Link, + Video, + Image, + Gifv + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index 5eef5ec9b..f390c4c28 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -6,9 +6,9 @@ namespace Discord public interface IEmbed { string Url { get; } - string Type { get; } string Title { get; } string Description { get; } + EmbedType Type { get; } DateTimeOffset? Timestamp { get; } Color? Color { get; } EmbedImage? Image { get; } diff --git a/src/Discord.Net.Rest/API/Common/Embed.cs b/src/Discord.Net.Rest/API/Common/Embed.cs index f6325efbb..7e8dd9b90 100644 --- a/src/Discord.Net.Rest/API/Common/Embed.cs +++ b/src/Discord.Net.Rest/API/Common/Embed.cs @@ -8,14 +8,14 @@ namespace Discord.API { [JsonProperty("title")] public string Title { get; set; } - [JsonProperty("type")] - public string Type { get; set; } [JsonProperty("description")] public string Description { get; set; } [JsonProperty("url")] public string Url { get; set; } [JsonProperty("color")] public uint? Color { get; set; } + [JsonProperty("type")] + public EmbedType Type { get; set; } [JsonProperty("timestamp")] public DateTimeOffset? Timestamp { get; set; } [JsonProperty("author")] diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs index 98a191379..7f037c0c0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs @@ -10,7 +10,7 @@ namespace Discord public EmbedBuilder() { - _embed = new Embed("rich"); + _embed = new Embed(EmbedType.Rich); Fields = new List(); } From 0550006d567fe28a52d6c63c03a3c28c843b9506 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 17 Jun 2017 15:10:35 +0100 Subject: [PATCH 23/62] Change wording of permission preconditions Also fix an issue where RequireBotPermission may throw if used in a non-guild channel which required guild permissions. --- .../Preconditions/RequireBotPermissionAttribute.cs | 8 +++++--- .../Preconditions/RequireUserPermissionAttribute.cs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs index 82975a2f6..0f865e864 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -44,14 +44,16 @@ namespace Discord.Commands public override async Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) { - var guildUser = await context.Guild.GetCurrentUserAsync(); + IGuildUser guildUser = null; + if (context.Guild != null) + guildUser = await context.Guild.GetCurrentUserAsync().ConfigureAwait(false); if (GuildPermission.HasValue) { if (guildUser == null) return PreconditionResult.FromError("Command must be used in a guild channel"); if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) - return PreconditionResult.FromError($"Command requires guild permission {GuildPermission.Value}"); + return PreconditionResult.FromError($"Bot requires guild permission {GuildPermission.Value}"); } if (ChannelPermission.HasValue) @@ -65,7 +67,7 @@ namespace Discord.Commands perms = ChannelPermissions.All(guildChannel); if (!perms.Has(ChannelPermission.Value)) - return PreconditionResult.FromError($"Command requires channel permission {ChannelPermission.Value}"); + return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}"); } return PreconditionResult.FromSuccess(); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index 44c69d76a..b7729b0c8 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -52,7 +52,7 @@ namespace Discord.Commands if (guildUser == null) return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel")); if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) - return Task.FromResult(PreconditionResult.FromError($"Command requires guild permission {GuildPermission.Value}")); + return Task.FromResult(PreconditionResult.FromError($"User requires guild permission {GuildPermission.Value}")); } if (ChannelPermission.HasValue) @@ -66,7 +66,7 @@ namespace Discord.Commands perms = ChannelPermissions.All(guildChannel); if (!perms.Has(ChannelPermission.Value)) - return Task.FromResult(PreconditionResult.FromError($"Command requires channel permission {ChannelPermission.Value}")); + return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}")); } return Task.FromResult(PreconditionResult.FromSuccess()); From 33e765f8f5c4fd9be839ab0fee418d4c62be1bef Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 17 Jun 2017 19:00:22 -0400 Subject: [PATCH 24/62] Use StringEnum converter in API model --- src/Discord.Net.Rest/API/Common/Embed.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Rest/API/Common/Embed.cs b/src/Discord.Net.Rest/API/Common/Embed.cs index 7e8dd9b90..1c9fa34e2 100644 --- a/src/Discord.Net.Rest/API/Common/Embed.cs +++ b/src/Discord.Net.Rest/API/Common/Embed.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace Discord.API { @@ -14,7 +15,7 @@ namespace Discord.API public string Url { get; set; } [JsonProperty("color")] public uint? Color { get; set; } - [JsonProperty("type")] + [JsonProperty("type"), JsonConverter(typeof(StringEnumConverter))] public EmbedType Type { get; set; } [JsonProperty("timestamp")] public DateTimeOffset? Timestamp { get; set; } From 759da09c38631efe66630036fa8aa61cd6e92fa8 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Mon, 19 Jun 2017 15:21:46 -0400 Subject: [PATCH 25/62] Update events.cs Gladly taking suggestions for a better comments. --- docs/guides/concepts/samples/events.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/guides/concepts/samples/events.cs b/docs/guides/concepts/samples/events.cs index c662b51a9..f268b6e49 100644 --- a/docs/guides/concepts/samples/events.cs +++ b/docs/guides/concepts/samples/events.cs @@ -8,7 +8,11 @@ public class Program public async Task MainAsync() { - _client = new DiscordSocketClient(); + // When working with events that have Cacheable parameters, + // you must enable the message cache in your config settings if you plan to + // use the cached message entity. + _config = new DiscordSocketConfig { MessageCacheSize = 100 }; + _client = new DiscordSocketClient(_config); await _client.LoginAsync(TokenType.Bot, "bot token"); await _client.StartAsync(); @@ -25,7 +29,8 @@ public class Program private async Task MessageUpdated(Cacheable before, SocketMessage after, ISocketMessageChannel channel) { + // If the message was not in the cache, downloading it will result in getting a copy of `after`. var message = await before.GetOrDownloadAsync(); Console.WriteLine($"{message} -> {after}"); } -} \ No newline at end of file +} From 6e21d33999b493783a68500cabbffdefa7c986ab Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Tue, 20 Jun 2017 20:44:33 -0400 Subject: [PATCH 26/62] Update events.cs Forgot a var >_> --- docs/guides/concepts/samples/events.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/concepts/samples/events.cs b/docs/guides/concepts/samples/events.cs index f268b6e49..cf0492cb5 100644 --- a/docs/guides/concepts/samples/events.cs +++ b/docs/guides/concepts/samples/events.cs @@ -11,7 +11,7 @@ public class Program // When working with events that have Cacheable parameters, // you must enable the message cache in your config settings if you plan to // use the cached message entity. - _config = new DiscordSocketConfig { MessageCacheSize = 100 }; + var _config = new DiscordSocketConfig { MessageCacheSize = 100 }; _client = new DiscordSocketClient(_config); await _client.LoginAsync(TokenType.Bot, "bot token"); From 4a9c8168a9e395bc93b29722d682fdfe98ac2f33 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Fri, 23 Jun 2017 16:28:22 +0200 Subject: [PATCH 27/62] Add grouping of preconditions to allow for flexible precondition logic. (#672) * Add grouping of preconditions to allow for flexible precondition logic. * Fix checking Module Preconditions twice (and none of the command's own) * Fix command preconditions group 0 looping over every other precondition anyway #whoopsies * Use custom message when a non-zero Precondition Group fails. * Fix doc comment rendering. * Refactor loops into local function * Considering a new result type * Switch to IReadOnlyCollection and fix compiler errors * Revert PreconditionResult -> IResult in return types - Change PreconditionResult to a class that PreconditionGroupResult inherits. * Feedback on property name. * Change grouping type int -> string * Explicitly use an ordinal StringComparer * Full stops on error messages * Remove some sillyness. * Remove unneeded using. --- .../Attributes/PreconditionAttribute.cs | 7 +++ src/Discord.Net.Commands/CommandMatch.cs | 2 +- src/Discord.Net.Commands/Info/CommandInfo.cs | 46 +++++++++++++------ .../Results/PreconditionGroupResult.cs | 27 +++++++++++ .../Results/PreconditionResult.cs | 4 +- 5 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 src/Discord.Net.Commands/Results/PreconditionGroupResult.cs diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index e099380f6..3727510d9 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -6,6 +6,13 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public abstract class PreconditionAttribute : Attribute { + /// + /// Specify a group that this precondition belongs to. Preconditions of the same group require only one + /// of the preconditions to pass in order to be successful (A || B). Specifying = + /// or not at all will require *all* preconditions to pass, just like normal (A && B). + /// + public string Group { get; set; } = null; + public abstract Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/CommandMatch.cs b/src/Discord.Net.Commands/CommandMatch.cs index 04a2d040f..74c0de73e 100644 --- a/src/Discord.Net.Commands/CommandMatch.cs +++ b/src/Discord.Net.Commands/CommandMatch.cs @@ -18,7 +18,7 @@ namespace Discord.Commands public Task CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) => Command.CheckPreconditionsAsync(context, services); - public Task ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) + public Task ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult preconditionResult = null) => Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult); public Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) => Command.ExecuteAsync(context, argList, paramList, services); diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 5acd1f648..ae350e592 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -68,29 +68,49 @@ namespace Discord.Commands { services = services ?? EmptyServiceProvider.Instance; - foreach (PreconditionAttribute precondition in Module.Preconditions) + async Task CheckGroups(IEnumerable preconditions, string type) { - var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); - if (!result.IsSuccess) - return result; + foreach (IGrouping preconditionGroup in preconditions.GroupBy(p => p.Group, StringComparer.Ordinal)) + { + if (preconditionGroup.Key == null) + { + foreach (PreconditionAttribute precondition in preconditionGroup) + { + var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); + if (!result.IsSuccess) + return PreconditionGroupResult.FromError($"{type} default precondition group failed.", new[] { result }); + } + } + else + { + var results = new List(); + foreach (PreconditionAttribute precondition in preconditionGroup) + results.Add(await precondition.CheckPermissions(context, this, services).ConfigureAwait(false)); + + if (!results.Any(p => p.IsSuccess)) + return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results); + } + } + return PreconditionGroupResult.FromSuccess(); } - foreach (PreconditionAttribute precondition in Preconditions) - { - var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); - if (!result.IsSuccess) - return result; - } + var moduleResult = await CheckGroups(Module.Preconditions, "Module"); + if (!moduleResult.IsSuccess) + return moduleResult; + + var commandResult = await CheckGroups(Preconditions, "Command"); + if (!commandResult.IsSuccess) + return commandResult; return PreconditionResult.FromSuccess(); } - public async Task ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult? preconditionResult = null) + public async Task ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null) { if (!searchResult.IsSuccess) return ParseResult.FromError(searchResult); - if (preconditionResult != null && !preconditionResult.Value.IsSuccess) - return ParseResult.FromError(preconditionResult.Value); + if (preconditionResult != null && !preconditionResult.IsSuccess) + return ParseResult.FromError(preconditionResult); string input = searchResult.Text.Substring(startIndex); return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); diff --git a/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs b/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs new file mode 100644 index 000000000..1d7f29122 --- /dev/null +++ b/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Discord.Commands +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class PreconditionGroupResult : PreconditionResult + { + public IReadOnlyCollection PreconditionResults { get; } + + protected PreconditionGroupResult(CommandError? error, string errorReason, ICollection preconditions) + : base(error, errorReason) + { + PreconditionResults = (preconditions ?? new List(0)).ToReadOnlyCollection(); + } + + public static new PreconditionGroupResult FromSuccess() + => new PreconditionGroupResult(null, null, null); + public static PreconditionGroupResult FromError(string reason, ICollection preconditions) + => new PreconditionGroupResult(CommandError.UnmetPrecondition, reason, preconditions); + public static new PreconditionGroupResult FromError(IResult result) //needed? + => new PreconditionGroupResult(result.Error, result.ErrorReason, null); + + public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; + private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; + } +} diff --git a/src/Discord.Net.Commands/Results/PreconditionResult.cs b/src/Discord.Net.Commands/Results/PreconditionResult.cs index 77ba1b5b9..ca65a373e 100644 --- a/src/Discord.Net.Commands/Results/PreconditionResult.cs +++ b/src/Discord.Net.Commands/Results/PreconditionResult.cs @@ -3,14 +3,14 @@ namespace Discord.Commands { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public struct PreconditionResult : IResult + public class PreconditionResult : IResult { public CommandError? Error { get; } public string ErrorReason { get; } public bool IsSuccess => !Error.HasValue; - private PreconditionResult(CommandError? error, string errorReason) + protected PreconditionResult(CommandError? error, string errorReason) { Error = error; ErrorReason = errorReason; From cce572c6000cb2d2572a176d73c402c800913ec4 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Fri, 23 Jun 2017 15:28:30 +0100 Subject: [PATCH 28/62] Include names in command builder exceptions (#663) --- src/Discord.Net.Commands/Builders/CommandBuilder.cs | 4 ++-- src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 2 +- src/Discord.Net.Commands/Builders/ParameterBuilder.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index ff89b7559..8c2207f10 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -122,11 +122,11 @@ namespace Discord.Commands.Builders var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple); if ((firstMultipleParam != null) && (firstMultipleParam != lastParam)) - throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag."); + throw new InvalidOperationException($"Only the last parameter in a command may have the Multiple flag. Parameter: {firstMultipleParam.Name} in {PrimaryAlias}"); var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder); if ((firstRemainderParam != null) && (firstRemainderParam != lastParam)) - throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag."); + throw new InvalidOperationException($"Only the last parameter in a command may have the Remainder flag. Parameter: {firstRemainderParam.Name} in {PrimaryAlias}"); } return new CommandInfo(this, info, service); diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index d8464ea72..fe35e3b2a 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -210,7 +210,7 @@ namespace Discord.Commands else if (attribute is RemainderAttribute) { if (position != count-1) - throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); + throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}"); builder.IsRemainder = true; } diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 6761033b0..d2bebbad0 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -49,7 +49,7 @@ namespace Discord.Commands.Builders TypeReader = Command.Module.Service.GetDefaultTypeReader(type); if (TypeReader == null) - throw new InvalidOperationException($"{type} does not have a TypeReader registered for it"); + throw new InvalidOperationException($"{type} does not have a TypeReader registered for it. Parameter: {Name} in {Command.PrimaryAlias}"); if (type.GetTypeInfo().IsValueType) DefaultValue = Activator.CreateInstance(type); From fb0a056d76bab60ecf9dfe3ca7d13aa7f859c6cf Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 23 Jun 2017 10:29:39 -0400 Subject: [PATCH 29/62] Add IUser#SendMessageAsync extension (#706) * Add IUser#SendMessageAsync extension * Add ConfigureAwait --- .../Extensions/UserExtensions.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/Discord.Net.Core/Extensions/UserExtensions.cs diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs new file mode 100644 index 000000000..0861ed33e --- /dev/null +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Discord +{ + public static class UserExtensions + { + public static async Task SendMessageAsync(this IUser user, + string text, + bool isTTS = false, + Embed embed = null, + RequestOptions options = null) + { + return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + } + } +} From 5f04e2beba12904c2a4839eac28e648e8b3669d0 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 23 Jun 2017 10:29:45 -0400 Subject: [PATCH 30/62] Cache outgoing presence data if disconnected (#705) This resolves #702 --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index d42df7b55..f9458caff 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -360,7 +360,7 @@ namespace Discord.WebSocket private async Task SendStatusAsync() { if (CurrentUser == null) - throw new InvalidOperationException("Presence data cannot be sent before the client has logged in."); + return; var game = Game; var status = Status; var statusSince = _statusSince; From 5601d002851e82ea919cd81923f3423ce7ef0bb1 Mon Sep 17 00:00:00 2001 From: Pat Murphy Date: Fri, 23 Jun 2017 07:29:55 -0700 Subject: [PATCH 31/62] Add various property validation in EmbedBuilder (#711) * Add various property validation in EmbedBuilder * Embed URI changes Changes property types for any URLs in Embeds to System.URI. Adding field name/value null/empty checks. * including property names in argumentexceptions * Adds overall embed length check --- .../Entities/Messages/Embed.cs | 7 +- .../Entities/Messages/EmbedAuthor.cs | 11 +- .../Entities/Messages/EmbedFooter.cs | 9 +- .../Entities/Messages/EmbedImage.cs | 11 +- .../Entities/Messages/EmbedProvider.cs | 7 +- .../Entities/Messages/EmbedThumbnail.cs | 11 +- .../Entities/Messages/EmbedVideo.cs | 9 +- .../Entities/Messages/IEmbed.cs | 2 +- src/Discord.Net.Rest/API/Common/Embed.cs | 2 +- .../API/Common/EmbedAuthor.cs | 9 +- .../API/Common/EmbedFooter.cs | 7 +- src/Discord.Net.Rest/API/Common/EmbedImage.cs | 5 +- .../API/Common/EmbedProvider.cs | 3 +- .../API/Common/EmbedThumbnail.cs | 5 +- src/Discord.Net.Rest/API/Common/EmbedVideo.cs | 3 +- .../Entities/Messages/EmbedBuilder.cs | 131 ++++++++++++++---- 16 files changed, 163 insertions(+), 69 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/Embed.cs b/src/Discord.Net.Core/Entities/Messages/Embed.cs index ebde05d4c..3210c22f5 100644 --- a/src/Discord.Net.Core/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Core/Entities/Messages/Embed.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; namespace Discord { @@ -10,7 +11,7 @@ namespace Discord public string Type { get; } public string Description { get; internal set; } - public string Url { get; internal set; } + public Uri Url { get; internal set; } public string Title { get; internal set; } public DateTimeOffset? Timestamp { get; internal set; } public Color? Color { get; internal set; } @@ -30,7 +31,7 @@ namespace Discord internal Embed(string type, string title, string description, - string url, + Uri url, DateTimeOffset? timestamp, Color? color, EmbedImage? image, @@ -56,6 +57,8 @@ namespace Discord Fields = fields; } + public int Length => Title?.Length + Author?.Name?.Length + Description?.Length + Footer?.Text?.Length + Fields.Sum(f => f.Name.Length + f.Value.ToString().Length) ?? 0; + public override string ToString() => Title; private string DebuggerDisplay => $"{Title} ({Type})"; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs index 142e36832..d1f2b9618 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { @@ -6,11 +7,11 @@ namespace Discord public struct EmbedAuthor { public string Name { get; internal set; } - public string Url { get; internal set; } - public string IconUrl { get; internal set; } - public string ProxyIconUrl { get; internal set; } + public Uri Url { get; internal set; } + public Uri IconUrl { get; internal set; } + public Uri ProxyIconUrl { get; internal set; } - internal EmbedAuthor(string name, string url, string iconUrl, string proxyIconUrl) + internal EmbedAuthor(string name, Uri url, Uri iconUrl, Uri proxyIconUrl) { Name = name; Url = url; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs index 33582070a..3c9bf35a9 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { @@ -6,10 +7,10 @@ namespace Discord public struct EmbedFooter { public string Text { get; internal set; } - public string IconUrl { get; internal set; } - public string ProxyUrl { get; internal set; } + public Uri IconUrl { get; internal set; } + public Uri ProxyUrl { get; internal set; } - internal EmbedFooter(string text, string iconUrl, string proxyUrl) + internal EmbedFooter(string text, Uri iconUrl, Uri proxyUrl) { Text = text; IconUrl = iconUrl; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index fa4847721..fd87e3db3 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -1,16 +1,17 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedImage { - public string Url { get; } - public string ProxyUrl { get; } + public Uri Url { get; } + public Uri ProxyUrl { get; } public int? Height { get; } public int? Width { get; } - internal EmbedImage(string url, string proxyUrl, int? height, int? width) + internal EmbedImage(Uri url, Uri proxyUrl, int? height, int? width) { Url = url; ProxyUrl = proxyUrl; @@ -19,6 +20,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url; + public override string ToString() => Url.ToString(); } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs index 943ac5b52..0b816b32b 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { @@ -6,9 +7,9 @@ namespace Discord public struct EmbedProvider { public string Name { get; } - public string Url { get; } + public Uri Url { get; } - internal EmbedProvider(string name, string url) + internal EmbedProvider(string name, Uri url) { Name = name; Url = url; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 4e125bf2a..b83401e07 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -1,16 +1,17 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedThumbnail { - public string Url { get; } - public string ProxyUrl { get; } + public Uri Url { get; } + public Uri ProxyUrl { get; } public int? Height { get; } public int? Width { get; } - internal EmbedThumbnail(string url, string proxyUrl, int? height, int? width) + internal EmbedThumbnail(Uri url, Uri proxyUrl, int? height, int? width) { Url = url; ProxyUrl = proxyUrl; @@ -19,6 +20,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url; + public override string ToString() => Url.ToString(); } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index eaf6f4a4c..9ea4b11d6 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -1,15 +1,16 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord { [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedVideo { - public string Url { get; } + public Uri Url { get; } public int? Height { get; } public int? Width { get; } - internal EmbedVideo(string url, int? height, int? width) + internal EmbedVideo(Uri url, int? height, int? width) { Url = url; Height = height; @@ -17,6 +18,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url; + public override string ToString() => Url.ToString(); } } diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index 5eef5ec9b..145b1fa3c 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -5,7 +5,7 @@ namespace Discord { public interface IEmbed { - string Url { get; } + Uri Url { get; } string Type { get; } string Title { get; } string Description { get; } diff --git a/src/Discord.Net.Rest/API/Common/Embed.cs b/src/Discord.Net.Rest/API/Common/Embed.cs index f6325efbb..110c5ec8d 100644 --- a/src/Discord.Net.Rest/API/Common/Embed.cs +++ b/src/Discord.Net.Rest/API/Common/Embed.cs @@ -13,7 +13,7 @@ namespace Discord.API [JsonProperty("description")] public string Description { get; set; } [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("color")] public uint? Color { get; set; } [JsonProperty("timestamp")] diff --git a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs index e69fee6eb..9ade58edf 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System; +using Newtonsoft.Json; namespace Discord.API { @@ -7,10 +8,10 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("icon_url")] - public string IconUrl { get; set; } + public Uri IconUrl { get; set; } [JsonProperty("proxy_icon_url")] - public string ProxyIconUrl { get; set; } + public Uri ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs index 27048972e..1e079d03e 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System; +using Newtonsoft.Json; namespace Discord.API { @@ -7,8 +8,8 @@ namespace Discord.API [JsonProperty("text")] public string Text { get; set; } [JsonProperty("icon_url")] - public string IconUrl { get; set; } + public Uri IconUrl { get; set; } [JsonProperty("proxy_icon_url")] - public string ProxyIconUrl { get; set; } + public Uri ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedImage.cs b/src/Discord.Net.Rest/API/Common/EmbedImage.cs index a5ef748f8..a12299783 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedImage.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedImage.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API @@ -6,9 +7,9 @@ namespace Discord.API internal class EmbedImage { [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("proxy_url")] - public string ProxyUrl { get; set; } + public Uri ProxyUrl { get; set; } [JsonProperty("height")] public Optional Height { get; set; } [JsonProperty("width")] diff --git a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs index 8c46b10dc..7ca87185c 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API @@ -8,6 +9,6 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs index f22953a25..b4ccd4b21 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API @@ -6,9 +7,9 @@ namespace Discord.API internal class EmbedThumbnail { [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("proxy_url")] - public string ProxyUrl { get; set; } + public Uri ProxyUrl { get; set; } [JsonProperty("height")] public Optional Height { get; set; } [JsonProperty("width")] diff --git a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs index 09e933784..2512151ed 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API @@ -6,7 +7,7 @@ namespace Discord.API internal class EmbedVideo { [JsonProperty("url")] - public string Url { get; set; } + public Uri Url { get; set; } [JsonProperty("height")] public Optional Height { get; set; } [JsonProperty("width")] diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs index 98a191379..a7c2436b0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs @@ -8,19 +8,42 @@ namespace Discord { private readonly Embed _embed; + public const int MaxFieldCount = 25; + public const int MaxTitleLength = 256; + public const int MaxDescriptionLength = 2048; + public const int MaxEmbedLength = 6000; // user bot limit is 2000, but we don't validate that here. + public EmbedBuilder() { _embed = new Embed("rich"); Fields = new List(); } - public string Title { get { return _embed.Title; } set { _embed.Title = value; } } - public string Description { get { return _embed.Description; } set { _embed.Description = value; } } - public string Url { get { return _embed.Url; } set { _embed.Url = value; } } - public string ThumbnailUrl { get { return _embed.Thumbnail?.Url; } set { _embed.Thumbnail = new EmbedThumbnail(value, null, null, null); } } - public string ImageUrl { get { return _embed.Image?.Url; } set { _embed.Image = new EmbedImage(value, null, null, null); } } - public DateTimeOffset? Timestamp { get { return _embed.Timestamp; } set { _embed.Timestamp = value; } } - public Color? Color { get { return _embed.Color; } set { _embed.Color = value; } } + public string Title + { + get => _embed.Title; + set + { + if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title)); + _embed.Title = value; + } + } + + public string Description + { + get => _embed.Description; + set + { + if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description)); + _embed.Description = value; + } + } + + public Uri Url { get => _embed.Url; set { _embed.Url = value; } } + public Uri ThumbnailUrl { get => _embed.Thumbnail?.Url; set { _embed.Thumbnail = new EmbedThumbnail(value, null, null, null); } } + public Uri ImageUrl { get => _embed.Image?.Url; set { _embed.Image = new EmbedImage(value, null, null, null); } } + public DateTimeOffset? Timestamp { get => _embed.Timestamp; set { _embed.Timestamp = value; } } + public Color? Color { get => _embed.Color; set { _embed.Color = value; } } public EmbedAuthorBuilder Author { get; set; } public EmbedFooterBuilder Footer { get; set; } @@ -30,8 +53,10 @@ namespace Discord get => _fields; set { - if (value != null) _fields = value; - else throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(value)); + + if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields)); + if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields)); + _fields = value; } } @@ -45,17 +70,17 @@ namespace Discord Description = description; return this; } - public EmbedBuilder WithUrl(string url) + public EmbedBuilder WithUrl(Uri url) { Url = url; return this; } - public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) + public EmbedBuilder WithThumbnailUrl(Uri thumbnailUrl) { ThumbnailUrl = thumbnailUrl; return this; } - public EmbedBuilder WithImageUrl(string imageUrl) + public EmbedBuilder WithImageUrl(Uri imageUrl) { ImageUrl = imageUrl; return this; @@ -107,7 +132,7 @@ namespace Discord .WithIsInline(false) .WithName(name) .WithValue(value); - Fields.Add(field); + AddField(field); return this; } public EmbedBuilder AddInlineField(string name, object value) @@ -116,11 +141,16 @@ namespace Discord .WithIsInline(true) .WithName(name) .WithValue(value); - Fields.Add(field); + AddField(field); return this; } public EmbedBuilder AddField(EmbedFieldBuilder field) { + if (Fields.Count >= MaxFieldCount) + { + throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(field)); + } + Fields.Add(field); return this; } @@ -128,7 +158,7 @@ namespace Discord { var field = new EmbedFieldBuilder(); action(field); - Fields.Add(field); + this.AddField(field); return this; } @@ -140,6 +170,12 @@ namespace Discord for (int i = 0; i < Fields.Count; i++) fields.Add(Fields[i].Build()); _embed.Fields = fields.ToImmutable(); + + if (_embed.Length > MaxEmbedLength) + { + throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}"); + } + return _embed; } public static implicit operator Embed(EmbedBuilder builder) => builder?.Build(); @@ -149,9 +185,32 @@ namespace Discord { private EmbedField _field; - public string Name { get { return _field.Name; } set { _field.Name = value; } } - public object Value { get { return _field.Value; } set { _field.Value = value.ToString(); } } - public bool IsInline { get { return _field.Inline; } set { _field.Inline = value; } } + public const int MaxFieldNameLength = 256; + public const int MaxFieldValueLength = 1024; + + public string Name + { + get => _field.Name; + set + { + if (string.IsNullOrEmpty(value)) throw new ArgumentException($"Field name must not be null or empty.", nameof(Name)); + if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); + _field.Name = value; + } + } + + public object Value + { + get => _field.Value; + set + { + var stringValue = value.ToString(); + if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); + if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); + _field.Value = stringValue; + } + } + public bool IsInline { get => _field.Inline; set { _field.Inline = value; } } public EmbedFieldBuilder() { @@ -182,9 +241,19 @@ namespace Discord { private EmbedAuthor _author; - public string Name { get { return _author.Name; } set { _author.Name = value; } } - public string Url { get { return _author.Url; } set { _author.Url = value; } } - public string IconUrl { get { return _author.IconUrl; } set { _author.IconUrl = value; } } + public const int MaxAuthorNameLength = 256; + + public string Name + { + get => _author.Name; + set + { + if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name)); + _author.Name = value; + } + } + public Uri Url { get => _author.Url; set { _author.Url = value; } } + public Uri IconUrl { get => _author.IconUrl; set { _author.IconUrl = value; } } public EmbedAuthorBuilder() { @@ -196,12 +265,12 @@ namespace Discord Name = name; return this; } - public EmbedAuthorBuilder WithUrl(string url) + public EmbedAuthorBuilder WithUrl(Uri url) { Url = url; return this; } - public EmbedAuthorBuilder WithIconUrl(string iconUrl) + public EmbedAuthorBuilder WithIconUrl(Uri iconUrl) { IconUrl = iconUrl; return this; @@ -215,8 +284,18 @@ namespace Discord { private EmbedFooter _footer; - public string Text { get { return _footer.Text; } set { _footer.Text = value; } } - public string IconUrl { get { return _footer.IconUrl; } set { _footer.IconUrl = value; } } + public const int MaxFooterTextLength = 2048; + + public string Text + { + get => _footer.Text; + set + { + if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text)); + _footer.Text = value; + } + } + public Uri IconUrl { get => _footer.IconUrl; set { _footer.IconUrl = value; } } public EmbedFooterBuilder() { @@ -228,7 +307,7 @@ namespace Discord Text = text; return this; } - public EmbedFooterBuilder WithIconUrl(string iconUrl) + public EmbedFooterBuilder WithIconUrl(Uri iconUrl) { IconUrl = iconUrl; return this; From d088d7b05c4e398333d928ad5a5940cb6f8870b0 Mon Sep 17 00:00:00 2001 From: Amir Zaidi Date: Fri, 23 Jun 2017 16:48:42 +0200 Subject: [PATCH 32/62] Add packetLoss argument for PCM streams, change FrameBytes to FrameSamplesPerChannel in OpusEncodeStream (#677) --- src/Discord.Net.Core/Audio/IAudioClient.cs | 4 ++-- src/Discord.Net.WebSocket/Audio/AudioClient.cs | 8 ++++---- src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs | 4 ++-- .../Audio/Streams/OpusEncodeStream.cs | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Discord.Net.Core/Audio/IAudioClient.cs b/src/Discord.Net.Core/Audio/IAudioClient.cs index 7373a8e4d..9be8ceef5 100644 --- a/src/Discord.Net.Core/Audio/IAudioClient.cs +++ b/src/Discord.Net.Core/Audio/IAudioClient.cs @@ -28,8 +28,8 @@ namespace Discord.Audio /// Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer. AudioOutStream CreateDirectOpusStream(); /// Creates a new outgoing stream accepting PCM (raw) data. - AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate = null, int bufferMillis = 1000); + AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate = null, int bufferMillis = 1000, int packetLoss = 30); /// Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer. - AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate = null); + AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate = null, int packetLoss = 30); } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 0ca45a557..1f33b3cc5 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -153,20 +153,20 @@ namespace Discord.Audio var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header return new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header (external input), passes } - public AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate, int bufferMillis) + public AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate, int bufferMillis, int packetLoss) { var outputStream = new OutputStream(ApiClient); //Ignores header var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes var bufferedStream = new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, _audioLogger); //Ignores header, generates header - return new OpusEncodeStream(bufferedStream, bitrate ?? (96 * 1024), application); //Generates header + return new OpusEncodeStream(bufferedStream, bitrate ?? (96 * 1024), application, packetLoss); //Generates header } - public AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate) + public AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate, int packetLoss) { var outputStream = new OutputStream(ApiClient); //Ignores header var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes - return new OpusEncodeStream(rtpWriter, bitrate ?? (96 * 1024), application); //Generates header + return new OpusEncodeStream(rtpWriter, bitrate ?? (96 * 1024), application, packetLoss); //Generates header } internal async Task CreateInputStreamAsync(ulong userId) diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs index a12854d69..1ff5a5d9a 100644 --- a/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs +++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs @@ -17,7 +17,7 @@ namespace Discord.Audio public AudioApplication Application { get; } public int BitRate { get;} - public OpusEncoder(int bitrate, AudioApplication application) + public OpusEncoder(int bitrate, AudioApplication application, int packetLoss) { if (bitrate < 1 || bitrate > DiscordVoiceAPIClient.MaxBitrate) throw new ArgumentOutOfRangeException(nameof(bitrate)); @@ -48,7 +48,7 @@ namespace Discord.Audio _ptr = CreateEncoder(SamplingRate, Channels, (int)opusApplication, out var error); CheckError(error); CheckError(EncoderCtl(_ptr, OpusCtl.SetSignal, (int)opusSignal)); - CheckError(EncoderCtl(_ptr, OpusCtl.SetPacketLossPercent, 30)); //% + CheckError(EncoderCtl(_ptr, OpusCtl.SetPacketLossPercent, packetLoss)); //% CheckError(EncoderCtl(_ptr, OpusCtl.SetInbandFEC, 1)); //True CheckError(EncoderCtl(_ptr, OpusCtl.SetBitrate, bitrate)); } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs index a7779a84c..f5883ad4b 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs @@ -8,18 +8,18 @@ namespace Discord.Audio.Streams public class OpusEncodeStream : AudioOutStream { public const int SampleRate = 48000; - + private readonly AudioStream _next; private readonly OpusEncoder _encoder; private readonly byte[] _buffer; private int _partialFramePos; private ushort _seq; private uint _timestamp; - - public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application) + + public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application, int packetLoss) { _next = next; - _encoder = new OpusEncoder(bitrate, application); + _encoder = new OpusEncoder(bitrate, application, packetLoss); _buffer = new byte[OpusConverter.FrameBytes]; } @@ -38,7 +38,7 @@ namespace Discord.Audio.Streams offset += OpusConverter.FrameBytes; count -= OpusConverter.FrameBytes; _seq++; - _timestamp += OpusConverter.FrameBytes; + _timestamp += OpusConverter.FrameSamplesPerChannel; } else if (_partialFramePos + count >= OpusConverter.FrameBytes) { @@ -53,7 +53,7 @@ namespace Discord.Audio.Streams count -= partialSize; _partialFramePos = 0; _seq++; - _timestamp += OpusConverter.FrameBytes; + _timestamp += OpusConverter.FrameSamplesPerChannel; } else { From 707ec9571786db888dc01e804a30265b6f355931 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Fri, 23 Jun 2017 11:01:44 -0400 Subject: [PATCH 33/62] Add SocketRole.Members property (#659) * Add SocketRole.Members property * Change Members to IEnumerable. --- src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index 57d913317..7d24d8e1c 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -1,6 +1,8 @@ using Discord.Rest; using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Role; @@ -22,6 +24,8 @@ namespace Discord.WebSocket public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public bool IsEveryone => Id == Guild.Id; public string Mention => MentionUtils.MentionRole(Id); + public IEnumerable Members + => Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id)); internal SocketRole(SocketGuild guild, ulong id) : base(guild.Discord, id) From ea685b4f2352b31abcbf769826499aea0e8fb793 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 23 Jun 2017 14:33:41 -0400 Subject: [PATCH 34/62] Add 'article' EmbedType --- src/Discord.Net.Core/Entities/Messages/EmbedType.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedType.cs b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs index e071e7dc8..ed39317a9 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedType.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs @@ -6,6 +6,7 @@ Link, Video, Image, - Gifv + Gifv, + Article } } From 36ed2b49f06f5f2736bf6b0937f4d002e2bb52ef Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Fri, 23 Jun 2017 20:46:59 +0200 Subject: [PATCH 35/62] PreconditionGroup quick fix It didn't make much sense --- src/Discord.Net.Commands/Info/CommandInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index ae350e592..f187460d5 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -68,7 +68,7 @@ namespace Discord.Commands { services = services ?? EmptyServiceProvider.Instance; - async Task CheckGroups(IEnumerable preconditions, string type) + async Task CheckGroups(IEnumerable preconditions, string type) { foreach (IGrouping preconditionGroup in preconditions.GroupBy(p => p.Group, StringComparer.Ordinal)) { @@ -78,7 +78,7 @@ namespace Discord.Commands { var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); if (!result.IsSuccess) - return PreconditionGroupResult.FromError($"{type} default precondition group failed.", new[] { result }); + return result; } } else @@ -243,4 +243,4 @@ namespace Discord.Commands return $"\"{Name}\" for {context.User} in {context.Channel}"; } } -} \ No newline at end of file +} From 444868b22d3d7787cad0fbeb01532b7dd1edd7ee Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Sat, 24 Jun 2017 02:39:02 +0200 Subject: [PATCH 36/62] Fix attempting to inject into static properties --- src/Discord.Net.Commands/Utilities/ReflectionUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 4cca0e864..ca3e01ebd 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -58,7 +58,7 @@ namespace Discord.Commands { foreach (var prop in ownerType.DeclaredProperties) { - if (prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null) + if (prop.GetMethod.IsStatic && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null) result.Add(prop); } ownerType = ownerType.BaseType.GetTypeInfo(); From 34917a35de48aea438aa4dda1a5789591753843a Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Sat, 24 Jun 2017 02:50:30 +0200 Subject: [PATCH 37/62] In my defense, it was 2:40 AM --- src/Discord.Net.Commands/Utilities/ReflectionUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index ca3e01ebd..d9956cdc0 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -58,7 +58,7 @@ namespace Discord.Commands { foreach (var prop in ownerType.DeclaredProperties) { - if (prop.GetMethod.IsStatic && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null) + if (!prop.GetMethod.IsStatic && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null) result.Add(prop); } ownerType = ownerType.BaseType.GetTypeInfo(); From cc390f03de4e583d9057091bc5e36e23b1eb472c Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Sat, 24 Jun 2017 02:56:57 +0200 Subject: [PATCH 38/62] Fix the off-chance that someone has a property without a getter --- src/Discord.Net.Commands/Utilities/ReflectionUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index d9956cdc0..b6ceda426 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -58,7 +58,7 @@ namespace Discord.Commands { foreach (var prop in ownerType.DeclaredProperties) { - if (!prop.GetMethod.IsStatic && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null) + if (prop.GetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null) result.Add(prop); } ownerType = ownerType.BaseType.GetTypeInfo(); From 107f1b580380c8c5906fd61a3bdd86bc30c6ebc0 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 24 Jun 2017 22:09:46 +0100 Subject: [PATCH 39/62] Add 'tweet' embed type --- src/Discord.Net.Core/Entities/Messages/EmbedType.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedType.cs b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs index ed39317a9..469e968a5 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedType.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs @@ -7,6 +7,7 @@ Video, Image, Gifv, - Article + Article, + Tweet } } From 1d612f15c859b0a5d9e0880e6817f99417ff21ac Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 27 Jun 2017 08:49:46 -0400 Subject: [PATCH 40/62] ToString on types of IEmote should return a chat formatted string --- src/Discord.Net.Core/Entities/Emotes/Emoji.cs | 2 ++ src/Discord.Net.Core/Entities/Emotes/Emote.cs | 2 +- src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs index 5c1969613..07cee10a9 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs @@ -19,5 +19,7 @@ /// The unicode representation of this emote. /// public string Name { get; } + + public override string ToString() => Name; } } diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs index b1ca272eb..76c20a77d 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -58,6 +58,6 @@ namespace Discord } private string DebuggerDisplay => $"{Name} ({Id})"; - public override string ToString() => Name; + public override string ToString() => $"<:{Name}:{Id}>"; } } diff --git a/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs b/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs index e883c707e..8d776a4cd 100644 --- a/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs @@ -20,7 +20,7 @@ namespace Discord RoleIds = roleIds; } - public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; + public override string ToString() => $"<:{Name}:{Id}>"; } } From 1ce1c019b399b83fb7ed946ec7908d5d37b567fb Mon Sep 17 00:00:00 2001 From: Christopher F Date: Thu, 29 Jun 2017 16:01:59 -0400 Subject: [PATCH 41/62] Add support for audit log reasons (#708) * Add support for audit log reasons * Made changes per discussion --- src/Discord.Net.Core/Entities/Guilds/IGuild.cs | 4 ++-- src/Discord.Net.Core/Entities/Users/IGuildUser.cs | 2 +- src/Discord.Net.Core/Net/Rest/IRestClient.cs | 6 +++--- src/Discord.Net.Core/RequestOptions.cs | 4 ++++ src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs | 1 + src/Discord.Net.Rest/DiscordRestApiClient.cs | 8 +++++--- src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs | 4 ++-- src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 8 ++++---- src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs | 4 ++-- .../Entities/Users/RestWebhookUser.cs | 2 +- src/Discord.Net.Rest/Entities/Users/UserHelper.cs | 4 ++-- src/Discord.Net.Rest/Net/DefaultRestClient.cs | 11 ++++++++--- .../Net/Queue/Requests/JsonRestRequest.cs | 2 +- .../Net/Queue/Requests/MultipartRestRequest.cs | 2 +- .../Net/Queue/Requests/RestRequest.cs | 2 +- .../Entities/Guilds/SocketGuild.cs | 8 ++++---- .../Entities/Users/SocketGuildUser.cs | 4 ++-- .../Entities/Users/SocketWebhookUser.cs | 2 +- test/Discord.Net.Tests/Net/CachedRestClient.cs | 6 +++--- 19 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 506cbd3e4..7874f5fd1 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -66,10 +66,10 @@ namespace Discord Task> GetBansAsync(RequestOptions options = null); /// Bans the provided user from this guild and optionally prunes their recent messages. /// The number of days to remove messages from this user for - must be between [0, 7] - Task AddBanAsync(IUser user, int pruneDays = 0, RequestOptions options = null); + Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null); /// Bans the provided user id from this guild and optionally prunes their recent messages. /// The number of days to remove messages from this user for - must be between [0, 7] - Task AddBanAsync(ulong userId, int pruneDays = 0, RequestOptions options = null); + Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null); /// Unbans the provided user if it is currently banned. Task RemoveBanAsync(IUser user, RequestOptions options = null); /// Unbans the provided user id if it is currently banned. diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index cd9516395..57cad1333 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -25,7 +25,7 @@ namespace Discord ChannelPermissions GetPermissions(IGuildChannel channel); /// Kicks this user from this guild. - Task KickAsync(RequestOptions options = null); + Task KickAsync(string reason = null, RequestOptions options = null); /// Modifies this user's properties in this guild. Task ModifyAsync(Action func, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Net/Rest/IRestClient.cs b/src/Discord.Net.Core/Net/Rest/IRestClient.cs index b5f136cb0..addfa9061 100644 --- a/src/Discord.Net.Core/Net/Rest/IRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/IRestClient.cs @@ -9,8 +9,8 @@ namespace Discord.Net.Rest void SetHeader(string key, string value); void SetCancelToken(CancellationToken cancelToken); - Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly = false); - Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly = false); - Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly = false); + Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly = false, string reason = null); + Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly = false, string reason = null); + Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly = false, string reason = null); } } diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 4f5910c53..5f3a8814b 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -14,6 +14,10 @@ namespace Discord public CancellationToken CancelToken { get; set; } = CancellationToken.None; public RetryMode? RetryMode { get; set; } public bool HeaderOnly { get; internal set; } + /// + /// The reason for this action in the guild's audit log + /// + public string AuditLogReason { get; set; } internal bool IgnoreState { get; set; } internal string BucketId { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs index 0c148fe70..f0432e517 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs @@ -4,5 +4,6 @@ namespace Discord.API.Rest internal class CreateGuildBanParams { public Optional DeleteMessageDays { get; set; } + public string Reason { get; set; } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index a632e5d42..621c2d0e2 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -803,7 +803,8 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}", ids, options: options).ConfigureAwait(false); + string reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={args.Reason}"; + await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false); } public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null) { @@ -980,14 +981,15 @@ namespace Discord.API Expression> endpoint = () => $"guilds/{guildId}/members?limit={limit}&after={afterUserId}"; return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } - public async Task RemoveGuildMemberAsync(ulong guildId, ulong userId, RequestOptions options = null) + public async Task RemoveGuildMemberAsync(ulong guildId, ulong userId, string reason, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}", ids, options: options).ConfigureAwait(false); + reason = string.IsNullOrWhiteSpace(reason) ? "" : $"?reason={reason}"; + await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}{reason}", ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildMemberAsync(ulong guildId, ulong userId, Rest.ModifyGuildMemberParams args, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 98303cea6..5cfb1e566 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -107,9 +107,9 @@ namespace Discord.Rest } public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client, - ulong userId, int pruneDays, RequestOptions options) + ulong userId, int pruneDays, string reason, RequestOptions options) { - var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays }; + var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays, Reason = reason }; await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args, options).ConfigureAwait(false); } public static async Task RemoveBanAsync(IGuild guild, BaseDiscordClient client, diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 8b5598ffe..11971a5c1 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -137,10 +137,10 @@ namespace Discord.Rest public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); - public Task AddBanAsync(IUser user, int pruneDays = 0, RequestOptions options = null) - => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, options); - public Task AddBanAsync(ulong userId, int pruneDays = 0, RequestOptions options = null) - => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, options); + public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); + public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); public Task RemoveBanAsync(IUser user, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index f6db057f2..2fce5f619 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -85,8 +85,8 @@ namespace Discord.Rest else if (args.RoleIds.IsSpecified) UpdateRoles(args.RoleIds.Value.ToArray()); } - public Task KickAsync(RequestOptions options = null) - => UserHelper.KickAsync(this, Discord, options); + public Task KickAsync(string reason = null, RequestOptions options = null) + => UserHelper.KickAsync(this, Discord, reason, options); /// public Task AddRoleAsync(IRole role, RequestOptions options = null) => AddRolesAsync(new[] { role }, options); diff --git a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs index ae794becc..bb44f2777 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs @@ -45,7 +45,7 @@ namespace Discord.Rest GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); - Task IGuildUser.KickAsync(RequestOptions options) + Task IGuildUser.KickAsync(string reason, RequestOptions options) { throw new NotSupportedException("Webhook users cannot be kicked."); } diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index 82e59227d..562cfaae8 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -53,9 +53,9 @@ namespace Discord.Rest } public static async Task KickAsync(IGuildUser user, BaseDiscordClient client, - RequestOptions options) + string reason, RequestOptions options) { - await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, options).ConfigureAwait(false); + await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, reason, options).ConfigureAwait(false); } public static async Task CreateDMChannelAsync(IUser user, BaseDiscordClient client, diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index 20fbe2278..a54107829 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -62,26 +62,31 @@ namespace Discord.Net.Rest _cancelToken = cancelToken; } - public async Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly) + public async Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly, string reason = null) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) + { + if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason)); return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); + } } - public async Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly) + public async Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly, string reason = null) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { + if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason)); restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json"); return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); } } - public async Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly) + public async Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { + if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason)); var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)); if (multipartParams != null) { diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs index 83c5e0eb5..2949bab3c 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs @@ -15,7 +15,7 @@ namespace Discord.Net.Queue public override async Task SendAsync() { - return await Client.SendAsync(Method, Endpoint, Json, Options.CancelToken, Options.HeaderOnly).ConfigureAwait(false); + return await Client.SendAsync(Method, Endpoint, Json, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/MultipartRestRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/MultipartRestRequest.cs index 424a5325e..c8d97bbdf 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/MultipartRestRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/MultipartRestRequest.cs @@ -16,7 +16,7 @@ namespace Discord.Net.Queue public override async Task SendAsync() { - return await Client.SendAsync(Method, Endpoint, MultipartParams, Options.CancelToken, Options.HeaderOnly).ConfigureAwait(false); + return await Client.SendAsync(Method, Endpoint, MultipartParams, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs index 7f358e786..8f160273a 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs @@ -28,7 +28,7 @@ namespace Discord.Net.Queue public virtual async Task SendAsync() { - return await Client.SendAsync(Method, Endpoint, Options.CancelToken, Options.HeaderOnly).ConfigureAwait(false); + return await Client.SendAsync(Method, Endpoint, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 5358605c8..aae18be36 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -281,10 +281,10 @@ namespace Discord.WebSocket public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); - public Task AddBanAsync(IUser user, int pruneDays = 0, RequestOptions options = null) - => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, options); - public Task AddBanAsync(ulong userId, int pruneDays = 0, RequestOptions options = null) - => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, options); + public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); + public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); public Task RemoveBanAsync(IUser user, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 05aa132a5..844b0c7f4 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -122,8 +122,8 @@ namespace Discord.WebSocket public Task ModifyAsync(Action func, RequestOptions options = null) => UserHelper.ModifyAsync(this, Discord, func, options); - public Task KickAsync(RequestOptions options = null) - => UserHelper.KickAsync(this, Discord, options); + public Task KickAsync(string reason = null, RequestOptions options = null) + => UserHelper.KickAsync(this, Discord, reason, options); /// public Task AddRoleAsync(IRole role, RequestOptions options = null) => AddRolesAsync(new[] { role }, options); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index c34f866cb..78a29639b 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -47,7 +47,7 @@ namespace Discord.WebSocket GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); - Task IGuildUser.KickAsync(RequestOptions options) + Task IGuildUser.KickAsync(string reason, RequestOptions options) { throw new NotSupportedException("Webhook users cannot be kicked."); } diff --git a/test/Discord.Net.Tests/Net/CachedRestClient.cs b/test/Discord.Net.Tests/Net/CachedRestClient.cs index f4b3bb279..4bc8a386a 100644 --- a/test/Discord.Net.Tests/Net/CachedRestClient.cs +++ b/test/Discord.Net.Tests/Net/CachedRestClient.cs @@ -66,7 +66,7 @@ namespace Discord.Net _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; } - public async Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly) + public async Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly, string reason = null) { if (method != "GET") throw new InvalidOperationException("This RestClient only supports GET requests."); @@ -75,11 +75,11 @@ namespace Discord.Net var bytes = await _blobCache.DownloadUrl(uri, _headers); return new RestResponse(HttpStatusCode.OK, _headers, new MemoryStream(bytes)); } - public Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly) + public Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly, string reason = null) { throw new InvalidOperationException("This RestClient does not support payloads."); } - public Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly) + public Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null) { throw new InvalidOperationException("This RestClient does not support multipart requests."); } From 7837c4862cab32ecc432b3c6794277d92d89647d Mon Sep 17 00:00:00 2001 From: Christopher F Date: Thu, 29 Jun 2017 16:38:05 -0400 Subject: [PATCH 42/62] Revert change of all Url types on IEmbed to string (#724) --- .../Entities/Messages/Embed.cs | 4 +- .../Entities/Messages/EmbedAuthor.cs | 8 +-- .../Entities/Messages/EmbedFooter.cs | 6 +- .../Entities/Messages/EmbedImage.cs | 6 +- .../Entities/Messages/EmbedProvider.cs | 4 +- .../Entities/Messages/EmbedThumbnail.cs | 6 +- .../Entities/Messages/EmbedVideo.cs | 4 +- .../Entities/Messages/IEmbed.cs | 2 +- src/Discord.Net.Rest/API/Common/Embed.cs | 2 +- .../API/Common/EmbedAuthor.cs | 6 +- .../API/Common/EmbedFooter.cs | 4 +- src/Discord.Net.Rest/API/Common/EmbedImage.cs | 4 +- .../API/Common/EmbedProvider.cs | 2 +- .../API/Common/EmbedThumbnail.cs | 4 +- src/Discord.Net.Rest/API/Common/EmbedVideo.cs | 2 +- .../Entities/Messages/EmbedBuilder.cs | 72 +++++++++++++++---- 16 files changed, 92 insertions(+), 44 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/Embed.cs b/src/Discord.Net.Core/Entities/Messages/Embed.cs index 9b72b9194..5fae7acde 100644 --- a/src/Discord.Net.Core/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Core/Entities/Messages/Embed.cs @@ -11,7 +11,7 @@ namespace Discord public EmbedType Type { get; } public string Description { get; internal set; } - public Uri Url { get; internal set; } + public string Url { get; internal set; } public string Title { get; internal set; } public DateTimeOffset? Timestamp { get; internal set; } public Color? Color { get; internal set; } @@ -31,7 +31,7 @@ namespace Discord internal Embed(EmbedType type, string title, string description, - Uri url, + string url, DateTimeOffset? timestamp, Color? color, EmbedImage? image, diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs index d1f2b9618..c59473704 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs @@ -7,11 +7,11 @@ namespace Discord public struct EmbedAuthor { public string Name { get; internal set; } - public Uri Url { get; internal set; } - public Uri IconUrl { get; internal set; } - public Uri ProxyIconUrl { get; internal set; } + public string Url { get; internal set; } + public string IconUrl { get; internal set; } + public string ProxyIconUrl { get; internal set; } - internal EmbedAuthor(string name, Uri url, Uri iconUrl, Uri proxyIconUrl) + internal EmbedAuthor(string name, string url, string iconUrl, string proxyIconUrl) { Name = name; Url = url; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs index 3c9bf35a9..29d85cd90 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs @@ -7,10 +7,10 @@ namespace Discord public struct EmbedFooter { public string Text { get; internal set; } - public Uri IconUrl { get; internal set; } - public Uri ProxyUrl { get; internal set; } + public string IconUrl { get; internal set; } + public string ProxyUrl { get; internal set; } - internal EmbedFooter(string text, Uri iconUrl, Uri proxyUrl) + internal EmbedFooter(string text, string iconUrl, string proxyUrl) { Text = text; IconUrl = iconUrl; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index fd87e3db3..f21d42c0c 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -6,12 +6,12 @@ namespace Discord [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedImage { - public Uri Url { get; } - public Uri ProxyUrl { get; } + public string Url { get; } + public string ProxyUrl { get; } public int? Height { get; } public int? Width { get; } - internal EmbedImage(Uri url, Uri proxyUrl, int? height, int? width) + internal EmbedImage(string url, string proxyUrl, int? height, int? width) { Url = url; ProxyUrl = proxyUrl; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs index 0b816b32b..24722b158 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs @@ -7,9 +7,9 @@ namespace Discord public struct EmbedProvider { public string Name { get; } - public Uri Url { get; } + public string Url { get; } - internal EmbedProvider(string name, Uri url) + internal EmbedProvider(string name, string url) { Name = name; Url = url; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index b83401e07..209a93e37 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -6,12 +6,12 @@ namespace Discord [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedThumbnail { - public Uri Url { get; } - public Uri ProxyUrl { get; } + public string Url { get; } + public string ProxyUrl { get; } public int? Height { get; } public int? Width { get; } - internal EmbedThumbnail(Uri url, Uri proxyUrl, int? height, int? width) + internal EmbedThumbnail(string url, string proxyUrl, int? height, int? width) { Url = url; ProxyUrl = proxyUrl; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index 9ea4b11d6..f00681d89 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -6,11 +6,11 @@ namespace Discord [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedVideo { - public Uri Url { get; } + public string Url { get; } public int? Height { get; } public int? Width { get; } - internal EmbedVideo(Uri url, int? height, int? width) + internal EmbedVideo(string url, int? height, int? width) { Url = url; Height = height; diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index 01ea2b248..f390c4c28 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -5,7 +5,7 @@ namespace Discord { public interface IEmbed { - Uri Url { get; } + string Url { get; } string Title { get; } string Description { get; } EmbedType Type { get; } diff --git a/src/Discord.Net.Rest/API/Common/Embed.cs b/src/Discord.Net.Rest/API/Common/Embed.cs index ebce9757b..1c9fa34e2 100644 --- a/src/Discord.Net.Rest/API/Common/Embed.cs +++ b/src/Discord.Net.Rest/API/Common/Embed.cs @@ -12,7 +12,7 @@ namespace Discord.API [JsonProperty("description")] public string Description { get; set; } [JsonProperty("url")] - public Uri Url { get; set; } + public string Url { get; set; } [JsonProperty("color")] public uint? Color { get; set; } [JsonProperty("type"), JsonConverter(typeof(StringEnumConverter))] diff --git a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs index 9ade58edf..4381a9da3 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs @@ -8,10 +8,10 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } [JsonProperty("url")] - public Uri Url { get; set; } + public string Url { get; set; } [JsonProperty("icon_url")] - public Uri IconUrl { get; set; } + public string IconUrl { get; set; } [JsonProperty("proxy_icon_url")] - public Uri ProxyIconUrl { get; set; } + public string ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs index 1e079d03e..3dd7020d9 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs @@ -8,8 +8,8 @@ namespace Discord.API [JsonProperty("text")] public string Text { get; set; } [JsonProperty("icon_url")] - public Uri IconUrl { get; set; } + public string IconUrl { get; set; } [JsonProperty("proxy_icon_url")] - public Uri ProxyIconUrl { get; set; } + public string ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedImage.cs b/src/Discord.Net.Rest/API/Common/EmbedImage.cs index a12299783..c6b3562a3 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedImage.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedImage.cs @@ -7,9 +7,9 @@ namespace Discord.API internal class EmbedImage { [JsonProperty("url")] - public Uri Url { get; set; } + public string Url { get; set; } [JsonProperty("proxy_url")] - public Uri ProxyUrl { get; set; } + public string ProxyUrl { get; set; } [JsonProperty("height")] public Optional Height { get; set; } [JsonProperty("width")] diff --git a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs index 7ca87185c..1658eda1a 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs @@ -9,6 +9,6 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } [JsonProperty("url")] - public Uri Url { get; set; } + public string Url { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs index b4ccd4b21..993beb72b 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs @@ -7,9 +7,9 @@ namespace Discord.API internal class EmbedThumbnail { [JsonProperty("url")] - public Uri Url { get; set; } + public string Url { get; set; } [JsonProperty("proxy_url")] - public Uri ProxyUrl { get; set; } + public string ProxyUrl { get; set; } [JsonProperty("height")] public Optional Height { get; set; } [JsonProperty("width")] diff --git a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs index 2512151ed..610cf58a8 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs @@ -7,7 +7,7 @@ namespace Discord.API internal class EmbedVideo { [JsonProperty("url")] - public Uri Url { get; set; } + public string Url { get; set; } [JsonProperty("height")] public Optional Height { get; set; } [JsonProperty("width")] diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs index be5ef7f32..c299bd1a1 100644 --- a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs @@ -39,9 +39,33 @@ namespace Discord } } - public Uri Url { get => _embed.Url; set { _embed.Url = value; } } - public Uri ThumbnailUrl { get => _embed.Thumbnail?.Url; set { _embed.Thumbnail = new EmbedThumbnail(value, null, null, null); } } - public Uri ImageUrl { get => _embed.Image?.Url; set { _embed.Image = new EmbedImage(value, null, null, null); } } + public string Url + { + get => _embed.Url; + set + { + if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + _embed.Url = value; + } + } + public string ThumbnailUrl + { + get => _embed.Thumbnail?.Url; + set + { + if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); + _embed.Thumbnail = new EmbedThumbnail(value, null, null, null); + } + } + public string ImageUrl + { + get => _embed.Image?.Url; + set + { + if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); + _embed.Image = new EmbedImage(value, null, null, null); + } + } public DateTimeOffset? Timestamp { get => _embed.Timestamp; set { _embed.Timestamp = value; } } public Color? Color { get => _embed.Color; set { _embed.Color = value; } } @@ -70,17 +94,17 @@ namespace Discord Description = description; return this; } - public EmbedBuilder WithUrl(Uri url) + public EmbedBuilder WithUrl(string url) { Url = url; return this; } - public EmbedBuilder WithThumbnailUrl(Uri thumbnailUrl) + public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) { ThumbnailUrl = thumbnailUrl; return this; } - public EmbedBuilder WithImageUrl(Uri imageUrl) + public EmbedBuilder WithImageUrl(string imageUrl) { ImageUrl = imageUrl; return this; @@ -252,8 +276,24 @@ namespace Discord _author.Name = value; } } - public Uri Url { get => _author.Url; set { _author.Url = value; } } - public Uri IconUrl { get => _author.IconUrl; set { _author.IconUrl = value; } } + public string Url + { + get => _author.Url; + set + { + if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + _author.Url = value; + } + } + public string IconUrl + { + get => _author.IconUrl; + set + { + if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + _author.IconUrl = value; + } + } public EmbedAuthorBuilder() { @@ -265,12 +305,12 @@ namespace Discord Name = name; return this; } - public EmbedAuthorBuilder WithUrl(Uri url) + public EmbedAuthorBuilder WithUrl(string url) { Url = url; return this; } - public EmbedAuthorBuilder WithIconUrl(Uri iconUrl) + public EmbedAuthorBuilder WithIconUrl(string iconUrl) { IconUrl = iconUrl; return this; @@ -295,7 +335,15 @@ namespace Discord _footer.Text = value; } } - public Uri IconUrl { get => _footer.IconUrl; set { _footer.IconUrl = value; } } + public string IconUrl + { + get => _footer.IconUrl; + set + { + if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + _footer.IconUrl = value; + } + } public EmbedFooterBuilder() { @@ -307,7 +355,7 @@ namespace Discord Text = text; return this; } - public EmbedFooterBuilder WithIconUrl(Uri iconUrl) + public EmbedFooterBuilder WithIconUrl(string iconUrl) { IconUrl = iconUrl; return this; From 41222eafeb57822d42015060a807d482187cc047 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Thu, 29 Jun 2017 16:40:40 -0400 Subject: [PATCH 43/62] Add color presets. (#725) * Add DiscordColors struct * Moved presets to Discord.Color --- src/Discord.Net.Core/Entities/Roles/Color.cs | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 3250acb2d..89e76df6d 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -8,6 +8,46 @@ namespace Discord { /// Gets the default user color value. public static readonly Color Default = new Color(0); + /// Gets the teal color value + public static readonly Color Teal = new Color(0x1ABC9C); + /// Gets the dark teal color value + public static readonly Color DarkTeal = new Color(0x11806A); + /// Gets the green color value + public static readonly Color Green = new Color(0x2ECC71); + /// Gets the dark green color value + public static readonly Color DarkGreen = new Color(0x1F8B4C); + /// Gets the blue color value + public static readonly Color Blue = new Color(0x3498DB); + /// Gets the dark blue color value + public static readonly Color DarkBlue = new Color(0x206694); + /// Gets the purple color value + public static readonly Color Purple = new Color(0x9B59B6); + /// Gets the dark purple color value + public static readonly Color DarkPurple = new Color(0x71368A); + /// Gets the magenta color value + public static readonly Color Magenta = new Color(0xE91E63); + /// Gets the dark magenta color value + public static readonly Color DarkMagenta = new Color(0xAD1457); + /// Gets the gold color value + public static readonly Color Gold = new Color(0xF1C40F); + /// Gets the light orange color value + public static readonly Color LightOrange = new Color(0xC27C0E); + /// Gets the orange color value + public static readonly Color Orange = new Color(0xE67E22); + /// Gets the dark orange color value + public static readonly Color DarkOrange = new Color(0xA84300); + /// Gets the red color value + public static readonly Color Red = new Color(0xE74C3C); + /// Gets the dark red color value + public static readonly Color DarkRed = new Color(0x992D22); + /// Gets the light grey color value + public static readonly Color LightGrey = new Color(0x979C9F); + /// Gets the lighter grey color value + public static readonly Color LighterGrey = new Color(0x95A5A6); + /// Gets the dark grey color value + public static readonly Color DarkGrey = new Color(0x607D8B); + /// Gets the darker grey color value + public static readonly Color DarkerGrey = new Color(0x546E7A); /// Gets the encoded value for this color. public uint RawValue { get; } From 032aba91291e1423af8f040733cc82e706ac6e7d Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Thu, 29 Jun 2017 21:43:55 +0100 Subject: [PATCH 44/62] Update commands with C#7 features (#689) * C#7 features in commands, CommandInfo in ModuleBase * Update TypeReaders with C#7 features and IServiceProvider * Add best-choice command selection to CommandService * Normalize type reader scores correctly * Fix logic error and rebase onto dev * Change GetMethod for SetMethod in ReflectionUtils Should be checking against setters, not getters * Ensure args/params scores do not overwhelm Priority * Remove possibility of NaNs --- .../Builders/CommandBuilder.cs | 4 +- .../Builders/ModuleBuilder.cs | 2 +- .../Builders/ModuleClassBuilder.cs | 120 ++++++++++------- src/Discord.Net.Commands/CommandMatch.cs | 4 +- src/Discord.Net.Commands/CommandParser.cs | 9 +- src/Discord.Net.Commands/CommandService.cs | 123 ++++++++++++------ src/Discord.Net.Commands/IModuleBase.cs | 4 +- src/Discord.Net.Commands/Info/CommandInfo.cs | 10 +- .../Info/ParameterInfo.cs | 5 +- src/Discord.Net.Commands/ModuleBase.cs | 12 +- src/Discord.Net.Commands/PrimitiveParsers.cs | 5 - .../Readers/ChannelTypeReader.cs | 2 +- .../Readers/EnumTypeReader.cs | 5 +- .../Readers/MessageTypeReader.cs | 8 +- .../Readers/PrimitiveTypeReader.cs | 18 ++- .../Readers/RoleTypeReader.cs | 2 +- .../Readers/TypeReader.cs | 5 +- .../Readers/UserTypeReader.cs | 5 +- .../Utilities/ReflectionUtils.cs | 2 +- 19 files changed, 206 insertions(+), 139 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 8c2207f10..30db1fa62 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -13,7 +13,7 @@ namespace Discord.Commands.Builders private readonly List _aliases; public ModuleBuilder Module { get; } - internal Func Callback { get; set; } + internal Func Callback { get; set; } public string Name { get; set; } public string Summary { get; set; } @@ -36,7 +36,7 @@ namespace Discord.Commands.Builders _aliases = new List(); } //User-defined - internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func callback) + internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func callback) : this(module) { Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index d79239057..525907b8b 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -74,7 +74,7 @@ namespace Discord.Commands.Builders _preconditions.Add(precondition); return this; } - public ModuleBuilder AddCommand(string primaryAlias, Func callback, Action createFunc) + public ModuleBuilder AddCommand(string primaryAlias, Func callback, Action createFunc) { var builder = new CommandBuilder(this, primaryAlias, callback); createFunc(builder); diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index fe35e3b2a..401396900 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -12,25 +12,42 @@ namespace Discord.Commands { private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo(); - public static IEnumerable Search(Assembly assembly) + public static async Task> SearchAsync(Assembly assembly, CommandService service) { - foreach (var type in assembly.ExportedTypes) + bool IsLoadableModule(TypeInfo info) { - var typeInfo = type.GetTypeInfo(); - if (IsValidModuleDefinition(typeInfo) && - !typeInfo.IsDefined(typeof(DontAutoLoadAttribute))) + return info.DeclaredMethods.Any(x => x.GetCustomAttribute() != null) && + info.GetCustomAttribute() == null; + } + + List result = new List(); + + foreach (var typeInfo in assembly.DefinedTypes) + { + if (typeInfo.IsPublic) + { + if (IsValidModuleDefinition(typeInfo) && + !typeInfo.IsDefined(typeof(DontAutoLoadAttribute))) + { + result.Add(typeInfo); + } + } + else if (IsLoadableModule(typeInfo)) { - yield return typeInfo; + await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}."); } } + + return result; } - public static Dictionary Build(CommandService service, params TypeInfo[] validTypes) => Build(validTypes, service); - public static Dictionary Build(IEnumerable validTypes, CommandService service) + + public static Task> BuildAsync(CommandService service, params TypeInfo[] validTypes) => BuildAsync(validTypes, service); + public static async Task> BuildAsync(IEnumerable validTypes, CommandService service) { /*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); @@ -48,10 +65,13 @@ namespace Discord.Commands BuildModule(module, typeInfo, service); BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); + builtTypes.Add(typeInfo); result[typeInfo.AsType()] = module.Build(service); } + await service._cmdLogger.DebugAsync($"Successfully built and loaded {builtTypes.Count} modules.").ConfigureAwait(false); + return result; } @@ -128,26 +148,32 @@ namespace Discord.Commands foreach (var attribute in attributes) { - // TODO: C#7 type switch - if (attribute is CommandAttribute) + switch (attribute) { - var cmdAttr = attribute as CommandAttribute; - builder.AddAliases(cmdAttr.Text); - builder.RunMode = cmdAttr.RunMode; - builder.Name = builder.Name ?? cmdAttr.Text; + case CommandAttribute command: + builder.AddAliases(command.Text); + builder.RunMode = command.RunMode; + builder.Name = builder.Name ?? command.Text; + break; + case NameAttribute name: + builder.Name = name.Text; + break; + case PriorityAttribute priority: + builder.Priority = priority.Priority; + break; + case SummaryAttribute summary: + builder.Summary = summary.Text; + break; + case RemarksAttribute remarks: + builder.Remarks = remarks.Text; + break; + case AliasAttribute alias: + builder.AddAliases(alias.Aliases); + break; + case PreconditionAttribute precondition: + builder.AddPrecondition(precondition); + break; } - else if (attribute is NameAttribute) - builder.Name = (attribute as NameAttribute).Text; - else if (attribute is PriorityAttribute) - builder.Priority = (attribute as PriorityAttribute).Priority; - else if (attribute is SummaryAttribute) - builder.Summary = (attribute as SummaryAttribute).Text; - else if (attribute is RemarksAttribute) - builder.Remarks = (attribute as RemarksAttribute).Text; - else if (attribute is AliasAttribute) - builder.AddAliases((attribute as AliasAttribute).Aliases); - else if (attribute is PreconditionAttribute) - builder.AddPrecondition(attribute as PreconditionAttribute); } if (builder.Name == null) @@ -165,19 +191,19 @@ namespace Discord.Commands var createInstance = ReflectionUtils.CreateBuilder(typeInfo, service); - builder.Callback = async (ctx, args, map) => + builder.Callback = async (ctx, args, map, cmd) => { var instance = createInstance(map); instance.SetContext(ctx); try { - instance.BeforeExecute(); + instance.BeforeExecute(cmd); var task = method.Invoke(instance, args) as Task ?? Task.Delay(0); await task.ConfigureAwait(false); } finally { - instance.AfterExecute(); + instance.AfterExecute(cmd); (instance as IDisposable)?.Dispose(); } }; @@ -195,24 +221,24 @@ namespace Discord.Commands foreach (var attribute in attributes) { - // TODO: C#7 type switch - if (attribute is SummaryAttribute) - builder.Summary = (attribute as SummaryAttribute).Text; - else if (attribute is OverrideTypeReaderAttribute) - builder.TypeReader = GetTypeReader(service, paramType, (attribute as OverrideTypeReaderAttribute).TypeReader); - else if (attribute is ParameterPreconditionAttribute) - builder.AddPrecondition(attribute as ParameterPreconditionAttribute); - else if (attribute is ParamArrayAttribute) - { - builder.IsMultiple = true; - paramType = paramType.GetElementType(); - } - else if (attribute is RemainderAttribute) + switch (attribute) { - if (position != count-1) - throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}"); - - builder.IsRemainder = true; + case SummaryAttribute summary: + builder.Summary = summary.Text; + break; + case OverrideTypeReaderAttribute typeReader: + builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader); + break; + case ParamArrayAttribute _: + builder.IsMultiple = true; + paramType = paramType.GetElementType(); + break; + case RemainderAttribute _: + if (position != count - 1) + throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}"); + + builder.IsRemainder = true; + break; } } diff --git a/src/Discord.Net.Commands/CommandMatch.cs b/src/Discord.Net.Commands/CommandMatch.cs index 74c0de73e..d2bd9ef03 100644 --- a/src/Discord.Net.Commands/CommandMatch.cs +++ b/src/Discord.Net.Commands/CommandMatch.cs @@ -18,8 +18,8 @@ namespace Discord.Commands public Task CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) => Command.CheckPreconditionsAsync(context, services); - public Task ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult preconditionResult = null) - => Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult); + public Task ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null) + => Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult, services); public Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) => Command.ExecuteAsync(context, argList, paramList, services); public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 5b4ba2480..394f8589d 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Immutable; using System.Text; using System.Threading.Tasks; @@ -13,7 +14,7 @@ namespace Discord.Commands QuotedParameter } - public static async Task ParseArgs(CommandInfo command, ICommandContext context, string input, int startPos) + public static async Task ParseArgs(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos) { ParameterInfo curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); @@ -110,7 +111,7 @@ namespace Discord.Commands if (curParam == null) return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters."); - var typeReaderResult = await curParam.Parse(context, argString).ConfigureAwait(false); + var typeReaderResult = await curParam.Parse(context, argString, services).ConfigureAwait(false); if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches) return ParseResult.FromError(typeReaderResult); @@ -133,7 +134,7 @@ namespace Discord.Commands if (curParam != null && curParam.IsRemainder) { - var typeReaderResult = await curParam.Parse(context, argBuilder.ToString()).ConfigureAwait(false); + var typeReaderResult = await curParam.Parse(context, argBuilder.ToString(), services).ConfigureAwait(false); if (!typeReaderResult.IsSuccess) return ParseResult.FromError(typeReaderResult); argList.Add(typeReaderResult); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index f526e8f3b..90e7c8097 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -33,7 +33,7 @@ namespace Discord.Commands public IEnumerable Modules => _moduleDefs.Select(x => x); public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); - public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new {y.Key, y.Value})).ToLookup(x => x.Key, x => x.Value); + public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) @@ -59,6 +59,9 @@ namespace Discord.Commands foreach (var type in PrimitiveParsers.SupportedTypes) _defaultTypeReaders[type] = PrimitiveTypeReader.Create(type); + _defaultTypeReaders[typeof(string)] = + new PrimitiveTypeReader((string x, out string y) => { y = x; return true; }, 0); + var entityTypeReaders = ImmutableList.CreateBuilder>(); entityTypeReaders.Add(new Tuple(typeof(IMessage), typeof(MessageTypeReader<>))); entityTypeReaders.Add(new Tuple(typeof(IChannel), typeof(ChannelTypeReader<>))); @@ -95,7 +98,7 @@ namespace Discord.Commands if (_typedModuleDefs.ContainsKey(type)) throw new ArgumentException($"This module has already been added."); - var module = ModuleClassBuilder.Build(this, typeInfo).FirstOrDefault(); + var module = (await ModuleClassBuilder.BuildAsync(this, 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?"); @@ -114,8 +117,8 @@ namespace Discord.Commands await _moduleLock.WaitAsync().ConfigureAwait(false); try { - var types = ModuleClassBuilder.Search(assembly).ToArray(); - var moduleDefs = ModuleClassBuilder.Build(types, this); + var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false); + var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this).ConfigureAwait(false); foreach (var info in moduleDefs) { @@ -161,8 +164,7 @@ namespace Discord.Commands await _moduleLock.WaitAsync().ConfigureAwait(false); try { - ModuleInfo module; - if (!_typedModuleDefs.TryRemove(type, out module)) + if (!_typedModuleDefs.TryRemove(type, out var module)) return false; return RemoveModuleInternal(module); @@ -196,20 +198,18 @@ namespace Discord.Commands } public void AddTypeReader(Type type, TypeReader reader) { - var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentDictionary()); + var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary()); readers[reader.GetType()] = reader; } internal IDictionary GetTypeReaders(Type type) { - ConcurrentDictionary definedTypeReaders; - if (_typeReaders.TryGetValue(type, out definedTypeReaders)) + if (_typeReaders.TryGetValue(type, out var definedTypeReaders)) return definedTypeReaders; return null; } internal TypeReader GetDefaultTypeReader(Type type) { - TypeReader reader; - if (_defaultTypeReaders.TryGetValue(type, out reader)) + if (_defaultTypeReaders.TryGetValue(type, out var reader)) return reader; var typeInfo = type.GetTypeInfo(); @@ -235,13 +235,13 @@ namespace Discord.Commands } //Execution - public SearchResult Search(ICommandContext context, int argPos) + public SearchResult Search(ICommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); public SearchResult Search(ICommandContext context, string input) { string searchInput = _caseSensitive ? input : input.ToLowerInvariant(); var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray(); - + if (matches.Length > 0) return SearchResult.FromSuccess(input, matches); else @@ -259,46 +259,83 @@ namespace Discord.Commands return searchResult; var commands = searchResult.Commands; - for (int i = 0; i < commands.Count; i++) + var preconditionResults = new Dictionary(); + + foreach (var match in commands) { - var preconditionResult = await commands[i].CheckPreconditionsAsync(context, services).ConfigureAwait(false); - if (!preconditionResult.IsSuccess) - { - if (commands.Count == 1) - return preconditionResult; - else - continue; - } + preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false); + } + + var successfulPreconditions = preconditionResults + .Where(x => x.Value.IsSuccess) + .ToArray(); + + 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 we get this far, at least one precondition was successful. + + var parseResultsDict = new Dictionary(); + foreach (var pair in successfulPreconditions) + { + var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false); - var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false); - if (!parseResult.IsSuccess) + if (parseResult.Error == CommandError.MultipleMatches) { - 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; } + } - if (!parseResult.IsSuccess) - { - if (commands.Count == 1) - return parseResult; - else - continue; - } + 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) + { + argValuesScore = parseResult.ArgValues.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) / match.Command.Parameters.Count; + paramValuesScore = parseResult.ParamValues.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) / match.Command.Parameters.Count; } - return await commands[i].ExecuteAsync(context, parseResult, services).ConfigureAwait(false); + 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)); + + 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; } - return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); + //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/IModuleBase.cs b/src/Discord.Net.Commands/IModuleBase.cs index fda768b53..479724ae3 100644 --- a/src/Discord.Net.Commands/IModuleBase.cs +++ b/src/Discord.Net.Commands/IModuleBase.cs @@ -4,8 +4,8 @@ { void SetContext(ICommandContext context); - void BeforeExecute(); + void BeforeExecute(CommandInfo command); - void AfterExecute(); + void AfterExecute(CommandInfo command); } } diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index f187460d5..a97bd4fa5 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -18,7 +18,7 @@ namespace Discord.Commands private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); - private readonly Func _action; + private readonly Func _action; public ModuleInfo Module { get; } public string Name { get; } @@ -105,15 +105,17 @@ namespace Discord.Commands return PreconditionResult.FromSuccess(); } - public async Task ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null) + public async Task ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null) { + services = services ?? EmptyServiceProvider.Instance; + if (!searchResult.IsSuccess) return ParseResult.FromError(searchResult); if (preconditionResult != null && !preconditionResult.IsSuccess) return ParseResult.FromError(preconditionResult); string input = searchResult.Text.Substring(startIndex); - return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); + return await CommandParser.ParseArgs(this, context, services, input, 0).ConfigureAwait(false); } public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) @@ -181,7 +183,7 @@ namespace Discord.Commands await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); try { - await _action(context, args, services).ConfigureAwait(false); + await _action(context, args, services, this).ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 2ecf26a9f..2b71bb90b 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -54,9 +54,10 @@ namespace Discord.Commands return PreconditionResult.FromSuccess(); } - public async Task Parse(ICommandContext context, string input) + public async Task Parse(ICommandContext context, string input, IServiceProvider services = null) { - return await _reader.Read(context, input).ConfigureAwait(false); + services = services ?? EmptyServiceProvider.Instance; + return await _reader.Read(context, input, services).ConfigureAwait(false); } public override string ToString() => Name; diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index ed0b49006..f51656e40 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -15,11 +15,11 @@ namespace Discord.Commands return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); } - protected virtual void BeforeExecute() + protected virtual void BeforeExecute(CommandInfo command) { } - protected virtual void AfterExecute() + protected virtual void AfterExecute(CommandInfo command) { } @@ -27,13 +27,11 @@ namespace Discord.Commands void IModuleBase.SetContext(ICommandContext context) { var newValue = context as T; - if (newValue == null) - throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}"); - Context = newValue; + Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}"); } - void IModuleBase.BeforeExecute() => BeforeExecute(); + void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command); - void IModuleBase.AfterExecute() => AfterExecute(); + void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); } } diff --git a/src/Discord.Net.Commands/PrimitiveParsers.cs b/src/Discord.Net.Commands/PrimitiveParsers.cs index 623ddafa7..6a54ba402 100644 --- a/src/Discord.Net.Commands/PrimitiveParsers.cs +++ b/src/Discord.Net.Commands/PrimitiveParsers.cs @@ -31,11 +31,6 @@ namespace Discord.Commands parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate)DateTimeOffset.TryParse; parserBuilder[typeof(TimeSpan)] = (TryParseDelegate)TimeSpan.TryParse; parserBuilder[typeof(char)] = (TryParseDelegate)char.TryParse; - parserBuilder[typeof(string)] = (TryParseDelegate)delegate (string str, out string value) - { - value = str; - return true; - }; return parserBuilder.ToImmutable(); } diff --git a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs index d2e34b436..72c62282e 100644 --- a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs @@ -9,7 +9,7 @@ namespace Discord.Commands internal class ChannelTypeReader : TypeReader where T : class, IChannel { - public override async Task Read(ICommandContext context, string input) + public override async Task Read(ICommandContext context, string input, IServiceProvider services) { if (context.Guild != null) { diff --git a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs index 7b2ff505a..383b8e63c 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -44,12 +44,11 @@ namespace Discord.Commands _enumsByValue = byValueBuilder.ToImmutable(); } - public override Task Read(ICommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider services) { - T baseValue; object enumValue; - if (_tryParse(input, out baseValue)) + if (_tryParse(input, out T baseValue)) { if (_enumsByValue.TryGetValue(baseValue, out enumValue)) return Task.FromResult(TypeReaderResult.FromSuccess(enumValue)); diff --git a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs index 9baa1901a..895713e4f 100644 --- a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.Threading.Tasks; namespace Discord.Commands @@ -6,15 +7,14 @@ namespace Discord.Commands internal class MessageTypeReader : TypeReader where T : class, IMessage { - public override async Task Read(ICommandContext context, string input) + public override async Task Read(ICommandContext context, string input, IServiceProvider services) { ulong id; //By Id (1.0) if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) { - var msg = await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T; - if (msg != null) + if (await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) is T msg) return TypeReaderResult.FromSuccess(msg); } diff --git a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs index aa4c7c7a4..2656741f0 100644 --- a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs @@ -15,17 +15,25 @@ namespace Discord.Commands internal class PrimitiveTypeReader : TypeReader { private readonly TryParseDelegate _tryParse; + private readonly float _score; public PrimitiveTypeReader() + : this(PrimitiveParsers.Get(), 1) + { } + + public PrimitiveTypeReader(TryParseDelegate tryParse, float score) { - _tryParse = PrimitiveParsers.Get(); + if (score < 0 || score > 1) + throw new ArgumentOutOfRangeException(nameof(score), score, "Scores must be within the range [0, 1]"); + + _tryParse = tryParse; + _score = score; } - public override Task Read(ICommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider services) { - T value; - if (_tryParse(input, out value)) - return Task.FromResult(TypeReaderResult.FromSuccess(value)); + if (_tryParse(input, out T value)) + return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score))); return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}")); } } diff --git a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs index a90432782..17786e6f0 100644 --- a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs @@ -9,7 +9,7 @@ namespace Discord.Commands internal class RoleTypeReader : TypeReader where T : class, IRole { - public override Task Read(ICommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider services) { ulong id; diff --git a/src/Discord.Net.Commands/Readers/TypeReader.cs b/src/Discord.Net.Commands/Readers/TypeReader.cs index d53491e92..2c4644376 100644 --- a/src/Discord.Net.Commands/Readers/TypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TypeReader.cs @@ -1,9 +1,10 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace Discord.Commands { public abstract class TypeReader { - public abstract Task Read(ICommandContext context, string input); + public abstract Task Read(ICommandContext context, string input, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index d7fc6cfdc..c71dac2d2 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -10,7 +10,7 @@ namespace Discord.Commands internal class UserTypeReader : TypeReader where T : class, IUser { - public override async Task Read(ICommandContext context, string input) + public override async Task Read(ICommandContext context, string input, IServiceProvider services) { var results = new Dictionary(); IReadOnlyCollection channelUsers = (await context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way? @@ -43,8 +43,7 @@ namespace Discord.Commands if (index >= 0) { string username = input.Substring(0, index); - ushort discriminator; - if (ushort.TryParse(input.Substring(index + 1), out discriminator)) + if (ushort.TryParse(input.Substring(index + 1), out ushort discriminator)) { var channelUser = channelUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator && string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase)); diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index b6ceda426..ab88f66ae 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -58,7 +58,7 @@ namespace Discord.Commands { foreach (var prop in ownerType.DeclaredProperties) { - if (prop.GetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null) + if (prop.SetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null) result.Add(prop); } ownerType = ownerType.BaseType.GetTypeInfo(); From 224d0403dbd5bc8352de53a09129847cef45fe07 Mon Sep 17 00:00:00 2001 From: Pat Murphy Date: Thu, 29 Jun 2017 14:05:16 -0700 Subject: [PATCH 45/62] Adding Equals() overloads for reactions/emotes (#723) --- src/Discord.Net.Core/Entities/Emotes/Emoji.cs | 13 +++++++++++ src/Discord.Net.Core/Entities/Emotes/Emote.cs | 19 ++++++++++++++++ .../Entities/Messages/SocketReaction.cs | 22 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs index 07cee10a9..76eef58c8 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs @@ -21,5 +21,18 @@ public string Name { get; } public override string ToString() => Name; + + public override bool Equals(object other) + { + if (other == null) return false; + if (other == this) return true; + + var otherEmoji = other as Emoji; + if (otherEmoji == null) return false; + + return string.Equals(Name, otherEmoji.Name); + } + + public override int GetHashCode() => Name.GetHashCode(); } } diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs index 76c20a77d..f498c818e 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -25,6 +25,25 @@ namespace Discord Name = name; } + public override bool Equals(object other) + { + if (other == null) return false; + if (other == this) return true; + + var otherEmote = other as Emote; + if (otherEmote == null) return false; + + return string.Equals(Name, otherEmote.Name) && Id == otherEmote.Id; + } + + public override int GetHashCode() + { + unchecked + { + return (Name.GetHashCode() * 397) ^ Id.GetHashCode(); + } + } + /// /// Parse an Emote from its raw format /// diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs index 9f58f1cf6..35bee9e68 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs @@ -29,5 +29,27 @@ namespace Discord.WebSocket emote = new Emoji(model.Emoji.Name); return new SocketReaction(channel, model.MessageId, message, model.UserId, user, emote); } + + public override bool Equals(object other) + { + if (other == null) return false; + if (other == this) return true; + + var otherReaction = other as SocketReaction; + if (otherReaction == null) return false; + + return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = UserId.GetHashCode(); + hashCode = (hashCode * 397) ^ MessageId.GetHashCode(); + hashCode = (hashCode * 397) ^ Emote.GetHashCode(); + return hashCode; + } + } } } From 394e0aa4d19fe5bc30d47cfcc30423e18186770c Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 29 Jun 2017 18:06:12 -0300 Subject: [PATCH 46/62] Reorganized properties in Emoji.cs --- src/Discord.Net.Core/Entities/Emotes/Emoji.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs index 76eef58c8..c2dfc31ad 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs @@ -6,6 +6,14 @@ public class Emoji : IEmote { // TODO: need to constrain this to unicode-only emojis somehow + + /// + /// The unicode representation of this emote. + /// + public string Name { get; } + + public override string ToString() => Name; + /// /// Creates a unicode emoji. /// @@ -15,13 +23,6 @@ Name = unicode; } - /// - /// The unicode representation of this emote. - /// - public string Name { get; } - - public override string ToString() => Name; - public override bool Equals(object other) { if (other == null) return false; From b96748f9c3d4851191f0e4893fd19a32690df6fc Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Thu, 29 Jun 2017 22:30:26 +0100 Subject: [PATCH 47/62] Allow arbitrary attributes to be added to commands (#458) * Allow arbitrary attributes to be added to commands I still don't approve adding type info back into commands, so I decided to use this solution for allowing arbitrary attributes to be added to commands. Add attributes property to ParameterBuilder Add Attributes properties to info types * Why on earth git * Add using for system so that Attribute can be used --- .../Builders/CommandBuilder.cs | 8 ++++++++ .../Builders/ModuleBuilder.cs | 8 ++++++++ .../Builders/ModuleClassBuilder.cs | 11 ++++++++++- .../Builders/ParameterBuilder.cs | 10 +++++++++- src/Discord.Net.Commands/Info/CommandInfo.cs | 2 ++ src/Discord.Net.Commands/Info/ModuleInfo.cs | 17 +++++++++++++++++ src/Discord.Net.Commands/Info/ParameterInfo.cs | 2 ++ 7 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 30db1fa62..0a79f13f2 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -10,6 +10,7 @@ namespace Discord.Commands.Builders { private readonly List _preconditions; private readonly List _parameters; + private readonly List _attributes; private readonly List _aliases; public ModuleBuilder Module { get; } @@ -24,6 +25,7 @@ namespace Discord.Commands.Builders public IReadOnlyList Preconditions => _preconditions; public IReadOnlyList Parameters => _parameters; + public IReadOnlyList Attributes => _attributes; public IReadOnlyList Aliases => _aliases; //Automatic @@ -33,6 +35,7 @@ namespace Discord.Commands.Builders _preconditions = new List(); _parameters = new List(); + _attributes = new List(); _aliases = new List(); } //User-defined @@ -83,6 +86,11 @@ namespace Discord.Commands.Builders } return this; } + public CommandBuilder AddAttributes(params Attribute[] attributes) + { + _attributes.AddRange(attributes); + return this; + } public CommandBuilder AddPrecondition(PreconditionAttribute precondition) { _preconditions.Add(precondition); diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 525907b8b..e5e688fe9 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -10,6 +10,7 @@ namespace Discord.Commands.Builders private readonly List _commands; private readonly List _submodules; private readonly List _preconditions; + private readonly List _attributes; private readonly List _aliases; public CommandService Service { get; } @@ -21,6 +22,7 @@ namespace Discord.Commands.Builders public IReadOnlyList Commands => _commands; public IReadOnlyList Modules => _submodules; public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Attributes => _attributes; public IReadOnlyList Aliases => _aliases; //Automatic @@ -32,6 +34,7 @@ namespace Discord.Commands.Builders _commands = new List(); _submodules = new List(); _preconditions = new List(); + _attributes = new List(); _aliases = new List(); } //User-defined @@ -69,6 +72,11 @@ namespace Discord.Commands.Builders } return this; } + public ModuleBuilder AddAttributes(params Attribute[] attributes) + { + _attributes.AddRange(attributes); + return this; + } public ModuleBuilder AddPrecondition(PreconditionAttribute precondition) { _preconditions.Add(precondition); diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 401396900..2df1d805f 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -122,6 +122,9 @@ namespace Discord.Commands case PreconditionAttribute precondition: builder.AddPrecondition(precondition); break; + default: + builder.AddAttributes(attribute); + break; } } @@ -173,6 +176,9 @@ namespace Discord.Commands case PreconditionAttribute precondition: builder.AddPrecondition(precondition); break; + default: + builder.AddAttributes(attribute); + break; } } @@ -239,6 +245,9 @@ namespace Discord.Commands builder.IsRemainder = true; break; + default: + builder.AddAttributes(attribute); + break; } } @@ -289,4 +298,4 @@ namespace Discord.Commands !methodInfo.IsGenericMethod; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index d2bebbad0..d1782d7ea 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -8,7 +8,8 @@ namespace Discord.Commands.Builders { public class ParameterBuilder { - private readonly List _preconditions; + private readonly List _preconditions; + private readonly List _attributes; public CommandBuilder Command { get; } public string Name { get; internal set; } @@ -22,11 +23,13 @@ namespace Discord.Commands.Builders public string Summary { get; set; } public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Attributes => _attributes; //Automatic internal ParameterBuilder(CommandBuilder command) { _preconditions = new List(); + _attributes = new List(); Command = command; } @@ -84,6 +87,11 @@ namespace Discord.Commands.Builders return this; } + public ParameterBuilder AddAttributes(params Attribute[] attributes) + { + _attributes.AddRange(attributes); + return this; + } public ParameterBuilder AddPrecondition(ParameterPreconditionAttribute precondition) { _preconditions.Add(precondition); diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index a97bd4fa5..00041f22d 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -31,6 +31,7 @@ namespace Discord.Commands public IReadOnlyList Aliases { get; } public IReadOnlyList Parameters { get; } public IReadOnlyList Preconditions { get; } + public IReadOnlyList Attributes { get; } internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service) { @@ -57,6 +58,7 @@ namespace Discord.Commands .ToImmutableArray(); Preconditions = builder.Preconditions.ToImmutableArray(); + Attributes = builder.Attributes.ToImmutableArray(); Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index a2094df65..97b90bf4e 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; @@ -16,6 +17,7 @@ namespace Discord.Commands public IReadOnlyList Aliases { get; } public IReadOnlyList Commands { get; } public IReadOnlyList Preconditions { get; } + public IReadOnlyList Attributes { get; } public IReadOnlyList Submodules { get; } public ModuleInfo Parent { get; } public bool IsSubmodule => Parent != null; @@ -32,6 +34,7 @@ namespace Discord.Commands 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(); } @@ -86,5 +89,19 @@ namespace Discord.Commands return result; } + + private static List BuildAttributes(ModuleBuilder builder) + { + var result = new List(); + + ModuleBuilder parent = builder; + while (parent != null) + { + result.AddRange(parent.Attributes); + parent = parent.Parent; + } + + return result; + } } } diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 2b71bb90b..e417b1ab6 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -21,6 +21,7 @@ namespace Discord.Commands public object DefaultValue { get; } public IReadOnlyList Preconditions { get; } + public IReadOnlyList Attributes { get; } internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service) { @@ -36,6 +37,7 @@ namespace Discord.Commands DefaultValue = builder.DefaultValue; Preconditions = builder.Preconditions.ToImmutableArray(); + Attributes = builder.Attributes.ToImmutableArray(); _reader = builder.TypeReader; } From 74f6a4b392fb9ff6dfed77f6a1195f93a9c16efe Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Thu, 29 Jun 2017 23:21:05 +0100 Subject: [PATCH 48/62] Allow commands to return a Task (#466) * Allow commands to return a Task This allows bot developers to centralize command result logic by using result data whether the command as successful or not. Example usage: ```csharp var _result = await Commands.ExecuteAsync(context, argPos); if (_result is RuntimeResult result) { await message.Channel.SendMessageAsync(result.Reason); } else if (!_result.IsSuccess) { // Previous error handling } ``` The RuntimeResult class can be subclassed too, for example: ```csharp var _result = await Commands.ExecuteAsync(context, argPos); if (_result is MySubclassedResult result) { var builder = new EmbedBuilder(); for (var pair in result.Data) { builder.AddField(pair.Key, pair.Value, true); } await message.Channel.SendMessageAsync("", embed: builder); } else if (_result is RuntimeResult result) { await message.Channel.SendMessageAsync(result.Reason); } else if (!_result.IsSuccess) { // Previous error handling } ``` * Make RuntimeResult's ctor protected * Make RuntimeResult abstract It never really made sense to have it instantiable in the first place, frankly. --- .../Builders/ModuleClassBuilder.cs | 24 ++++++--- src/Discord.Net.Commands/CommandError.cs | 5 +- src/Discord.Net.Commands/CommandMatch.cs | 4 +- src/Discord.Net.Commands/Info/CommandInfo.cs | 52 +++++++++++++------ .../Results/RuntimeResult.cs | 27 ++++++++++ 5 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 src/Discord.Net.Commands/Results/RuntimeResult.cs diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 2df1d805f..be3449b6f 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -197,22 +197,34 @@ namespace Discord.Commands var createInstance = ReflectionUtils.CreateBuilder(typeInfo, service); - builder.Callback = async (ctx, args, map, cmd) => + async Task ExecuteCallback(ICommandContext context, object[] args, IServiceProvider services, CommandInfo cmd) { - var instance = createInstance(map); - instance.SetContext(ctx); + var instance = createInstance(services); + instance.SetContext(context); + try { instance.BeforeExecute(cmd); + var task = method.Invoke(instance, args) as Task ?? Task.Delay(0); - await task.ConfigureAwait(false); + if (task is Task resultTask) + { + return await resultTask.ConfigureAwait(false); + } + else + { + await task.ConfigureAwait(false); + return ExecuteResult.FromSuccess(); + } } finally { instance.AfterExecute(cmd); (instance as IDisposable)?.Dispose(); } - }; + } + + builder.Callback = ExecuteCallback; } private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service) @@ -293,7 +305,7 @@ namespace Discord.Commands private static bool IsValidCommandDefinition(MethodInfo methodInfo) { return methodInfo.IsDefined(typeof(CommandAttribute)) && - (methodInfo.ReturnType == typeof(Task) || methodInfo.ReturnType == typeof(void)) && + (methodInfo.ReturnType == typeof(Task) || methodInfo.ReturnType == typeof(Task)) && !methodInfo.IsStatic && !methodInfo.IsGenericMethod; } diff --git a/src/Discord.Net.Commands/CommandError.cs b/src/Discord.Net.Commands/CommandError.cs index 41b4822ad..abfc14e1d 100644 --- a/src/Discord.Net.Commands/CommandError.cs +++ b/src/Discord.Net.Commands/CommandError.cs @@ -18,6 +18,9 @@ UnmetPrecondition, //Execute - Exception + Exception, + + //Runtime + Unsuccessful } } diff --git a/src/Discord.Net.Commands/CommandMatch.cs b/src/Discord.Net.Commands/CommandMatch.cs index d2bd9ef03..d922a2229 100644 --- a/src/Discord.Net.Commands/CommandMatch.cs +++ b/src/Discord.Net.Commands/CommandMatch.cs @@ -20,9 +20,9 @@ namespace Discord.Commands => Command.CheckPreconditionsAsync(context, services); public Task ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null) => Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult, services); - public Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) + public Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) => Command.ExecuteAsync(context, argList, paramList, services); - public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) + public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) => Command.ExecuteAsync(context, parseResult, services); } } diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 00041f22d..60df2a6a9 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -36,14 +36,14 @@ namespace Discord.Commands internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service) { Module = module; - + Name = builder.Name; Summary = builder.Summary; Remarks = builder.Remarks; RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode); Priority = builder.Priority; - + Aliases = module.Aliases .Permutate(builder.Aliases, (first, second) => { @@ -106,7 +106,7 @@ namespace Discord.Commands return PreconditionResult.FromSuccess(); } - + public async Task ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null) { services = services ?? EmptyServiceProvider.Instance; @@ -115,35 +115,35 @@ namespace Discord.Commands return ParseResult.FromError(searchResult); if (preconditionResult != null && !preconditionResult.IsSuccess) return ParseResult.FromError(preconditionResult); - + string input = searchResult.Text.Substring(startIndex); return await CommandParser.ParseArgs(this, context, services, input, 0).ConfigureAwait(false); } - public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) + public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) { if (!parseResult.IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult)); + return Task.FromResult((IResult)ExecuteResult.FromError(parseResult)); var argList = new object[parseResult.ArgValues.Count]; for (int i = 0; i < parseResult.ArgValues.Count; i++) { if (!parseResult.ArgValues[i].IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i])); + return Task.FromResult((IResult)ExecuteResult.FromError(parseResult.ArgValues[i])); argList[i] = parseResult.ArgValues[i].Values.First().Value; } - + var paramList = new object[parseResult.ParamValues.Count]; for (int i = 0; i < parseResult.ParamValues.Count; i++) { if (!parseResult.ParamValues[i].IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i])); + return Task.FromResult((IResult)ExecuteResult.FromError(parseResult.ParamValues[i])); paramList[i] = parseResult.ParamValues[i].Values.First().Value; } return ExecuteAsync(context, argList, paramList, services); } - public async Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) + public async Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) { services = services ?? EmptyServiceProvider.Instance; @@ -163,10 +163,9 @@ namespace Discord.Commands switch (RunMode) { case RunMode.Sync: //Always sync - await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); - break; + return await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); case RunMode.Async: //Always async - var t2 = Task.Run(async () => + var t2 = Task.Run(async () => { await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); }); @@ -180,12 +179,26 @@ namespace Discord.Commands } } - private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider services) + private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider services) { await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); try { - await _action(context, args, services, this).ConfigureAwait(false); + var task = _action(context, args, services, this); + if (task is Task resultTask) + { + var result = await resultTask.ConfigureAwait(false); + if (result is RuntimeResult execResult) + return execResult; + } + else if (task is Task execTask) + { + return await execTask.ConfigureAwait(false); + } + else + await task.ConfigureAwait(false); + + return ExecuteResult.FromSuccess(); } catch (Exception ex) { @@ -202,8 +215,13 @@ namespace Discord.Commands else ExceptionDispatchInfo.Capture(ex).Throw(); } + + return ExecuteResult.FromError(CommandError.Exception, ex.Message); + } + finally + { + await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); } - await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); } private object[] GenerateArgs(IEnumerable argList, IEnumerable paramsList) @@ -240,7 +258,7 @@ namespace Discord.Commands => paramsList.Cast().ToArray(); internal string GetLogText(ICommandContext context) - { + { if (context.Guild != null) return $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}"; else diff --git a/src/Discord.Net.Commands/Results/RuntimeResult.cs b/src/Discord.Net.Commands/Results/RuntimeResult.cs new file mode 100644 index 000000000..2a326a7a3 --- /dev/null +++ b/src/Discord.Net.Commands/Results/RuntimeResult.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Discord.Commands +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public abstract class RuntimeResult : IResult + { + protected RuntimeResult(CommandError? error, string reason) + { + Error = error; + Reason = reason; + } + + public CommandError? Error { get; } + public string Reason { get; } + + public bool IsSuccess => !Error.HasValue; + + string IResult.ErrorReason => Reason; + + public override string ToString() => Reason ?? (IsSuccess ? "Successful" : "Unsuccessful"); + private string DebuggerDisplay => IsSuccess ? $"Success: {Reason ?? "No Reason"}" : $"{Error}: {Reason}"; + } +} From fdd38c8d7f9292b6ab5e25ac9596b973e07fcca6 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Thu, 29 Jun 2017 23:44:08 +0100 Subject: [PATCH 49/62] Add embed builder extensions (#460) * Add embed builder extensions People in #dotnet_discord-net suggested that this should be part of the lib after I demonstrated it * Move some extensions into EmbedBuilder [2] Apparently git didn't like that previous commit * Fix error with EmbedBuilderExtensions A summary of issues which happened: - Git decided to add an amend commit (I told it to quit?) - VS Code thinks everything is an error so it wasn't helpful - dotnet decided to think there was no error until I deleted all build outputs and rebuild Sometimes I question my ability to use version control properly. --- .../Entities/Messages/EmbedBuilder.cs | 32 +++++++++++++++++++ .../Extensions/EmbedBuilderExtensions.cs | 20 ++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs index c299bd1a1..2331f6749 100644 --- a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs @@ -137,6 +137,17 @@ namespace Discord Author = author; return this; } + public EmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null) + { + var author = new EmbedAuthorBuilder + { + Name = name, + IconUrl = iconUrl, + Url = url + }; + Author = author; + return this; + } public EmbedBuilder WithFooter(EmbedFooterBuilder footer) { Footer = footer; @@ -149,6 +160,16 @@ namespace Discord Footer = footer; return this; } + public EmbedBuilder WithFooter(string text, string iconUrl = null) + { + var footer = new EmbedFooterBuilder + { + Text = text, + IconUrl = iconUrl + }; + Footer = footer; + return this; + } public EmbedBuilder AddField(string name, object value) { @@ -185,6 +206,17 @@ namespace Discord this.AddField(field); return this; } + public EmbedBuilder AddField(string title, string text, bool inline = false) + { + var field = new EmbedFieldBuilder + { + Name = title, + Value = text, + IsInline = inline + }; + _fields.Add(field); + return this; + } public Embed Build() { diff --git a/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs b/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs new file mode 100644 index 000000000..64f96c93f --- /dev/null +++ b/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs @@ -0,0 +1,20 @@ +namespace Discord +{ + public static class EmbedBuilderExtensions + { + public static EmbedBuilder WithColor(this EmbedBuilder builder, uint rawValue) => + builder.WithColor(new Color(rawValue)); + + public static EmbedBuilder WithColor(this EmbedBuilder builder, byte r, byte g, byte b) => + builder.WithColor(new Color(r, g, b)); + + public static EmbedBuilder WithColor(this EmbedBuilder builder, float r, float g, float b) => + builder.WithColor(new Color(r, g, b)); + + public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IUser user) => + builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.AvatarUrl); + + public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IGuildUser user) => + builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.AvatarUrl); + } +} From 14dfc48df3dcce3842731a08e73f76ccb5dee675 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 29 Jun 2017 19:25:21 -0300 Subject: [PATCH 50/62] Style cleanup --- .../Builders/CommandBuilder.cs | 2 +- .../Builders/ModuleBuilder.cs | 2 +- .../Builders/ModuleClassBuilder.cs | 2 +- src/Discord.Net.Commands/Info/CommandInfo.cs | 4 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 10 ++-- .../DiscordSocketClient.cs | 51 +++++++++---------- 6 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 0a79f13f2..b6d002c70 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -80,7 +80,7 @@ namespace Discord.Commands.Builders { for (int i = 0; i < aliases.Length; i++) { - var alias = aliases[i] ?? ""; + string alias = aliases[i] ?? ""; if (!_aliases.Contains(alias)) _aliases.Add(alias); } diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index e5e688fe9..0a33c9e26 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -66,7 +66,7 @@ namespace Discord.Commands.Builders { for (int i = 0; i < aliases.Length; i++) { - var alias = aliases[i] ?? ""; + string alias = aliases[i] ?? ""; if (!_aliases.Contains(alias)) _aliases.Add(alias); } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index be3449b6f..b8fbbf462 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -20,7 +20,7 @@ namespace Discord.Commands info.GetCustomAttribute() == null; } - List result = new List(); + var result = new List(); foreach (var typeInfo in assembly.DefinedTypes) { diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 60df2a6a9..ebef80baf 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -154,7 +154,7 @@ namespace Discord.Commands for (int position = 0; position < Parameters.Count; position++) { var parameter = Parameters[position]; - var argument = args[position]; + object argument = args[position]; var result = await parameter.CheckPreconditionsAsync(context, argument, services).ConfigureAwait(false); if (!result.IsSuccess) return ExecuteResult.FromError(result); @@ -232,7 +232,7 @@ namespace Discord.Commands argCount--; int i = 0; - foreach (var arg in argList) + foreach (object arg in argList) { if (i == argCount) throw new InvalidOperationException("Command was invoked with too many parameters"); diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 621c2d0e2..1fac66ec5 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -30,7 +30,7 @@ namespace Discord.API protected readonly JsonSerializer _serializer; protected readonly SemaphoreSlim _stateLock; - private readonly RestClientProvider RestClientProvider; + private readonly RestClientProvider _restClientProvider; protected bool _isDisposed; private CancellationTokenSource _loginCancelToken; @@ -48,7 +48,7 @@ namespace Discord.API public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) { - RestClientProvider = restClientProvider; + _restClientProvider = restClientProvider; UserAgent = userAgent; DefaultRetryMode = defaultRetryMode; _serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() }; @@ -60,7 +60,7 @@ namespace Discord.API } internal void SetBaseUrl(string baseUrl) { - RestClient = RestClientProvider(baseUrl); + RestClient = _restClientProvider(baseUrl); RestClient.SetHeader("accept", "*/*"); RestClient.SetHeader("user-agent", UserAgent); RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); @@ -189,7 +189,7 @@ namespace Discord.API options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; - var json = payload != null ? SerializeJson(payload) : null; + string json = payload != null ? SerializeJson(payload) : null; var request = new JsonRestRequest(RestClient, method, endpoint, json, options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); } @@ -233,7 +233,7 @@ namespace Discord.API options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; - var json = payload != null ? SerializeJson(payload) : null; + string json = payload != null ? SerializeJson(payload) : null; var request = new JsonRestRequest(RestClient, method, endpoint, json, 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 f9458caff..b13ceca1d 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -34,7 +34,7 @@ namespace Discord.WebSocket private int _lastSeq; private ImmutableDictionary _voiceRegions; private Task _heartbeatTask, _guildDownloadTask; - private int _unavailableGuilds; + private int _unavailableGuildCount; private long _lastGuildAvailableTime, _lastMessageTime; private int _nextAudioId; private DateTimeOffset? _statusSince; @@ -60,7 +60,7 @@ namespace Discord.WebSocket internal int? HandlerTimeout { get; private set; } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; - public new SocketSelfUser CurrentUser { get { return base.CurrentUser as SocketSelfUser; } private set { base.CurrentUser = value; } } + public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; private set => base.CurrentUser = value; } public IReadOnlyCollection Guilds => State.Guilds; public IReadOnlyCollection PrivateChannels => State.PrivateChannels; public IReadOnlyCollection DMChannels @@ -474,7 +474,7 @@ namespace Discord.WebSocket AddPrivateChannel(data.PrivateChannels[i], state); _sessionId = data.SessionId; - _unavailableGuilds = unavailableGuilds; + _unavailableGuildCount = unavailableGuilds; CurrentUser = currentUser; State = state; } @@ -537,10 +537,9 @@ namespace Discord.WebSocket if (guild != null) { guild.Update(State, data); - - var unavailableGuilds = _unavailableGuilds; - if (unavailableGuilds != 0) - _unavailableGuilds = unavailableGuilds - 1; + + if (_unavailableGuildCount != 0) + _unavailableGuildCount--; await GuildAvailableAsync(guild).ConfigureAwait(false); if (guild.DownloadedMemberCount >= guild.MemberCount && !guild.DownloaderPromise.IsCompleted) @@ -622,7 +621,7 @@ namespace Discord.WebSocket var before = guild.Clone(); guild.Update(State, data); //This is treated as an extension of GUILD_AVAILABLE - _unavailableGuilds--; + _unavailableGuildCount--; _lastGuildAvailableTime = Environment.TickCount; await GuildAvailableAsync(guild).ConfigureAwait(false); await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); @@ -646,7 +645,7 @@ namespace Discord.WebSocket if (guild != null) { await GuildUnavailableAsync(guild).ConfigureAwait(false); - _unavailableGuilds++; + _unavailableGuildCount++; } else { @@ -1212,10 +1211,10 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { - SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; bool isCached = cachedMsg != null; var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); - SocketReaction reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); + var reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); cachedMsg?.AddReaction(reaction); @@ -1236,10 +1235,10 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { - SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; bool isCached = cachedMsg != null; var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); - SocketReaction reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); + var reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); cachedMsg?.RemoveReaction(reaction); @@ -1260,7 +1259,7 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { - SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; bool isCached = cachedMsg != null; var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); @@ -1289,7 +1288,7 @@ namespace Discord.WebSocket return; } - foreach (var id in data.Ids) + foreach (ulong id in data.Ids) { var msg = SocketChannelHelper.RemoveMessage(channel, this, id); bool isCached = msg != null; @@ -1542,7 +1541,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Heartbeat Started").ConfigureAwait(false); while (!cancelToken.IsCancellationRequested) { - var now = Environment.TickCount; + int now = Environment.TickCount; //Did server respond to our last heartbeat, or are we still receiving messages (long load?) if (_heartbeatTimes.Count != 0 && (now - _lastMessageTime) > intervalMillis) @@ -1589,7 +1588,7 @@ namespace Discord.WebSocket try { await logger.DebugAsync("GuildDownloader Started").ConfigureAwait(false); - while ((_unavailableGuilds != 0) && (Environment.TickCount - _lastGuildAvailableTime < 2000)) + while ((_unavailableGuildCount != 0) && (Environment.TickCount - _lastGuildAvailableTime < 2000)) await Task.Delay(500, cancelToken).ConfigureAwait(false); await logger.DebugAsync("GuildDownloader Stopped").ConfigureAwait(false); } @@ -1750,27 +1749,27 @@ namespace Discord.WebSocket private async Task UnknownGlobalUserAsync(string evnt, ulong userId) { - var details = $"{evnt} User={userId}"; + string details = $"{evnt} User={userId}"; await _gatewayLogger.WarningAsync($"Unknown User ({details}).").ConfigureAwait(false); } private async Task UnknownChannelUserAsync(string evnt, ulong userId, ulong channelId) { - var details = $"{evnt} User={userId} Channel={channelId}"; + string details = $"{evnt} User={userId} Channel={channelId}"; await _gatewayLogger.WarningAsync($"Unknown User ({details}).").ConfigureAwait(false); } private async Task UnknownGuildUserAsync(string evnt, ulong userId, ulong guildId) { - var details = $"{evnt} User={userId} Guild={guildId}"; + string details = $"{evnt} User={userId} Guild={guildId}"; await _gatewayLogger.WarningAsync($"Unknown User ({details}).").ConfigureAwait(false); } private async Task IncompleteGuildUserAsync(string evnt, ulong userId, ulong guildId) { - var details = $"{evnt} User={userId} Guild={guildId}"; + string details = $"{evnt} User={userId} Guild={guildId}"; await _gatewayLogger.DebugAsync($"User has not been downloaded ({details}).").ConfigureAwait(false); } private async Task UnknownChannelAsync(string evnt, ulong channelId) { - var details = $"{evnt} Channel={channelId}"; + string details = $"{evnt} Channel={channelId}"; await _gatewayLogger.WarningAsync($"Unknown Channel ({details}).").ConfigureAwait(false); } private async Task UnknownChannelAsync(string evnt, ulong channelId, ulong guildId) @@ -1780,22 +1779,22 @@ namespace Discord.WebSocket await UnknownChannelAsync(evnt, channelId).ConfigureAwait(false); return; } - var details = $"{evnt} Channel={channelId} Guild={guildId}"; + string details = $"{evnt} Channel={channelId} Guild={guildId}"; await _gatewayLogger.WarningAsync($"Unknown Channel ({details}).").ConfigureAwait(false); } private async Task UnknownRoleAsync(string evnt, ulong roleId, ulong guildId) { - var details = $"{evnt} Role={roleId} Guild={guildId}"; + string details = $"{evnt} Role={roleId} Guild={guildId}"; await _gatewayLogger.WarningAsync($"Unknown Role ({details}).").ConfigureAwait(false); } private async Task UnknownGuildAsync(string evnt, ulong guildId) { - var details = $"{evnt} Guild={guildId}"; + string details = $"{evnt} Guild={guildId}"; await _gatewayLogger.WarningAsync($"Unknown Guild ({details}).").ConfigureAwait(false); } private async Task UnsyncedGuildAsync(string evnt, ulong guildId) { - var details = $"{evnt} Guild={guildId}"; + string details = $"{evnt} Guild={guildId}"; await _gatewayLogger.DebugAsync($"Unsynced Guild ({details}).").ConfigureAwait(false); } From 3b78817c544d5a35fb0c0e7b9f395b9c353da6f6 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 29 Jun 2017 19:45:02 -0300 Subject: [PATCH 51/62] Added int overload to EmbedBuilderExtensions --- src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs b/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs index 64f96c93f..67acf7c8d 100644 --- a/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs @@ -8,6 +8,9 @@ namespace Discord public static EmbedBuilder WithColor(this EmbedBuilder builder, byte r, byte g, byte b) => builder.WithColor(new Color(r, g, b)); + public static EmbedBuilder WithColor(this EmbedBuilder builder, int r, int g, int b) => + builder.WithColor(new Color(r, g, b)); + public static EmbedBuilder WithColor(this EmbedBuilder builder, float r, float g, float b) => builder.WithColor(new Color(r, g, b)); From ba18179eb845e21cda7f2772e22e7a14f4bf2732 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 29 Jun 2017 19:50:07 -0300 Subject: [PATCH 52/62] Fixed compile error --- src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs b/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs index 67acf7c8d..cee9a136e 100644 --- a/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs @@ -15,9 +15,9 @@ namespace Discord builder.WithColor(new Color(r, g, b)); public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IUser user) => - builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.AvatarUrl); + builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.GetAvatarUrl()); public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IGuildUser user) => - builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.AvatarUrl); + builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.GetAvatarUrl()); } } From 26bc0b300da7198e2a3f62060316214664c69af5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 29 Jun 2017 20:00:26 -0300 Subject: [PATCH 53/62] Updated version to 1.0 --- Discord.Net.targets | 2 +- src/Discord.Net/Discord.Net.nuspec | 40 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 947819898..500d18fd8 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,7 +1,7 @@ 1.0.0 - rc3 + 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 2a637dbfb..667cc14b9 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,39 +2,39 @@ Discord.Net - 1.0.0-rc3$suffix$ + 1.0.0$suffix$ Discord.Net RogueException RogueException - An aynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components. + An asynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components. discord;discordapp https://github.com/RogueException/Discord.Net http://opensource.org/licenses/MIT false - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + From c316b29286f71f9ee11fada3becbf692e90b4084 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 2 Jul 2017 12:33:03 -0400 Subject: [PATCH 54/62] Bump version to 1.0.1 --- Discord.Net.targets | 2 +- src/Discord.Net/Discord.Net.nuspec | 38 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 500d18fd8..6dc4bb140 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,6 +1,6 @@ - 1.0.0 + 1.0.1 RogueException discord;discordapp diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 667cc14b9..19f173c3d 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 1.0.0$suffix$ + 1.0.1-build$suffix$ Discord.Net RogueException RogueException @@ -13,28 +13,28 @@ false - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + From 7597cf5baab8f8818b62f0a67ff649b00676430f Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Thu, 6 Jul 2017 00:09:38 +0100 Subject: [PATCH 55/62] Fix CalculateScore throwing on missing parameters (#727) * Fix CalculateScore throwing on missing parameters * Bump to version 1.0.1 --- src/Discord.Net.Commands/CommandService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 90e7c8097..6ea2abcf3 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -309,8 +309,11 @@ namespace Discord.Commands if (match.Command.Parameters.Count > 0) { - argValuesScore = parseResult.ArgValues.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) / match.Command.Parameters.Count; - paramValuesScore = parseResult.ParamValues.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) / match.Command.Parameters.Count; + 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; } var totalArgsScore = (argValuesScore + paramValuesScore) / 2; From b6dcc9e8d8b9bb72e66c9446bc6579560d9fbd23 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Thu, 6 Jul 2017 01:13:49 +0200 Subject: [PATCH 56/62] Add back the case for ParameterPreconditions (#735) --- src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index b8fbbf462..f284c34f4 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -251,6 +251,9 @@ namespace Discord.Commands builder.IsMultiple = true; paramType = paramType.GetElementType(); break; + case ParameterPreconditionAttribute precon: + builder.AddPrecondition(precon); + break; case RemainderAttribute _: if (position != count - 1) throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}"); From d2afb06942868521b1ea5cc193abc2a2a08d69e3 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Thu, 6 Jul 2017 00:19:09 +0100 Subject: [PATCH 57/62] Make the "cannot be loaded" warning fire correctly (#729) Why am I such a bad programmer? Maybe I'm just bad with git. Maybe I'm just bad in general. Maybe I should resign from programming. --- src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index f284c34f4..6fae719ee 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -24,7 +24,7 @@ namespace Discord.Commands foreach (var typeInfo in assembly.DefinedTypes) { - if (typeInfo.IsPublic) + if (typeInfo.IsPublic || typeInfo.IsNestedPublic) { if (IsValidModuleDefinition(typeInfo) && !typeInfo.IsDefined(typeof(DontAutoLoadAttribute))) @@ -70,7 +70,7 @@ namespace Discord.Commands result[typeInfo.AsType()] = module.Build(service); } - await service._cmdLogger.DebugAsync($"Successfully built and loaded {builtTypes.Count} modules.").ConfigureAwait(false); + await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} modules.").ConfigureAwait(false); return result; } From 8cd99beb622532290ce34ef39ae3000a3992831e Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Thu, 6 Jul 2017 01:23:46 +0200 Subject: [PATCH 58/62] Unify ShardedCommandContext with SocketCommandContext (#739) * Make ShardedCommandContext derive from SocketCommandContext * Explicitly re-implement ICommandContext.Client --- .../Commands/ShardedCommandContext.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs index 627b9b390..a29c9bb70 100644 --- a/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs +++ b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs @@ -2,30 +2,20 @@ namespace Discord.Commands { - public class ShardedCommandContext : ICommandContext + public class ShardedCommandContext : SocketCommandContext, ICommandContext { - public DiscordShardedClient Client { get; } - public SocketGuild Guild { get; } - public ISocketMessageChannel Channel { get; } - public SocketUser User { get; } - public SocketUserMessage Message { get; } - - public bool IsPrivate => Channel is IPrivateChannel; + public new DiscordShardedClient Client { get; } public ShardedCommandContext(DiscordShardedClient client, SocketUserMessage msg) + : base(client.GetShard(GetShardId(client, (msg.Channel as SocketGuildChannel)?.Guild)), msg) { Client = client; - Guild = (msg.Channel as SocketGuildChannel)?.Guild; - Channel = msg.Channel; - User = msg.Author; - Message = msg; } + private static int GetShardId(DiscordShardedClient client, IGuild guild) + => guild == null ? 0 : client.GetShardIdFor(guild); + //ICommandContext IDiscordClient ICommandContext.Client => Client; - IGuild ICommandContext.Guild => Guild; - IMessageChannel ICommandContext.Channel => Channel; - IUser ICommandContext.User => User; - IUserMessage ICommandContext.Message => Message; } } From d89804d7c7b4923bc9132fa622de3b843bcf3904 Mon Sep 17 00:00:00 2001 From: Pat Murphy Date: Wed, 5 Jul 2017 16:56:43 -0700 Subject: [PATCH 59/62] Fix potential nullref in embedBuilder value setter (#734) * Fix potential nullref in embedBuilder value setter * Null check on footer iconUrl * Adding checks for the other URL properties * Adding IsNullOrUri extension * Setting StringExtensions as internal --- .../Extensions/StringExtensions.cs | 10 ++++++++++ .../Entities/Messages/EmbedBuilder.cs | 14 +++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 src/Discord.Net.Core/Extensions/StringExtensions.cs diff --git a/src/Discord.Net.Core/Extensions/StringExtensions.cs b/src/Discord.Net.Core/Extensions/StringExtensions.cs new file mode 100644 index 000000000..c0ebb2626 --- /dev/null +++ b/src/Discord.Net.Core/Extensions/StringExtensions.cs @@ -0,0 +1,10 @@ +using System; + +namespace Discord +{ + internal static class StringExtensions + { + public static bool IsNullOrUri(this string url) => + string.IsNullOrEmpty(url) || Uri.IsWellFormedUriString(url, UriKind.Absolute); + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs index 2331f6749..7b0285891 100644 --- a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs @@ -44,7 +44,7 @@ namespace Discord get => _embed.Url; set { - if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); _embed.Url = value; } } @@ -53,7 +53,7 @@ namespace Discord get => _embed.Thumbnail?.Url; set { - if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); _embed.Thumbnail = new EmbedThumbnail(value, null, null, null); } } @@ -62,7 +62,7 @@ namespace Discord get => _embed.Image?.Url; set { - if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); _embed.Image = new EmbedImage(value, null, null, null); } } @@ -260,7 +260,7 @@ namespace Discord get => _field.Value; set { - var stringValue = value.ToString(); + var stringValue = value?.ToString(); if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); _field.Value = stringValue; @@ -313,7 +313,7 @@ namespace Discord get => _author.Url; set { - if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); _author.Url = value; } } @@ -322,7 +322,7 @@ namespace Discord get => _author.IconUrl; set { - if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); _author.IconUrl = value; } } @@ -372,7 +372,7 @@ namespace Discord get => _footer.IconUrl; set { - if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); _footer.IconUrl = value; } } From b35fbac017e496c885854eaa54f73a4c4092b561 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Jul 2017 21:30:25 -0300 Subject: [PATCH 60/62] Removed version from README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5ed907ee..2b58d4579 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Discord.Net v1.0.0-rc +# Discord.Net [![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net) [![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net) [![Build status](https://ci.appveyor.com/api/projects/status/5sb7n8a09w9clute/branch/dev?svg=true)](https://ci.appveyor.com/project/RogueException/discord-net/branch/dev) From d27657d193fdffe5c3b7f687ba2372fb57620334 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Jul 2017 21:31:59 -0300 Subject: [PATCH 61/62] Removed hardcoded suffix from nuspec --- appveyor.yml | 2 +- src/Discord.Net/Discord.Net.nuspec | 38 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3bf70c09c..25b4dc9bd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ after_build: if ($Env:APPVEYOR_REPO_TAG -eq "true") { nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" } else { - nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$Env:BUILD" + nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="build-$Env:BUILD" } - ps: Get-ChildItem artifacts\*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 19f173c3d..864083599 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 1.0.1-build$suffix$ + 1.0.1$suffix$ Discord.Net RogueException RogueException @@ -13,28 +13,28 @@ false - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + From ff10f17cba6b25642ac3543a47c32a3d513baaac Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Jul 2017 21:35:38 -0300 Subject: [PATCH 62/62] Proper versioning is hard --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 25b4dc9bd..d94e2ad68 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ after_build: if ($Env:APPVEYOR_REPO_TAG -eq "true") { nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" } else { - nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="build-$Env:BUILD" + nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-build-$Env:BUILD" } - ps: Get-ChildItem artifacts\*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }