From fc5adca94e30a3f7e51d2ae4f4bbf1309481a7e7 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Wed, 27 Sep 2017 18:17:48 -0400 Subject: [PATCH 01/27] Obsolete TokenType.User --- src/Discord.Net.Core/TokenType.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/TokenType.cs b/src/Discord.Net.Core/TokenType.cs index e19197cd6..c351b1c19 100644 --- a/src/Discord.Net.Core/TokenType.cs +++ b/src/Discord.Net.Core/TokenType.cs @@ -1,7 +1,10 @@ -namespace Discord +using System; + +namespace Discord { public enum TokenType { + [Obsolete("User logins are being deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827")] User, Bearer, Bot, From 9b7afec4cc5d92b8d7b607bb193d92b42b1e84ef Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Wed, 27 Sep 2017 18:20:31 -0400 Subject: [PATCH 02/27] Add BaseSocketClient object. (#773) * Add BaseDiscordClient. Add various missing RequestOptions args. DiscordSocketClient and DiscordShardedClient's shared members now exist in this abstract class. * Add ShardReady event. * Style consistency. Remove extraneous overloads. Remove extraneous overloads. * Add BaseSocketClient#DownloadUsersAsync(). Style cleanups. * Add ShardLatencyUpdated event. Style cleanup. * Hook LatencyUpdated for ShardedClient. * Begone whitespace. * I'm good at this, I swear. >_> * Add back DiscordShardedClient.UserPresenceUpdated * Add ObsoleteAttribute * Removing the UserPresenceUpdated event. --- src/Discord.Net.Rest/BaseDiscordClient.cs | 12 +- .../BaseSocketClient.Events.cs | 193 +++++++++++++++++ src/Discord.Net.WebSocket/BaseSocketClient.cs | 93 ++++++++ .../DiscordShardedClient.Events.cs | 205 ++---------------- .../DiscordShardedClient.cs | 68 +++--- .../DiscordSocketClient.Events.cs | 191 +--------------- .../DiscordSocketClient.cs | 78 +++---- 7 files changed, 377 insertions(+), 463 deletions(-) create mode 100644 src/Discord.Net.WebSocket/BaseSocketClient.Events.cs create mode 100644 src/Discord.Net.WebSocket/BaseSocketClient.cs diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 87f974dc2..47a946f20 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -85,7 +85,8 @@ namespace Discord.Rest await _loggedInEvent.InvokeAsync().ConfigureAwait(false); } - internal virtual Task OnLoginAsync(TokenType tokenType, string token) { return Task.Delay(0); } + internal virtual Task OnLoginAsync(TokenType tokenType, string token) + => Task.Delay(0); /// public async Task LogoutAsync() @@ -110,7 +111,8 @@ namespace Discord.Rest await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); } - internal virtual Task OnLogoutAsync() { return Task.Delay(0); } + internal virtual Task OnLogoutAsync() + => Task.Delay(0); internal virtual void Dispose(bool disposing) { @@ -127,7 +129,8 @@ namespace Discord.Rest ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; ISelfUser IDiscordClient.CurrentUser => CurrentUser; - Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) { throw new NotSupportedException(); } + Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) + => throw new NotSupportedException(); Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); @@ -148,7 +151,8 @@ namespace Discord.Rest => Task.FromResult(null); Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); - Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) { throw new NotSupportedException(); } + Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) + => throw new NotSupportedException(); Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs new file mode 100644 index 000000000..e881a7855 --- /dev/null +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -0,0 +1,193 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + public partial class BaseSocketClient + { + //Channels + /// Fired when a channel is created. + public event Func ChannelCreated + { + add { _channelCreatedEvent.Add(value); } + remove { _channelCreatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); + /// Fired when a channel is destroyed. + public event Func ChannelDestroyed { + add { _channelDestroyedEvent.Add(value); } + remove { _channelDestroyedEvent.Remove(value); } + } + internal readonly AsyncEvent> _channelDestroyedEvent = new AsyncEvent>(); + /// Fired when a channel is updated. + public event Func ChannelUpdated { + add { _channelUpdatedEvent.Add(value); } + remove { _channelUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _channelUpdatedEvent = new AsyncEvent>(); + + //Messages + /// Fired when a message is received. + public event Func MessageReceived { + add { _messageReceivedEvent.Add(value); } + remove { _messageReceivedEvent.Remove(value); } + } + internal readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); + /// Fired when a message is deleted. + public event Func, ISocketMessageChannel, Task> MessageDeleted { + add { _messageDeletedEvent.Add(value); } + remove { _messageDeletedEvent.Remove(value); } + } + internal readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + /// Fired when a message is updated. + public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated { + add { _messageUpdatedEvent.Add(value); } + remove { _messageUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, ISocketMessageChannel, Task>>(); + /// Fired when a reaction is added to a message. + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { + add { _reactionAddedEvent.Add(value); } + remove { _reactionAddedEvent.Remove(value); } + } + internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + /// Fired when a reaction is removed from a message. + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { + add { _reactionRemovedEvent.Add(value); } + remove { _reactionRemovedEvent.Remove(value); } + } + internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + /// Fired when all reactions to a message are cleared. + public event Func, ISocketMessageChannel, Task> ReactionsCleared { + add { _reactionsClearedEvent.Add(value); } + remove { _reactionsClearedEvent.Remove(value); } + } + internal readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + + //Roles + /// Fired when a role is created. + public event Func RoleCreated { + add { _roleCreatedEvent.Add(value); } + remove { _roleCreatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _roleCreatedEvent = new AsyncEvent>(); + /// Fired when a role is deleted. + public event Func RoleDeleted { + add { _roleDeletedEvent.Add(value); } + remove { _roleDeletedEvent.Remove(value); } + } + internal readonly AsyncEvent> _roleDeletedEvent = new AsyncEvent>(); + /// Fired when a role is updated. + public event Func RoleUpdated { + add { _roleUpdatedEvent.Add(value); } + remove { _roleUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _roleUpdatedEvent = new AsyncEvent>(); + + //Guilds + /// Fired when the connected account joins a guild. + public event Func JoinedGuild { + add { _joinedGuildEvent.Add(value); } + remove { _joinedGuildEvent.Remove(value); } + } + internal readonly AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); + /// Fired when the connected account leaves a guild. + public event Func LeftGuild { + add { _leftGuildEvent.Add(value); } + remove { _leftGuildEvent.Remove(value); } + } + internal readonly AsyncEvent> _leftGuildEvent = new AsyncEvent>(); + /// Fired when a guild becomes available. + public event Func GuildAvailable { + add { _guildAvailableEvent.Add(value); } + remove { _guildAvailableEvent.Remove(value); } + } + internal readonly AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); + /// Fired when a guild becomes unavailable. + public event Func GuildUnavailable { + add { _guildUnavailableEvent.Add(value); } + remove { _guildUnavailableEvent.Remove(value); } + } + internal readonly AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); + /// Fired when offline guild members are downloaded. + public event Func GuildMembersDownloaded { + add { _guildMembersDownloadedEvent.Add(value); } + remove { _guildMembersDownloadedEvent.Remove(value); } + } + internal readonly AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); + /// Fired when a guild is updated. + public event Func GuildUpdated { + add { _guildUpdatedEvent.Add(value); } + remove { _guildUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); + + //Users + /// Fired when a user joins a guild. + public event Func UserJoined { + add { _userJoinedEvent.Add(value); } + remove { _userJoinedEvent.Remove(value); } + } + internal readonly AsyncEvent> _userJoinedEvent = new AsyncEvent>(); + /// Fired when a user leaves a guild. + public event Func UserLeft { + add { _userLeftEvent.Add(value); } + remove { _userLeftEvent.Remove(value); } + } + internal readonly AsyncEvent> _userLeftEvent = new AsyncEvent>(); + /// Fired when a user is banned from a guild. + public event Func UserBanned { + add { _userBannedEvent.Add(value); } + remove { _userBannedEvent.Remove(value); } + } + internal readonly AsyncEvent> _userBannedEvent = new AsyncEvent>(); + /// Fired when a user is unbanned from a guild. + public event Func UserUnbanned { + add { _userUnbannedEvent.Add(value); } + remove { _userUnbannedEvent.Remove(value); } + } + internal readonly AsyncEvent> _userUnbannedEvent = new AsyncEvent>(); + /// Fired when a user is updated. + public event Func UserUpdated { + add { _userUpdatedEvent.Add(value); } + remove { _userUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); + /// Fired when a guild member is updated, or a member presence is updated. + public event Func GuildMemberUpdated { + add { _guildMemberUpdatedEvent.Add(value); } + remove { _guildMemberUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _guildMemberUpdatedEvent = new AsyncEvent>(); + /// Fired when a user joins, leaves, or moves voice channels. + public event Func UserVoiceStateUpdated { + add { _userVoiceStateUpdatedEvent.Add(value); } + remove { _userVoiceStateUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _userVoiceStateUpdatedEvent = new AsyncEvent>(); + /// Fired when the connected account is updated. + public event Func CurrentUserUpdated { + add { _selfUpdatedEvent.Add(value); } + remove { _selfUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); + /// Fired when a user starts typing. + public event Func UserIsTyping { + add { _userIsTypingEvent.Add(value); } + remove { _userIsTypingEvent.Remove(value); } + } + internal readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); + /// Fired when a user joins a group channel. + public event Func RecipientAdded { + add { _recipientAddedEvent.Add(value); } + remove { _recipientAddedEvent.Remove(value); } + } + internal readonly AsyncEvent> _recipientAddedEvent = new AsyncEvent>(); + /// Fired when a user is removed from a group channel. + public event Func RecipientRemoved { + add { _recipientRemovedEvent.Add(value); } + remove { _recipientRemovedEvent.Remove(value); } + } + internal readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>(); + } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs new file mode 100644 index 000000000..d248285cd --- /dev/null +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Discord.API; +using Discord.Rest; + +namespace Discord.WebSocket +{ + public abstract partial class BaseSocketClient : BaseDiscordClient, IDiscordClient + { + protected readonly DiscordSocketConfig _baseconfig; + + /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. + public abstract int Latency { get; protected set; } + public abstract UserStatus Status { get; protected set; } + public abstract Game? Game { get; protected set; } + + internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; + + public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } + public abstract IReadOnlyCollection Guilds { get; } + public abstract IReadOnlyCollection PrivateChannels { get; } + public abstract IReadOnlyCollection VoiceRegions { get; } + + internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) + : base(config, client) => _baseconfig = config; + private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) + => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); + + /// + public abstract Task GetApplicationInfoAsync(RequestOptions options = null); + /// + public abstract SocketUser GetUser(ulong id); + /// + public abstract SocketUser GetUser(string username, string discriminator); + /// + public abstract SocketChannel GetChannel(ulong id); + /// + public abstract SocketGuild GetGuild(ulong id); + /// + public abstract RestVoiceRegion GetVoiceRegion(string id); + /// + public abstract Task StartAsync(); + /// + public abstract Task StopAsync(); + public abstract Task SetStatusAsync(UserStatus status); + public abstract Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming); + public abstract Task DownloadUsersAsync(IEnumerable guilds); + + /// + public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) + => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default); + /// + public Task> GetConnectionsAsync(RequestOptions options = null) + => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); + /// + public Task GetInviteAsync(string inviteId, RequestOptions options = null) + => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); + + // IDiscordClient + async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) + => await GetApplicationInfoAsync(options).ConfigureAwait(false); + + Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetChannel(id)); + Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(PrivateChannels); + + async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) + => await GetConnectionsAsync(options).ConfigureAwait(false); + + async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + => await GetInviteAsync(inviteId, options).ConfigureAwait(false); + + Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetGuild(id)); + Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(Guilds); + + async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) + => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); + + Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetUser(id)); + Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) + => Task.FromResult(GetUser(username, discriminator)); + + Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) + => Task.FromResult(GetVoiceRegion(id)); + Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) + => Task.FromResult>(VoiceRegions); + } +} diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs index c52675e70..c9e679669 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs @@ -3,197 +3,36 @@ using System.Threading.Tasks; namespace Discord.WebSocket { - //TODO: Add event docstrings public partial class DiscordShardedClient { - //Channels - public event Func ChannelCreated + //General + /// Fired when a shard is connected to the Discord gateway. + public event Func ShardConnected { - add { _channelCreatedEvent.Add(value); } - remove { _channelCreatedEvent.Remove(value); } + add { _shardConnectedEvent.Add(value); } + remove { _shardConnectedEvent.Remove(value); } } - private readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); - public event Func ChannelDestroyed + private readonly AsyncEvent> _shardConnectedEvent = new AsyncEvent>(); + /// Fired when a shard is disconnected from the Discord gateway. + public event Func ShardDisconnected { - add { _channelDestroyedEvent.Add(value); } - remove { _channelDestroyedEvent.Remove(value); } + add { _shardDisconnectedEvent.Add(value); } + remove { _shardDisconnectedEvent.Remove(value); } } - private readonly AsyncEvent> _channelDestroyedEvent = new AsyncEvent>(); - public event Func ChannelUpdated + private readonly AsyncEvent> _shardDisconnectedEvent = new AsyncEvent>(); + /// Fired when a guild data for a shard has finished downloading. + public event Func ShardReady { - add { _channelUpdatedEvent.Add(value); } - remove { _channelUpdatedEvent.Remove(value); } + add { _shardReadyEvent.Add(value); } + remove { _shardReadyEvent.Remove(value); } } - private readonly AsyncEvent> _channelUpdatedEvent = new AsyncEvent>(); - - //Messages - public event Func MessageReceived - { - add { _messageReceivedEvent.Add(value); } - remove { _messageReceivedEvent.Remove(value); } - } - private readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); - public event Func, ISocketMessageChannel, Task> MessageDeleted - { - add { _messageDeletedEvent.Add(value); } - remove { _messageDeletedEvent.Remove(value); } - } - private readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); - public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated - { - add { _messageUpdatedEvent.Add(value); } - remove { _messageUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, ISocketMessageChannel, Task>>(); - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded - { - add { _reactionAddedEvent.Add(value); } - remove { _reactionAddedEvent.Remove(value); } - } - private readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved - { - add { _reactionRemovedEvent.Add(value); } - remove { _reactionRemovedEvent.Remove(value); } - } - private readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); - public event Func, ISocketMessageChannel, Task> ReactionsCleared - { - add { _reactionsClearedEvent.Add(value); } - remove { _reactionsClearedEvent.Remove(value); } - } - private readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); - - //Roles - public event Func RoleCreated - { - add { _roleCreatedEvent.Add(value); } - remove { _roleCreatedEvent.Remove(value); } - } - private readonly AsyncEvent> _roleCreatedEvent = new AsyncEvent>(); - public event Func RoleDeleted - { - add { _roleDeletedEvent.Add(value); } - remove { _roleDeletedEvent.Remove(value); } - } - private readonly AsyncEvent> _roleDeletedEvent = new AsyncEvent>(); - public event Func RoleUpdated - { - add { _roleUpdatedEvent.Add(value); } - remove { _roleUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _roleUpdatedEvent = new AsyncEvent>(); - - //Guilds - public event Func JoinedGuild - { - add { _joinedGuildEvent.Add(value); } - remove { _joinedGuildEvent.Remove(value); } - } - private AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); - public event Func LeftGuild - { - add { _leftGuildEvent.Add(value); } - remove { _leftGuildEvent.Remove(value); } - } - private AsyncEvent> _leftGuildEvent = new AsyncEvent>(); - public event Func GuildAvailable - { - add { _guildAvailableEvent.Add(value); } - remove { _guildAvailableEvent.Remove(value); } - } - private AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); - public event Func GuildUnavailable - { - add { _guildUnavailableEvent.Add(value); } - remove { _guildUnavailableEvent.Remove(value); } - } - private AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); - public event Func GuildMembersDownloaded - { - add { _guildMembersDownloadedEvent.Add(value); } - remove { _guildMembersDownloadedEvent.Remove(value); } - } - private AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); - public event Func GuildUpdated - { - add { _guildUpdatedEvent.Add(value); } - remove { _guildUpdatedEvent.Remove(value); } - } - private AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); - - //Users - public event Func UserJoined - { - add { _userJoinedEvent.Add(value); } - remove { _userJoinedEvent.Remove(value); } - } - private readonly AsyncEvent> _userJoinedEvent = new AsyncEvent>(); - public event Func UserLeft - { - add { _userLeftEvent.Add(value); } - remove { _userLeftEvent.Remove(value); } - } - private readonly AsyncEvent> _userLeftEvent = new AsyncEvent>(); - public event Func UserBanned - { - add { _userBannedEvent.Add(value); } - remove { _userBannedEvent.Remove(value); } - } - private readonly AsyncEvent> _userBannedEvent = new AsyncEvent>(); - public event Func UserUnbanned - { - add { _userUnbannedEvent.Add(value); } - remove { _userUnbannedEvent.Remove(value); } - } - private readonly AsyncEvent> _userUnbannedEvent = new AsyncEvent>(); - public event Func UserUpdated - { - add { _userUpdatedEvent.Add(value); } - remove { _userUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); - public event Func GuildMemberUpdated - { - add { _guildMemberUpdatedEvent.Add(value); } - remove { _guildMemberUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _guildMemberUpdatedEvent = new AsyncEvent>(); - public event Func, SocketUser, SocketPresence, SocketPresence, Task> UserPresenceUpdated - { - add { _userPresenceUpdatedEvent.Add(value); } - remove { _userPresenceUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent, SocketUser, SocketPresence, SocketPresence, Task>> _userPresenceUpdatedEvent = new AsyncEvent, SocketUser, SocketPresence, SocketPresence, Task>>(); - public event Func UserVoiceStateUpdated - { - add { _userVoiceStateUpdatedEvent.Add(value); } - remove { _userVoiceStateUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _userVoiceStateUpdatedEvent = new AsyncEvent>(); - public event Func CurrentUserUpdated - { - add { _selfUpdatedEvent.Add(value); } - remove { _selfUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); - public event Func UserIsTyping - { - add { _userIsTypingEvent.Add(value); } - remove { _userIsTypingEvent.Remove(value); } - } - private readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); - public event Func RecipientAdded - { - add { _recipientAddedEvent.Add(value); } - remove { _recipientAddedEvent.Remove(value); } - } - private readonly AsyncEvent> _recipientAddedEvent = new AsyncEvent>(); - public event Func RecipientRemoved + private readonly AsyncEvent> _shardReadyEvent = new AsyncEvent>(); + /// Fired when a shard receives a heartbeat from the Discord gateway. + public event Func ShardLatencyUpdated { - add { _recipientRemovedEvent.Add(value); } - remove { _recipientRemovedEvent.Remove(value); } + add { _shardLatencyUpdatedEvent.Add(value); } + remove { _shardLatencyUpdatedEvent.Remove(value); } } - private readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _shardLatencyUpdatedEvent = new AsyncEvent>(); } -} +} \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index ab2cb9266..6c2a0f3b9 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -9,7 +9,7 @@ using System.Threading; namespace Discord.WebSocket { - public partial class DiscordShardedClient : BaseDiscordClient, IDiscordClient + public partial class DiscordShardedClient : BaseSocketClient, IDiscordClient { private readonly DiscordSocketConfig _baseConfig; private readonly SemaphoreSlim _connectionGroupLock; @@ -20,16 +20,15 @@ namespace Discord.WebSocket private bool _automaticShards; /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. - public int Latency => GetLatency(); - public UserStatus Status => _shards[0].Status; - public Game? Game => _shards[0].Game; + public override int Latency { get => GetLatency(); protected set { } } + public override UserStatus Status { get => _shards[0].Status; protected set { } } + public override Game? Game { get => _shards[0].Game; protected 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 IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); - public IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); + public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); + public override IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); public IReadOnlyCollection Shards => _shards; - public IReadOnlyCollection VoiceRegions => _shards[0].VoiceRegions; + public override IReadOnlyCollection VoiceRegions => _shards[0].VoiceRegions; /// Creates a new REST/WebSocket discord client. public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } @@ -115,15 +114,11 @@ namespace Discord.WebSocket } /// - public async Task StartAsync() - { - await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false); - } + public override async Task StartAsync() + => await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false); /// - public async Task StopAsync() - { - await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false); - } + public override async Task StopAsync() + => await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false); public DiscordSocketClient GetShard(int id) { @@ -141,17 +136,15 @@ namespace Discord.WebSocket => GetShardFor(guild.Id); /// - public async Task GetApplicationInfoAsync() - => await _shards[0].GetApplicationInfoAsync().ConfigureAwait(false); + public override async Task GetApplicationInfoAsync(RequestOptions options = null) + => await _shards[0].GetApplicationInfoAsync(options).ConfigureAwait(false); /// - public SocketGuild GetGuild(ulong id) => GetShardFor(id).GetGuild(id); - /// - public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) - => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, new RequestOptions()); + public override SocketGuild GetGuild(ulong id) + => GetShardFor(id).GetGuild(id); /// - public SocketChannel GetChannel(ulong id) + public override SocketChannel GetChannel(ulong id) { for (int i = 0; i < _shards.Length; i++) { @@ -175,11 +168,7 @@ namespace Discord.WebSocket for (int i = 0; i < _shards.Length; i++) result += _shards[i].PrivateChannels.Count; return result; - } - - /// - public Task> GetConnectionsAsync() - => ClientHelper.GetConnectionsAsync(this, new RequestOptions()); + } private IEnumerable GetGuilds() { @@ -195,14 +184,10 @@ namespace Discord.WebSocket for (int i = 0; i < _shards.Length; i++) result += _shards[i].Guilds.Count; return result; - } + } /// - public Task GetInviteAsync(string inviteId) - => ClientHelper.GetInviteAsync(this, inviteId, new RequestOptions()); - - /// - public SocketUser GetUser(ulong id) + public override SocketUser GetUser(ulong id) { for (int i = 0; i < _shards.Length; i++) { @@ -213,7 +198,7 @@ namespace Discord.WebSocket return null; } /// - public SocketUser GetUser(string username, string discriminator) + public override SocketUser GetUser(string username, string discriminator) { for (int i = 0; i < _shards.Length; i++) { @@ -225,11 +210,11 @@ namespace Discord.WebSocket } /// - public RestVoiceRegion GetVoiceRegion(string id) + public override RestVoiceRegion GetVoiceRegion(string id) => _shards[0].GetVoiceRegion(id); /// Downloads the users list for the provided guilds, if they don't have a complete list. - public async Task DownloadUsersAsync(IEnumerable guilds) + public override async Task DownloadUsersAsync(IEnumerable guilds) { for (int i = 0; i < _shards.Length; i++) { @@ -248,12 +233,12 @@ namespace Discord.WebSocket return (int)Math.Round(total / (double)_shards.Length); } - public async Task SetStatusAsync(UserStatus status) + public override async Task SetStatusAsync(UserStatus status) { for (int i = 0; i < _shards.Length; i++) await _shards[i].SetStatusAsync(status).ConfigureAwait(false); } - public async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) + public override async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) { for (int i = 0; i < _shards.Length; i++) await _shards[i].SetGameAsync(name, streamUrl, streamType).ConfigureAwait(false); @@ -281,6 +266,11 @@ namespace Discord.WebSocket }; } + client.Connected += () => _shardConnectedEvent.InvokeAsync(client); + client.Disconnected += (exception) => _shardDisconnectedEvent.InvokeAsync(exception, client); + client.Ready += () => _shardReadyEvent.InvokeAsync(client); + client.LatencyUpdated += (oldLatency, newLatency) => _shardLatencyUpdatedEvent.InvokeAsync(oldLatency, newLatency, client); + client.ChannelCreated += (channel) => _channelCreatedEvent.InvokeAsync(channel); client.ChannelDestroyed += (channel) => _channelDestroyedEvent.InvokeAsync(channel); client.ChannelUpdated += (oldChannel, newChannel) => _channelUpdatedEvent.InvokeAsync(oldChannel, newChannel); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs index fb155e535..1222b270e 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs @@ -2,218 +2,37 @@ using System.Threading.Tasks; namespace Discord.WebSocket -{ - //TODO: Add event docstrings +{ public partial class DiscordSocketClient { //General + /// Fired when connected to the Discord gateway. public event Func Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } } private readonly AsyncEvent> _connectedEvent = new AsyncEvent>(); + /// Fired when disconnected to the Discord gateway. public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + /// Fired when guild data has finished downloading. public event Func Ready { add { _readyEvent.Add(value); } remove { _readyEvent.Remove(value); } } private readonly AsyncEvent> _readyEvent = new AsyncEvent>(); + /// Fired when a heartbeat is received from the Discord gateway. public event Func LatencyUpdated { add { _latencyUpdatedEvent.Add(value); } remove { _latencyUpdatedEvent.Remove(value); } } private readonly AsyncEvent> _latencyUpdatedEvent = new AsyncEvent>(); - - //Channels - public event Func ChannelCreated - { - add { _channelCreatedEvent.Add(value); } - remove { _channelCreatedEvent.Remove(value); } - } - private readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); - public event Func ChannelDestroyed - { - add { _channelDestroyedEvent.Add(value); } - remove { _channelDestroyedEvent.Remove(value); } - } - private readonly AsyncEvent> _channelDestroyedEvent = new AsyncEvent>(); - public event Func ChannelUpdated - { - add { _channelUpdatedEvent.Add(value); } - remove { _channelUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _channelUpdatedEvent = new AsyncEvent>(); - - //Messages - public event Func MessageReceived - { - add { _messageReceivedEvent.Add(value); } - remove { _messageReceivedEvent.Remove(value); } - } - private readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); - public event Func, ISocketMessageChannel, Task> MessageDeleted - { - add { _messageDeletedEvent.Add(value); } - remove { _messageDeletedEvent.Remove(value); } - } - private readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); - public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated - { - add { _messageUpdatedEvent.Add(value); } - remove { _messageUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, ISocketMessageChannel, Task>>(); - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded - { - add { _reactionAddedEvent.Add(value); } - remove { _reactionAddedEvent.Remove(value); } - } - private readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved - { - add { _reactionRemovedEvent.Add(value); } - remove { _reactionRemovedEvent.Remove(value); } - } - private readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); - public event Func, ISocketMessageChannel, Task> ReactionsCleared - { - add { _reactionsClearedEvent.Add(value); } - remove { _reactionsClearedEvent.Remove(value); } - } - private readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); - - //Roles - public event Func RoleCreated - { - add { _roleCreatedEvent.Add(value); } - remove { _roleCreatedEvent.Remove(value); } - } - private readonly AsyncEvent> _roleCreatedEvent = new AsyncEvent>(); - public event Func RoleDeleted - { - add { _roleDeletedEvent.Add(value); } - remove { _roleDeletedEvent.Remove(value); } - } - private readonly AsyncEvent> _roleDeletedEvent = new AsyncEvent>(); - public event Func RoleUpdated - { - add { _roleUpdatedEvent.Add(value); } - remove { _roleUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _roleUpdatedEvent = new AsyncEvent>(); - - //Guilds - public event Func JoinedGuild - { - add { _joinedGuildEvent.Add(value); } - remove { _joinedGuildEvent.Remove(value); } - } - private readonly AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); - public event Func LeftGuild - { - add { _leftGuildEvent.Add(value); } - remove { _leftGuildEvent.Remove(value); } - } - private readonly AsyncEvent> _leftGuildEvent = new AsyncEvent>(); - public event Func GuildAvailable - { - add { _guildAvailableEvent.Add(value); } - remove { _guildAvailableEvent.Remove(value); } - } - private readonly AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); - public event Func GuildUnavailable - { - add { _guildUnavailableEvent.Add(value); } - remove { _guildUnavailableEvent.Remove(value); } - } - private readonly AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); - public event Func GuildMembersDownloaded - { - add { _guildMembersDownloadedEvent.Add(value); } - remove { _guildMembersDownloadedEvent.Remove(value); } - } - private readonly AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); - public event Func GuildUpdated - { - add { _guildUpdatedEvent.Add(value); } - remove { _guildUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); - - //Users - public event Func UserJoined - { - add { _userJoinedEvent.Add(value); } - remove { _userJoinedEvent.Remove(value); } - } - private readonly AsyncEvent> _userJoinedEvent = new AsyncEvent>(); - public event Func UserLeft - { - add { _userLeftEvent.Add(value); } - remove { _userLeftEvent.Remove(value); } - } - private readonly AsyncEvent> _userLeftEvent = new AsyncEvent>(); - public event Func UserBanned - { - add { _userBannedEvent.Add(value); } - remove { _userBannedEvent.Remove(value); } - } - private readonly AsyncEvent> _userBannedEvent = new AsyncEvent>(); - public event Func UserUnbanned - { - add { _userUnbannedEvent.Add(value); } - remove { _userUnbannedEvent.Remove(value); } - } - private readonly AsyncEvent> _userUnbannedEvent = new AsyncEvent>(); - public event Func UserUpdated - { - add { _userUpdatedEvent.Add(value); } - remove { _userUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); - public event Func GuildMemberUpdated - { - add { _guildMemberUpdatedEvent.Add(value); } - remove { _guildMemberUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _guildMemberUpdatedEvent = new AsyncEvent>(); - public event Func UserVoiceStateUpdated - { - add { _userVoiceStateUpdatedEvent.Add(value); } - remove { _userVoiceStateUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _userVoiceStateUpdatedEvent = new AsyncEvent>(); - public event Func CurrentUserUpdated - { - add { _selfUpdatedEvent.Add(value); } - remove { _selfUpdatedEvent.Remove(value); } - } - private readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); - public event Func UserIsTyping - { - add { _userIsTypingEvent.Add(value); } - remove { _userIsTypingEvent.Remove(value); } - } - private readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); - public event Func RecipientAdded - { - add { _recipientAddedEvent.Add(value); } - remove { _recipientAddedEvent.Remove(value); } - } - private readonly AsyncEvent> _recipientAddedEvent = new AsyncEvent>(); - public event Func RecipientRemoved - { - add { _recipientRemovedEvent.Add(value); } - remove { _recipientRemovedEvent.Remove(value); } - } - private readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>(); } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 5256e0efd..09b10aac9 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -19,7 +19,7 @@ using GameModel = Discord.API.Game; namespace Discord.WebSocket { - public partial class DiscordSocketClient : BaseDiscordClient, IDiscordClient + public partial class DiscordSocketClient : BaseSocketClient, IDiscordClient { private readonly ConcurrentQueue _largeGuilds; private readonly JsonSerializer _serializer; @@ -44,10 +44,10 @@ namespace Discord.WebSocket public int ShardId { get; } /// Gets the current connection state of this client. public ConnectionState ConnectionState => _connection.State; - /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. - public int Latency { get; private set; } - internal UserStatus Status { get; private set; } = UserStatus.Online; - internal Game? Game { get; private set; } + /// + public override int Latency { get; protected set; } + public override UserStatus Status { get; protected set; } = UserStatus.Online; + public override Game? Game { get; protected set; } //From DiscordSocketConfig internal int TotalShards { get; private set; } @@ -60,14 +60,13 @@ namespace Discord.WebSocket internal int? HandlerTimeout { get; private set; } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; - 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 override IReadOnlyCollection Guilds => State.Guilds; + public override IReadOnlyCollection PrivateChannels => State.PrivateChannels; public IReadOnlyCollection DMChannels => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); public IReadOnlyCollection GroupChannels => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); - public IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); + public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); /// Creates a new REST/WebSocket discord client. public DiscordSocketClient() : this(new DiscordSocketConfig()) { } @@ -155,9 +154,9 @@ namespace Discord.WebSocket _voiceRegions = ImmutableDictionary.Create(); } - public async Task StartAsync() + public override async Task StartAsync() => await _connection.StartAsync().ConfigureAwait(false); - public async Task StopAsync() + public override async Task StopAsync() => await _connection.StopAsync().ConfigureAwait(false); private async Task OnConnectingAsync() @@ -231,44 +230,23 @@ namespace Discord.WebSocket } /// - public async Task GetApplicationInfoAsync() - { - return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, new RequestOptions())); - } - - /// - public SocketGuild GetGuild(ulong id) - { - return State.GetGuild(id); - } - /// - public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) - => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, new RequestOptions()); - - /// - public SocketChannel GetChannel(ulong id) - { - return State.GetChannel(id); - } + public override async Task GetApplicationInfoAsync(RequestOptions options = null) + => _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options ?? RequestOptions.Default).ConfigureAwait(false)); /// - public Task> GetConnectionsAsync() - => ClientHelper.GetConnectionsAsync(this, new RequestOptions()); + public override SocketGuild GetGuild(ulong id) + => State.GetGuild(id); /// - public Task GetInviteAsync(string inviteId) - => ClientHelper.GetInviteAsync(this, inviteId, new RequestOptions()); - + public override SocketChannel GetChannel(ulong id) + => State.GetChannel(id); + /// - public SocketUser GetUser(ulong id) - { - return State.GetUser(id); - } + public override SocketUser GetUser(ulong id) + => State.GetUser(id); /// - public SocketUser GetUser(string username, string discriminator) - { - return State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); - } + public override SocketUser GetUser(string username, string discriminator) + => State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) { return state.GetOrAddUser(model.Id, x => @@ -288,13 +266,11 @@ namespace Discord.WebSocket return user; }); } - internal void RemoveUser(ulong id) - { - State.RemoveUser(id); - } + internal void RemoveUser(ulong id) + => State.RemoveUser(id); /// - public RestVoiceRegion GetVoiceRegion(string id) + public override RestVoiceRegion GetVoiceRegion(string id) { if (_voiceRegions.TryGetValue(id, out RestVoiceRegion region)) return region; @@ -302,7 +278,7 @@ namespace Discord.WebSocket } /// Downloads the users list for the provided guilds, if they don't have a complete list. - public async Task DownloadUsersAsync(IEnumerable guilds) + public override async Task DownloadUsersAsync(IEnumerable guilds) { if (ConnectionState == ConnectionState.Connected) { @@ -340,7 +316,7 @@ namespace Discord.WebSocket } } - public async Task SetStatusAsync(UserStatus status) + public override async Task SetStatusAsync(UserStatus status) { Status = status; if (status == UserStatus.AFK) @@ -349,7 +325,7 @@ namespace Discord.WebSocket _statusSince = null; await SendStatusAsync().ConfigureAwait(false); } - public async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) + public override async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) { if (name != null) Game = new Game(name, streamUrl, streamType); From f8108871d6f2da081f83e3d5b9914c80c92db809 Mon Sep 17 00:00:00 2001 From: Chris Johnston <16418643+Chris-Johnston@users.noreply.github.com> Date: Thu, 28 Sep 2017 13:26:23 -0700 Subject: [PATCH 03/27] Typo fix for SocketGuildUser.Hierarchy description (#831) --- src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 844b0c7f4..66af20bb6 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -43,7 +43,7 @@ namespace Discord.WebSocket public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); public AudioInStream AudioStream => Guild.GetAudioStream(Id); - /// The position of the user within the role hirearchy. + /// The position of the user within the role hierarchy. /// The returned value equal to the position of the highest role the user has, /// or int.MaxValue if user is the server owner. public int Hierarchy From 347c5a0d3970d47e9c8c4a1fe358071e0ea81754 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 29 Sep 2017 17:37:07 -0400 Subject: [PATCH 04/27] Disable obsolete warnings in files that reference TokenType.User --- .../Attributes/Preconditions/RequireOwnerAttribute.cs | 3 ++- src/Discord.Net.Rest/DiscordRestApiClient.cs | 1 + src/Discord.Net.WebSocket/DiscordSocketClient.cs | 3 ++- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs index 0852ce39c..2055a358e 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -1,4 +1,5 @@ -using System; +#pragma warning disable CS0618 +using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index a6c42782a..6d551aa95 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable CS0618 using Discord.API.Rest; using Discord.Net; using Discord.Net.Converters; diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 09b10aac9..d152bbc03 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1,4 +1,5 @@ -using Discord.API; +#pragma warning disable CS0618 +using Discord.API; using Discord.API.Gateway; using Discord.Logging; using Discord.Net.Converters; diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index b47ca84e8..6001e4799 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1,4 +1,5 @@ -using Discord.Audio; +#pragma warning disable CS0618 +using Discord.Audio; using Discord.Rest; using System; using System.Collections.Concurrent; From e00f17fe5533e638d3218cdb1f8434b520cbe37e Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sun, 1 Oct 2017 15:37:13 -0400 Subject: [PATCH 05/27] Move DeleteMessagesAsync from IMessageChannel to ITextChannel (#829) --- src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs | 4 ---- src/Discord.Net.Core/Entities/Channels/ITextChannel.cs | 6 ++++++ src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs | 5 ----- src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs | 5 ----- .../Entities/Channels/RpcVirtualMessageChannel.cs | 5 ----- src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs | 5 ----- src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs | 5 ----- .../Entities/Channels/SocketDMChannel.cs | 5 ----- .../Entities/Channels/SocketGroupChannel.cs | 5 ----- 10 files changed, 7 insertions(+), 40 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index a465b3ad8..a1df5b4c7 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -29,10 +29,6 @@ namespace Discord CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// 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); - /// 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.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index b2b7e491f..be4dd0260 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Discord @@ -11,6 +12,11 @@ namespace Discord /// Gets the current topic for this text channel. string Topic { get; } + /// Bulk deletes multiple messages. + Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); + /// Bulk deletes multiple messages. + Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null); + /// Modifies this text channel. Task ModifyAsync(Action func, RequestOptions options = null); } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index d61b5d14a..8dcb8c284 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -181,7 +181,7 @@ namespace Discord.Rest return RestUserMessage.Create(client, channel, client.CurrentUser, model); } - public static async Task DeleteMessagesAsync(IMessageChannel channel, BaseDiscordClient client, + public static async Task DeleteMessagesAsync(ITextChannel channel, BaseDiscordClient client, IEnumerable messageIds, RequestOptions options) { var msgs = messageIds.ToArray(); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 8a31da3f1..d41441967 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -72,11 +72,6 @@ namespace Discord.Rest public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); - public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 44c118fee..aa5c0f7dc 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -85,11 +85,6 @@ namespace Discord.Rest public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); - public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index 7043c8c76..eb807423f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -42,11 +42,6 @@ namespace Discord.Rest public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); - public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs index da9bce700..42e590aca 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -53,11 +53,6 @@ namespace Discord.Rpc public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); - public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs index d449688a4..1c9355047 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -56,11 +56,6 @@ namespace Discord.Rpc public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); - public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 322a99496..00cef60f8 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -76,11 +76,6 @@ namespace Discord.WebSocket public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); - public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index dc1853e73..92a93a903 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -104,11 +104,6 @@ namespace Discord.WebSocket public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); - public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) From 22d79c100449f0f1e9380bdffc627e1097a82ff3 Mon Sep 17 00:00:00 2001 From: Hsu Still <341464@gmail.com> Date: Wed, 4 Oct 2017 04:48:05 +0800 Subject: [PATCH 06/27] Improve library documentation (#826) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve the Command Service documentation The following changes have been added to this PR: • Fix minor grammatical errors. • Capitalize terms such as Commands, Modules and such, as the context is specific to the lib. • Wrap methods and properties in code blocks. The docs page currently has several issues that remains to be fixed. 1. ```md >[!WARNING] >This article is out of date and has not been rewritten yet. Information is not guaranteed to be accurate. ``` The docs doesn't necessarily seem "out of date" as the warning claims. The basics seem pretty relevant to the latest version of the lib. 2. >“To manually load a module, invoke [CommandService.AddModuleAsync], by passing in the generic type of your module and optionally, a dependency map.” The latter part of the sentence seems off. Where should the user pass the dependency map to? It seems to suggest that `AddModuleAsync` has an argument to pass the dependency to. If it is referring to `AddModuleAsync(Type type)`, then I feel like it should be clarified here - or perhaps change the wording of the sentence. 3. >“First, you need to create an @System.IServiceProvider You may create your own IServiceProvider if you wish.” Any mention of @System.IServiceProvider is currently broken on the docs. 4. >“Submodules are Modules that reside within another one. Typically, submodules are used to create nested groups (although not required to create nested groups).” Clarification on the part after "although?" 5. >“Finally, pass the map into the LoadAssembly method. Your modules will automatically be loaded with this dependency map.” Where is this `LoadAssembly` method? 6. ```md >[!NOTE] >Preconditions can be applied to Modules, Groups, or Commands. ``` The docs should mention `ParameterPreconditionAttribute`'s existence. * Update line breaks to comply with docs standard * Change "you should..." to "instead, ..." * Trim trailing spaces * Change "inherits" to "inherit" * Fix Context warning note and add ReplyAsync xref * Fix broken xrefs * Fix [Command Service] xref * Fix consistency between TypeReaders and Preconditions returns * Add missing semi-colons in ServiceProvider sample * Change CommandContext to SocketCommandContext & change variable naming * Cleanup TypeReader section * Wrap [DontInject] in code block * Fix commands docs linking in intro * Improve Getting Started - Installation - Fix character misalignment to comply with docs standard. - Fix image numbering issues by moving the tooltips above some of the steps. - Add codeblocks to search terms like `Discord.Net`. - Remove broken `addons` reference. - Specify `.NET 4.6.1` as `.NET Framework 4.6.1`. - Minor cross-reference cleanup. * Fix Getting Started - Intro - Minor grammartical fixes. - Wrap mentions of the methods, properties, and events in code block. - Replace `Discord.Net` to `Discord.NET`. - Fix steps numbering under `Creating a Discord Bot` and `Adding your bot to a server`. - Change `Task-based Asynchronous Pattern ([TAP])` linking to mark the entire term instead. - Change code block of `Pong!` to quotation mark instead. * Fix cross references in Sending Voice * Mention parameter precondition attribute * Change `Discord.NET` to `Discord.Net` for consistency * Wrap project names in code blocks & minor fixes in Terminology * Change `add-ons` to `addons` for consistency * Fix cross references in Logging * Fix minor grammatical issues in "Working with Events" * Missed a tilda * Remove out-of-date warning in Commands * Minor grammatical fixes for Entities * Fix broken xref in Logging * Adjust service collection sample ...according to https://github.com/RogueException/Discord.Net/pull/826/files/f89aecb7bfa8fe82a541d3bba44d0e37c125d235#r141530227 * Update Command Handler sample - Update Main for C# 7.1. - Inject CommandService and DiscordSocketClient into the service collection. - Add Async suffix to asynchronous methods. * Minor grammatical fixes in Events * Revert 2 incorrect grammar corrections * Revert async Main sample * Add hardcode token notice in sample * Fix missing method for Command Handler * Modify module samples to use SocketCommandContext instead * Emphasize CommandContext and SocketCommandContext * Fix formatting for module sample * Add SocketCommandContext for Groups sample * Remove comma * Fix DepMap sample formatting * Replace [DontInject] with DontInjectAttribute with cross reference * Remove connection logic note There is no reason that this note should still be here since Ready event exists. * Add a new warning message informing the users the existence of CommandService * Make command handler private excellent change --- Discord.Net.sln | 8 +- docs/guides/commands/commands.md | 248 ++++++++++-------- .../commands/samples/command_handler.cs | 43 +-- .../commands/samples/dependency_map_setup.cs | 32 +-- docs/guides/commands/samples/empty-module.cs | 2 +- docs/guides/commands/samples/groups.cs | 4 +- docs/guides/commands/samples/module.cs | 59 ++--- docs/guides/concepts/entities.md | 11 +- docs/guides/concepts/events.md | 20 +- docs/guides/concepts/logging.md | 9 +- docs/guides/getting_started/installing.md | 125 ++++----- docs/guides/getting_started/intro.md | 134 +++++----- docs/guides/getting_started/terminology.md | 32 +-- docs/guides/voice/sending-voice.md | 8 +- 14 files changed, 394 insertions(+), 341 deletions(-) diff --git a/Discord.Net.sln b/Discord.Net.sln index a63606787..58bfcad86 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26730.12 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" EndProject @@ -142,4 +142,10 @@ Global {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} + EndGlobalSection + GlobalSection(CodealikeProperties) = postSolution + SolutionGuid = a45217b4-a401-4dbf-8654-34d2ec034cd9 + EndGlobalSection EndGlobal diff --git a/docs/guides/commands/commands.md b/docs/guides/commands/commands.md index e021b1eb3..6781764c9 100644 --- a/docs/guides/commands/commands.md +++ b/docs/guides/commands/commands.md @@ -1,24 +1,20 @@ # The Command Service ->[!WARNING] ->This article is out of date, and has not been rewritten yet. -Information is not guaranteed to be accurate. - [Discord.Commands](xref:Discord.Commands) provides an Attribute-based - Command Parser. +command parser. ## Setup -To use Commands, you must create a [Commands Service] and a -Command Handler. +To use Commands, you must create a [Command Service] and a Command +Handler. -Included below is a very bare-bones Command Handler. You can extend -your Command Handler as much as you like, however the below is the -bare minimum. +Included below is a very barebone Command Handler. You can extend your +Command Handler as much as you like; however, the below is the bare +minimum. -The CommandService optionally will accept a [CommandServiceConfig], +The `CommandService` will optionally accept a [CommandServiceConfig], which _does_ set a few default values for you. It is recommended to -look over the properties in [CommandServiceConfig], and their default +look over the properties in [CommandServiceConfig] and their default values. [!code-csharp[Command Handler](samples/command_handler.cs)] @@ -28,32 +24,32 @@ values. ## With Attributes -In 1.0, Commands can be defined ahead of time, with attributes, or -at runtime, with builders. +In 1.0, Commands can be defined ahead of time with attributes, or at +runtime with builders. -For most bots, ahead-of-time commands should be all you need, and this -is the recommended method of defining commands. +For most bots, ahead-of-time Commands should be all you need, and this +is the recommended method of defining Commands. ### Modules -The first step to creating commands is to create a _module_. +The first step to creating Commands is to create a _module_. -Modules are an organizational pattern that allow you to write your -commands in different classes, and have them automatically loaded. +A Module is an organizational pattern that allows you to write your +Commands in different classes and have them automatically loaded. Discord.Net's implementation of Modules is influenced heavily from -ASP.Net Core's Controller pattern. This means that the lifetime of a -module instance is only as long as the command being invoked. +ASP.NET Core's Controller pattern. This means that the lifetime of a +module instance is only as long as the Command is being invoked. **Avoid using long-running code** in your modules wherever possible. -You should **not** be implementing very much logic into your modules; -outsource to a service for that. +You should **not** be implementing very much logic into your modules, +instead, outsource to a service for that. If you are unfamiliar with Inversion of Control, it is recommended to read the MSDN article on [IoC] and [Dependency Injection]. -To begin, create a new class somewhere in your project, and -inherit the class from [ModuleBase]. This class **must** be `public`. +To begin, create a new class somewhere in your project and inherit the +class from [ModuleBase]. This class **must** be `public`. >[!NOTE] >[ModuleBase] is an _abstract_ class, meaning that you may extend it @@ -61,6 +57,7 @@ inherit the class from [ModuleBase]. This class **must** be `public`. >extension of ModuleBase. By now, your module should look like this: + [!code-csharp[Empty Module](samples/empty-module.cs)] [IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx @@ -69,72 +66,75 @@ By now, your module should look like this: ### Adding Commands -The next step to creating commands, is actually creating commands. +The next step to creating Commands is actually creating the Commands. -To create a command, add a method to your module of type `Task`. -Typically, you will want to mark this method as `async`, although it is -not required. +To create a Command, add a method to your module of type `Task`. +Typically, you will want to mark this method as `async`, although it +is not required. -Adding parameters to a command is done by adding parameters to the +Adding parameters to a Command is done by adding parameters to the parent Task. -For example, to take an integer as an argument, add `int arg`. To take -a user as an argument, add `IUser user`. In 1.0, a command can accept -nearly any type of argument; a full list of types that are parsed by -default can be found in the below section on _Type Readers_. +For example, to take an integer as an argument from the user, add `int +arg`; to take a user as an argument from the user, add `IUser user`. +In 1.0, a Command can accept nearly any type of argument; a full list +of types that are parsed by default can be found in the below section +on _Type Readers_. Parameters, by default, are always required. To make a parameter optional, give it a default value. To accept a comma-separated list, set the parameter to `params Type[]`. Should a parameter include spaces, it **must** be wrapped in quotes. -For example, for a command with a parameter `string food`, you would +For example, for a Command with a parameter `string food`, you would execute it with `!favoritefood "Key Lime Pie"`. -If you would like a parameter to parse until the end of a command, +If you would like a parameter to parse until the end of a Command, flag the parameter with the [RemainderAttribute]. This will allow a -user to invoke a command without wrapping a parameter in quotes. +user to invoke a Command without wrapping a parameter in quotes. -Finally, flag your command with the [CommandAttribute]. (You must -specify a name for this command, except for when it is part of a -module group - see below). +Finally, flag your Command with the [CommandAttribute] (you must +specify a name for this Command, except for when it is part of a +Module Group - see below). [RemainderAttribute]: xref:Discord.Commands.RemainderAttribute [CommandAttribute]: xref:Discord.Commands.CommandAttribute ### Command Overloads -You may add overloads of your commands, and the command parser will +You may add overloads to your Commands, and the Command parser will automatically pick up on it. -If, for whatever reason, you have too commands which are ambiguous to +If for whatever reason, you have two Commands which are ambiguous to each other, you may use the @Discord.Commands.PriorityAttribute to specify which should be tested before the other. -Priority's are sorted in ascending order; the higher priority will be -called first. +The `Priority` attributes are sorted in ascending order; the higher +priority will be called first. -### CommandContext +### Command Context -Every command can access the execution context through the [Context] -property on [ModuleBase]. CommandContext allows you to access the -message, channel, guild, and user that the command was invoked from, -as well as the underlying discord client the command was invoked from. +Every Command can access the execution context through the [Context] +property on [ModuleBase]. `ICommandContext` allows you to access the +message, channel, guild, and user that the Command was invoked from, +as well as the underlying Discord client that the Command was invoked +from. Different types of Contexts may be specified using the generic variant -of [ModuleBase]. When using a [SocketCommandContext], for example, -the properties on this context will already be Socket entities. You +of [ModuleBase]. When using a [SocketCommandContext], for example, the +properties on this context will already be Socket entities, so you will not need to cast them. To reply to messages, you may also invoke [ReplyAsync], instead of accessing the channel through the [Context] and sending a message. +> [!WARNING] +>Contexts should **NOT** be mixed! You cannot have one module that +>uses `CommandContext` and another that uses `SocketCommandContext`. + [Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context [SocketCommandContext]: xref:Discord.Commands.SocketCommandContext - ->![WARNING] ->Contexts should **NOT** be mixed! You cannot have one module that ->uses CommandContext, and another that uses SocketCommandContext. +[ReplyAsync]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_ReplyAsync_System_String_System_Boolean_Discord_Embed_Discord_RequestOptions_ ### Example Module @@ -144,50 +144,50 @@ At this point, your module should look comparable to this example: #### Loading Modules Automatically The Command Service can automatically discover all classes in an -Assembly that inherit [ModuleBase], and load them. +Assembly that inherit [ModuleBase] and load them. To opt a module out of auto-loading, flag it with -[DontAutoLoadAttribute] +[DontAutoLoadAttribute]. Invoke [CommandService.AddModulesAsync] to discover modules and install them. [DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute -[CommandService.AddModulesAsync]: xref:Discord_Commands_CommandService#Discord_Commands_CommandService_AddModulesAsync_Assembly_ +[CommandService.AddModulesAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModulesAsync_Assembly_ #### Loading Modules Manually -To manually load a module, invoke [CommandService.AddModuleAsync], -by passing in the generic type of your module, and optionally -a dependency map. +To manually load a module, invoke [CommandService.AddModuleAsync] by +passing in the generic type of your module and optionally, a +dependency map. [CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1 ### Module Constructors Modules are constructed using Dependency Injection. Any parameters -that are placed in the constructor must be injected into an -@System.IServiceProvider. Alternatively, you may accept an -IServiceProvider as an argument and extract services yourself. +that are placed in the Module's constructor must be injected into an +@System.IServiceProvider first. Alternatively, you may accept an +`IServiceProvider` as an argument and extract services yourself. ### Module Properties -Modules with public settable properties will have them injected after module -construction. +Modules with `public` settable properties will have the dependencies +injected after the construction of the Module. ### Module Groups -Module Groups allow you to create a module where commands are prefixed. -To create a group, flag a module with the -@Discord.Commands.GroupAttribute +Module Groups allow you to create a module where Commands are +prefixed. To create a group, flag a module with the +@Discord.Commands.GroupAttribute. -Module groups also allow you to create **nameless commands**, where the -[CommandAttribute] is configured with no name. In this case, the -command will inherit the name of the group it belongs to. +Module groups also allow you to create **nameless Commands**, where +the [CommandAttribute] is configured with no name. In this case, the +Command will inherit the name of the group it belongs to. ### Submodules -Submodules are modules that reside within another module. Typically, +Submodules are Modules that reside within another one. Typically, submodules are used to create nested groups (although not required to create nested groups). @@ -199,54 +199,62 @@ create nested groups). ## Dependency Injection -The commands service is bundled with a very barebones Dependency -Injection service for your convienence. It is recommended that -you use DI when writing your modules. +The Command Service is bundled with a very barebone Dependency +Injection service for your convenience. It is recommended that you use +DI when writing your modules. ### Setup -First, you need to create an @System.IServiceProvider -You may create your own IServiceProvider if you wish. +First, you need to create an @System.IServiceProvider; you may create +your own one if you wish. -Next, add the dependencies your modules will use to the map. +Next, add the dependencies that 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. +Finally, pass the map into the `LoadAssembly` method. Your modules +will be automatically loaded with this dependency map. [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] ### Usage in Modules -In the constructor of your module, any parameters will be filled in by -the @System.IServiceProvider you pass into `LoadAssembly`. +In the constructor of your Module, any parameters will be filled in by +the @System.IServiceProvider that you've passed into `LoadAssembly`. -Any publicly settable properties will also be filled in the same manner. +Any publicly settable properties will also be filled in the same +manner. >[!NOTE] -> Annotating a property with the [DontInject] attribute will prevent it from -being injected. +> Annotating a property with a [DontInjectAttribute] attribute will prevent the +property from being injected. >[!NOTE] ->If you accept `CommandService` or `IServiceProvider` as a parameter in -your constructor or as an injectable property, these entries will be filled -by the CommandService the module was loaded from, and the ServiceProvider passed -into it, respectively. +>If you accept `CommandService` or `IServiceProvider` as a parameter +in your constructor or as an injectable property, these entries will +be filled by the `CommandService` that the Module is loaded from and +the `ServiceProvider` that is passed into it respectively. [!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] +[DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute + # Preconditions -Preconditions serve as a permissions system for your commands. Keep in -mind, however, that they are not limited to _just_ permissions, and -can be as complex as you want them to be. +Precondition serve as a permissions system for your Commands. Keep in +mind, however, that they are not limited to _just_ permissions and can +be as complex as you want them to be. >[!NOTE] ->Preconditions can be applied to Modules, Groups, or Commands. +>There are two types of Preconditions. +[PreconditionAttribute] can be applied to Modules, Groups, or Commands; +[ParameterPreconditionAttribute] can be applied to Parameters. + +[PreconditionAttribute]: xref:Discord.Commands.PreconditionAttribute +[ParameterPreconditionAttribute]: xref:Discord.Commands.ParameterPreconditionAttribute ## Bundled Preconditions -Commands ships with four bundled preconditions; you may view their -usages on their API page. +Commands ship with four bundled Preconditions; you may view their +usages on their respective API pages. - @Discord.Commands.RequireContextAttribute - @Discord.Commands.RequireOwnerAttribute @@ -255,21 +263,23 @@ usages on their API page. ## Custom Preconditions -To write your own preconditions, create a new class that inherits from - @Discord.Commands.PreconditionAttribute +To write your own Precondition, create a new class that inherits from +either [PreconditionAttribute] or [ParameterPreconditionAttribute] +depending on your use. -In order for your precondition to function, you will need to override -[CheckPermissions]. +In order for your Precondition to function, you will need to override +the [CheckPermissions] method. Your IDE should provide an option to fill this in for you. -Return [PreconditionResult.FromSuccess] if the context met the -required parameters, otherwise return [PreconditionResult.FromError], -optionally including an error message. +If the context meets the required parameters, return +[PreconditionResult.FromSuccess], otherwise return +[PreconditionResult.FromError] and include an error message if +necessary. [!code-csharp[Custom Precondition](samples/require_owner.cs)] -[CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_CommandContext_Discord_Commands_CommandInfo_Discord_Commands_IDependencyMap_ +[CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_ICommandContext_Discord_Commands_CommandInfo_IServiceProvider_ [PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromSuccess [PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromError_System_String_ @@ -296,22 +306,28 @@ By default, the following Types are supported arguments: ### Creating a Type Readers -To create a TypeReader, create a new class that imports @Discord and -@Discord.Commands. Ensure your class inherits from @Discord.Commands.TypeReader +To create a `TypeReader`, create a new class that imports @Discord and +@Discord.Commands and ensure the class inherits from +@Discord.Commands.TypeReader. -Next, satisfy the `TypeReader` class by overriding [Read]. +Next, satisfy the `TypeReader` class by overriding the [Read] method. >[!NOTE] >In many cases, Visual Studio can fill this in for you, using the >"Implement Abstract Class" IntelliSense hint. -Inside this task, add whatever logic you need to parse the input string. +Inside this task, add whatever logic you need to parse the input +string. -Finally, return a `TypeReaderResult`. If you were able to successfully -parse the input, return `TypeReaderResult.FromSuccess(parsedInput)`. -Otherwise, return `TypeReaderResult.FromError`. +If you are able to successfully parse the input, return +[TypeReaderResult.FromSuccess] with the parsed input, otherwise return +[TypeReaderResult.FromError] and include an error message if +necessary. -[Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_CommandContext_System_String_ +[TypeReaderResult]: xref:Discord.Commands.TypeReaderResult +[TypeReaderResult.FromSuccess]: xref:Discord.Commands.TypeReaderResult#Discord_Commands_TypeReaderResult_FromSuccess_Discord_Commands_TypeReaderValue_ +[TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult#Discord_Commands_TypeReaderResult_FromError_Discord_Commands_CommandError_System_String_ +[Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_ICommandContext_System_String_IServiceProvider_ #### Sample @@ -319,5 +335,9 @@ Otherwise, return `TypeReaderResult.FromError`. ### Installing TypeReaders -TypeReaders are not automatically discovered by the Command Service, -and must be explicitly added. To install a TypeReader, invoke [CommandService.AddTypeReader](xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_). +TypeReaders are not automatically discovered by the Command Service +and must be explicitly added. + +To install a TypeReader, invoke [CommandService.AddTypeReader]. + +[CommandService.AddTypeReader]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_ \ No newline at end of file diff --git a/docs/guides/commands/samples/command_handler.cs b/docs/guides/commands/samples/command_handler.cs index 6b5d4ad2b..da2453aa8 100644 --- a/docs/guides/commands/samples/command_handler.cs +++ b/docs/guides/commands/samples/command_handler.cs @@ -8,39 +8,42 @@ using Microsoft.Extensions.DependencyInjection; public class Program { - private CommandService commands; - private DiscordSocketClient client; - private IServiceProvider services; + private CommandService _commands; + private DiscordSocketClient _client; + private IServiceProvider _services; - static void Main(string[] args) => new Program().Start().GetAwaiter().GetResult(); + private static void Main(string[] args) => new Program().StartAsync().GetAwaiter().GetResult(); - public async Task Start() + public async Task StartAsync() { - client = new DiscordSocketClient(); - commands = new CommandService(); + _client = new DiscordSocketClient(); + _commands = new CommandService(); + // Avoid hard coding your token. Use an external source instead in your code. string token = "bot token here"; - services = new ServiceCollection() - .BuildServiceProvider(); + _services = new ServiceCollection() + .AddSingleton(_client) + .AddSingleton(_commands) + .BuildServiceProvider(); - await InstallCommands(); + await InstallCommandsAsync(); - await client.LoginAsync(TokenType.Bot, token); - await client.StartAsync(); + await _client.LoginAsync(TokenType.Bot, token); + await _client.StartAsync(); await Task.Delay(-1); } - public async Task InstallCommands() + public async Task InstallCommandsAsync() { // Hook the MessageReceived Event into our Command Handler - client.MessageReceived += HandleCommand; + _client.MessageReceived += HandleCommandAsync; // Discover all of the commands in this assembly and load them. - await commands.AddModulesAsync(Assembly.GetEntryAssembly()); + await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); } - public async Task HandleCommand(SocketMessage messageParam) + private async Task HandleCommandAsync(SocketMessage messageParam) { // Don't process the command if it was a System Message var message = messageParam as SocketUserMessage; @@ -48,13 +51,13 @@ public class Program // 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; + if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return; // Create a Command Context - var context = new CommandContext(client, message); + var context = new SocketCommandContext(_client, message); // Execute the command. (result does not indicate a return value, // rather an object stating if the command executed successfully) - var result = await commands.ExecuteAsync(context, argPos, service); + var result = await _commands.ExecuteAsync(context, argPos, _services); if (!result.IsSuccess) await context.Channel.SendMessageAsync(result.ErrorReason); } -} +} \ No newline at end of file diff --git a/docs/guides/commands/samples/dependency_map_setup.cs b/docs/guides/commands/samples/dependency_map_setup.cs index e205d891d..a36925904 100644 --- a/docs/guides/commands/samples/dependency_map_setup.cs +++ b/docs/guides/commands/samples/dependency_map_setup.cs @@ -1,18 +1,18 @@ -using Discord; -using Discord.Commands; -using Discord.WebSocket; -using foxboat.Services; +private IServiceProvider _services; +private CommandService _commands; -public class Commands +public async Task InstallAsync(DiscordSocketClient client) { - public async Task Install(DiscordSocketClient client) - { - // Here, we will inject the ServiceProvider with - // all of the services our client will use. - _serviceCollection.AddSingleton(client) - _serviceCollection.AddSingleton(new NotificationService()) - _serviceCollection.AddSingleton(new DatabaseService()) - // ... - await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); - } -} + // Here, we will inject the ServiceProvider with + // all of the services our client will use. + _services = new ServiceCollection() + .AddSingleton(client) + .AddSingleton(_commands) + // You can pass in an instance of the desired type + .AddSingleton(new NotificationService()) + // ...or by using the generic method. + .AddSingleton() + .BuildServiceProvider(); + // ... + await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); +} \ No newline at end of file diff --git a/docs/guides/commands/samples/empty-module.cs b/docs/guides/commands/samples/empty-module.cs index cac9922b5..6483c7cd2 100644 --- a/docs/guides/commands/samples/empty-module.cs +++ b/docs/guides/commands/samples/empty-module.cs @@ -1,6 +1,6 @@ using Discord.Commands; -public class InfoModule : ModuleBase +public class InfoModule : ModuleBase { } \ No newline at end of file diff --git a/docs/guides/commands/samples/groups.cs b/docs/guides/commands/samples/groups.cs index db6456c87..5f96c34e8 100644 --- a/docs/guides/commands/samples/groups.cs +++ b/docs/guides/commands/samples/groups.cs @@ -1,8 +1,8 @@ [Group("admin")] -public class AdminModule : ModuleBase +public class AdminModule : ModuleBase { [Group("clean")] - public class CleanModule : ModuleBase + public class CleanModule : ModuleBase { // ~admin clean 15 [Command] diff --git a/docs/guides/commands/samples/module.cs b/docs/guides/commands/samples/module.cs index 403acba06..5014619da 100644 --- a/docs/guides/commands/samples/module.cs +++ b/docs/guides/commands/samples/module.cs @@ -1,42 +1,41 @@ -using Discord; -using Discord.Commands; -using Discord.WebSocket; - // Create a module with no prefix -public class Info : ModuleBase +public class Info : ModuleBase { - // ~say hello -> hello - [Command("say"), Summary("Echos a message.")] - public async Task Say([Remainder, Summary("The text to echo")] string echo) - { - // ReplyAsync is a method on ModuleBase - await ReplyAsync(echo); - } + // ~say hello -> hello + [Command("say")] + [Summary("Echos a message.")] + public async Task SayAsync([Remainder] [Summary("The text to echo")] string echo) + { + // ReplyAsync is a method on ModuleBase + await ReplyAsync(echo); + } } // Create a module with the 'sample' prefix [Group("sample")] -public class Sample : ModuleBase +public class Sample : ModuleBase { - // ~sample square 20 -> 400 - [Command("square"), Summary("Squares a number.")] - public async Task Square([Summary("The number to square.")] int num) - { - // We can also access the channel from the Command Context. - await Context.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}"); - } + // ~sample square 20 -> 400 + [Command("square")] + [Summary("Squares a number.")] + public async Task SquareAsync([Summary("The number to square.")] int num) + { + // We can also access the channel from the Command Context. + await Context.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}"); + } - // ~sample userinfo --> foxbot#0282 + // ~sample userinfo --> foxbot#0282 // ~sample userinfo @Khionu --> Khionu#8708 // ~sample userinfo Khionu#8708 --> Khionu#8708 // ~sample userinfo Khionu --> Khionu#8708 // ~sample userinfo 96642168176807936 --> Khionu#8708 - // ~sample whois 96642168176807936 --> Khionu#8708 - [Command("userinfo"), Summary("Returns info about the current user, or the user parameter, if one passed.")] - [Alias("user", "whois")] - public async Task UserInfo([Summary("The (optional) user to get info for")] IUser user = null) - { - var userInfo = user ?? Context.Client.CurrentUser; - await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}"); - } -} + // ~sample whois 96642168176807936 --> Khionu#8708 + [Command("userinfo")] + [Summary("Returns info about the current user, or the user parameter, if one passed.")] + [Alias("user", "whois")] + public async Task UserInfoAsync([Summary("The (optional) user to get info for")] SocketUser user = null) + { + var userInfo = user ?? Context.Client.CurrentUser; + await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}"); + } +} \ No newline at end of file diff --git a/docs/guides/concepts/entities.md b/docs/guides/concepts/entities.md index a38651829..3a5d5496b 100644 --- a/docs/guides/concepts/entities.md +++ b/docs/guides/concepts/entities.md @@ -12,7 +12,7 @@ Discord API. ### Inheritance Due to the nature of the Discord API, some entities are designed with -multiple variants, for example, `SocketUser` and `SocketGuildUser`. +multiple variants; for example, `SocketUser` and `SocketGuildUser`. All models will contain the most detailed version of an entity possible, even if the type is less detailed. @@ -61,8 +61,11 @@ a variant of the type that you need. ### Tips Avoid using boxing-casts to coerce entities into a variant, use the -`as` keyword, and a null-conditional operator. +[`as`] keyword, and a null-conditional operator instead. -This allows you to write safer code, and avoid InvalidCastExceptions. +This allows you to write safer code and avoid [InvalidCastExceptions]. -For example, `(message.Author as SocketGuildUser)?.Nickname`. \ No newline at end of file +For example, `(message.Author as SocketGuildUser)?.Nickname`. + +[`as`]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as +[InvalidCastExceptions]: https://msdn.microsoft.com/en-us/library/system.invalidcastexception(v=vs.110).aspx \ No newline at end of file diff --git a/docs/guides/concepts/events.md b/docs/guides/concepts/events.md index f2dfb00f0..47db49aa8 100644 --- a/docs/guides/concepts/events.md +++ b/docs/guides/concepts/events.md @@ -4,27 +4,27 @@ title: Working with Events Events in Discord.Net are consumed in a similar manner to the standard convention, with the exception that every event must be of the type -`System.Threading.Tasks.Task`, and instead of using EventArgs, the +`System.Threading.Tasks.Task` and instead of using `EventArgs`, the event's parameters are passed directly into the handler. -This allows for events to be handled in an async context directly, -instead of relying on async void. +This allows for events to be handled in an async context directly +instead of relying on `async void`. ### Usage To receive data from an event, hook into it using C#'s delegate event pattern. -You may opt either to hook an event to an anonymous function (lambda) +You may either opt to hook an event to an anonymous function (lambda) or a named function. ### Safety -All events are designed to be thread-safe, in that events are executed -synchronously off the gateway task, in the same context as the gateway +All events are designed to be thread-safe; events are executed +synchronously off the gateway task in the same context as the gateway task. -As a side effect, this makes it possible to deadlock the gateway task, +As a side effect, this makes it possible to deadlock the gateway task and kill a connection. As a general rule of thumb, any task that takes longer than three seconds should **not** be awaited directly in the context of an event, but should be wrapped in a `Task.Run` or @@ -62,7 +62,7 @@ This pattern is typically only found on `EntityUpdated` events. An event handler with a signature of `Func` means that the `before` state of the entity was not provided by the -API, so it can either be pulled from the client's cache, or +API, so it can either be pulled from the client's cache or downloaded from the API. See the documentation for [Cacheable] for more information on this @@ -76,8 +76,8 @@ object. ### Tips -Many events relating to a Message entity, e.g. `MessageUpdated` -and `ReactionAdded` rely on the client's message cache, which is +Many events relating to a Message entity (i.e. `MessageUpdated` and +`ReactionAdded`) rely on the client's message cache, which is **not** enabled by default. Set the `MessageCacheSize` flag in [DiscordSocketConfig] to enable it. diff --git a/docs/guides/concepts/logging.md b/docs/guides/concepts/logging.md index 1592dfc72..50d2e9546 100644 --- a/docs/guides/concepts/logging.md +++ b/docs/guides/concepts/logging.md @@ -14,12 +14,14 @@ section. ### Usage To receive log events, simply hook the discord client's log method -to a Task with a single parameter of type [LogMessage] +to a `Task` with a single parameter of type [LogMessage]. It is recommended that you use an established function instead of a -lambda for handling logs, because most [addons] accept a reference +lambda for handling logs, because most addons accept a reference to a logging function to write their own messages. +[LogMessage]: xref:Discord.LogMessage + ### Usage in Commands Discord.Net's [CommandService] also provides a log event, identical @@ -29,6 +31,9 @@ Data logged through this event is typically coupled with a [CommandException], where information about the command's context and error can be found and handled. +[CommandService]: xref:Discord.Commands.CommandService +[CommandException]: xref:Discord.Commands.CommandException + #### Samples [!code-csharp[Logging Sample](samples/logging.cs)] diff --git a/docs/guides/getting_started/installing.md b/docs/guides/getting_started/installing.md index 82d242647..5d4c85d81 100644 --- a/docs/guides/getting_started/installing.md +++ b/docs/guides/getting_started/installing.md @@ -2,84 +2,87 @@ title: Installing Discord.Net --- -Discord.Net is distributed through the NuGet package manager, and it is -recommended to use NuGet to get started. +Discord.Net is distributed through the NuGet package manager, and it +is recommended to use NuGet to get started. Optionally, you may compile from source and install yourself. # Supported Platforms -Currently, Discord.Net targets [.NET Standard] 1.3, and offers support for -.NET Standard 1.1. If your application will be targeting .NET Standard 1.1, -please see the [additional steps](#installing-on-net-standard-11). +Currently, Discord.Net targets [.NET Standard] 1.3 and offers support +for .NET Standard 1.1. If your application will be targeting .NET +Standard 1.1, please see the [additional steps]. -Since Discord.Net is built on the .NET Standard, it is also recommended to -create applications using [.NET Core], though you are not required to. When -using .NET Framework, it is suggested to target `.NET 4.6.1` or higher. +Since Discord.Net is built on the .NET Standard, it is also +recommended to create applications using [.NET Core], though not +required. When using .NET Framework, it is suggested to target +`.NET Framework 4.6.1` or higher. [.NET Standard]: https://docs.microsoft.com/en-us/dotnet/articles/standard/library [.NET Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/ +[additional steps]: #installing-on-net-standard-11 # Installing with NuGet Release builds of Discord.Net 1.0 will be published to the [official NuGet feed]. -Development builds of Discord.Net 1.0, as well as [addons](TODO) are published -to our development [MyGet feed]. +Development builds of Discord.Net 1.0, as well as addons *(TODO)* are +published to our development [MyGet feed]. Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` -Not sure how to add a direct feed? See how [with Visual Studio] -or [without Visual Studio](#configuring-nuget-without-visual-studio) +Not sure how to add a direct feed? See how [with Visual Studio] or +[without Visual Studio]. [official NuGet feed]: https://nuget.org [MyGet feed]: https://www.myget.org/feed/Packages/discord-net [with Visual Studio]: https://docs.microsoft.com/en-us/nuget/tools/package-manager-ui#package-sources - +[without Visual Studio]: #configuring-nuget-without-visual-studio ## Using Visual Studio -1. Create a solution for your bot -2. In Solution Explorer, find the 'Dependencies' element under your bot's -project -3. Right click on 'Dependencies', and select 'Manage NuGet packages' -![Step 3](images/install-vs-deps.png) -4. In the 'browse' tab, search for 'Discord.Net' - > [!TIP] -Don't forget to change your package source if you're installing from the -developer feed. -Also make sure to check 'Enable Prereleases' if installing a dev build! - -5. Install the 'Discord.Net' package - +>Don't forget to change your package source if you're installing from +the developer feed. +>Also make sure to check "Enable Prereleases" if installing a dev +build! + +1. Create a solution for your bot. +2. In Solution Explorer, find the "Dependencies" element under your +bot's project. +3. Right click on "Dependencies", and select "Manage NuGet packages." +![Step 3](images/install-vs-deps.png) +4. In the "Browse" tab, search for `Discord.Net`. +5. Install the `Discord.Net` package. ![Step 5](images/install-vs-nuget.png) ## Using JetBrains Rider -1. Create a new solution for your bot -2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for Solution) -![Step 2](images/install-rider-nuget-manager.png) -3. In the 'Packages' tab, search for 'Discord.Net' -![Step 3](images/install-rider-search.png) - > [!TIP] -Make sure to check the 'Prerelease' box if installing a dev build! +Make sure to check the "Prerelease" box if installing a dev build! -4. Install by adding the package to your project +1. Create a new solution for your bot. +2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for +Solution). +![Step 2](images/install-rider-nuget-manager.png) +3. In the "Packages" tab, search for `Discord.Net`. +![Step 3](images/install-rider-search.png) +4. Install by adding the package to your project. ![Step 4](images/install-rider-add.png) ## Using Visual Studio Code -1. Create a new project for your bot -2. Add Discord.Net to your .csproj +> [!TIP] +Don't forget to add the package source to a [NuGet.Config file] if +you're installing from the developer feed. + +1. Create a new project for your bot. +2. Add `Discord.Net` to your .csproj. [!code-xml[Sample .csproj](samples/project.csproj)] -> [!TIP] -Don't forget to add the package source to a [NuGet.Config file](#configuring-nuget-without-visual-studio) if you're installing from the -developer feed. +[NuGet.Config file]: #configuring-nuget-without-visual-studio # Compiling from Source @@ -90,8 +93,8 @@ In order to compile Discord.Net, you require the following: - [Visual Studio 2017](https://www.visualstudio.com/) - [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk) -The .NET Core and Docker (Preview) workload is required during Visual Studio -installation. +The .NET Core and Docker (Preview) workload is required during Visual +Studio installation. ### Using Command Line @@ -101,26 +104,27 @@ installation. ## Installing on .NET Standard 1.1 -For applications targeting a runtime corresponding with .NET Standard 1.1 or 1.2, -the builtin WebSocket and UDP provider will not work. For applications which -utilize a WebSocket connection to Discord (WebSocket or RPC), third-party -provider packages will need to be installed and configured. +For applications targeting a runtime corresponding with .NET Standard +1.1 or 1.2, the builtin WebSocket and UDP provider will not work. For +applications which utilize a WebSocket connection to Discord +(WebSocket or RPC), third-party provider packages will need to be +installed and configured. -First, install the following packages through NuGet, or compile yourself, if -you prefer: +First, install the following packages through NuGet, or compile +yourself, if you prefer: - Discord.Net.Providers.WS4Net - Discord.Net.Providers.UDPClient -Note that `Discord.Net.Providers.UDPClient` is _only_ required if your bot will -be utilizing voice chat. +Note that `Discord.Net.Providers.UDPClient` is _only_ required if your +bot will be utilizing voice chat. -Next, you will need to configure your [DiscordSocketClient] to use these custom -providers over the default ones. +Next, you will need to configure your [DiscordSocketClient] to use +these custom providers over the default ones. -To do this, set the `WebSocketProvider` and optionally `UdpSocketProvider` -properties on the [DiscordSocketConfig] that you are passing into your -client. +To do this, set the `WebSocketProvider` and the optional +`UdpSocketProvider` properties on the [DiscordSocketConfig] that you +are passing into your client. [!code-csharp[NET Standard 1.1 Example](samples/netstd11.cs)] @@ -129,13 +133,14 @@ client. ## Configuring NuGet without Visual Studio -If you plan on deploying your bot or developing outside of Visual Studio, you -will need to create a local NuGet configuration file for your project. +If you plan on deploying your bot or developing outside of Visual +Studio, you will need to create a local NuGet configuration file for +your project. -To do this, create a file named `nuget.config` alongside the root of your -application, where the project solution is located. +To do this, create a file named `nuget.config` alongside the root of +your application, where the project solution is located. -Paste the following snippets into this configuration file, adding any additional -feeds as necessary. +Paste the following snippets into this configuration file, adding any +additional feeds as necessary. [!code-xml[NuGet Configuration](samples/nuget.config)] diff --git a/docs/guides/getting_started/intro.md b/docs/guides/getting_started/intro.md index 837814511..02f04bec4 100644 --- a/docs/guides/getting_started/intro.md +++ b/docs/guides/getting_started/intro.md @@ -13,42 +13,46 @@ diverse commands later, but for now, it is a good starting point. Before you can begin writing your bot, it is necessary to create a bot account on Discord. -1. Visit the [Discord Applications Portal] -2. Create a New Application +1. Visit the [Discord Applications Portal]. +2. Create a New Application. 3. Give the application a name (this will be the bot's initial username). -4. Create the Application -![Step 4](images/intro-create-app.png) -5. In the application review page, click **Create a Bot User** -![Step 5](images/intro-create-bot.png) -6. Confirm the popup -7. If this bot will be public, check 'Public Bot'. -**Do not tick any other options!** +4. Create the Application. + + ![Step 4](images/intro-create-app.png) + +5. In the application review page, click **Create a Bot User**. + + ![Step 5](images/intro-create-bot.png) + +6. Confirm the popup. +7. If this bot will be public, check "Public Bot." **Do not tick any +other options!** [Discord Applications Portal]: https://discordapp.com/developers/applications/me ## Adding your bot to a server -Bots **can not** use invite links, they must be explicitly invited +Bots **cannot** use invite links, they must be explicitly invited through the OAuth2 flow. -1. Open your bot's application on the [Discord Applications Portal] +1. Open your bot's application on the [Discord Applications Portal]. 2. Retrieve the app's **Client ID**. - -![Step 2](images/intro-client-id.png) - + + ![Step 2](images/intro-client-id.png) + 3. Create an OAuth2 authorization URL `https://discordapp.com/oauth2/authorize?client_id=&scope=bot` -4. Open the authorization URL in your browser -5. Select a server - ->[!NOTE] -Only servers where you have the `MANAGE_SERVER` permission will be -present in this list. +4. Open the authorization URL in your browser. +5. Select a server. +6. Click on authorize. + + >[!NOTE] + Only servers where you have the `MANAGE_SERVER` permission will be + present in this list. + + ![Step 6](images/intro-add-bot.png) -6. Click authorize - -![Step 6](images/intro-add-bot.png) ## Connecting to Discord @@ -57,10 +61,10 @@ do that now. (see the [Installing](installing.md) section) ### Async -Discord.Net uses .NET's Task-based Asynchronous Pattern ([TAP]) +Discord.Net uses .NET's [Task-based Asynchronous Pattern (TAP)] extensively - nearly every operation is asynchronous. -It is highly recommended that these operations be awaited in a +It is highly recommended that these operations are awaited in a properly established async context whenever possible. Establishing an async context can be problematic, but not hard. @@ -70,27 +74,29 @@ async main. [!code-csharp[Async Context](samples/intro/async-context.cs)] -As a result of this, your program will now start, and immidiately -jump into an async context. This will allow us later on to create a -connection to Discord, without needing to worry about setting up the +As a result of this, your program will now start and immidiately +jump into an async context. This will allow us to create a connection +to Discord later on without needing to worry about setting up the correct async implementation. >[!TIP] If your application throws any exceptions within an async context, -they will be thrown all the way back up to the first non-async method. -Since our first non-async method is the program's Main method, this +they will be thrown all the way back up to the first non-async method; +since our first non-async method is the program's `Main` method, this means that **all** unhandled exceptions will be thrown up there, which will crash your application. Discord.Net will prevent exceptions in event handlers from crashing your program, but any exceptions in your async main **will** cause the application to crash. +[Task-based Asynchronous Pattern (TAP)]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async + ### Creating a logging method Before we create and configure a Discord client, we will add a method to handle Discord.Net's log events. To allow agnostic support of as many log providers as possible, we -log information through a Log event, with a proprietary LogMessage +log information through a `Log` event with a proprietary `LogMessage` parameter. See the [API Documentation] for this event. If you are using your own logging framework, this is where you would @@ -99,10 +105,12 @@ the Console. [!code-csharp[Async Context](samples/intro/logging.cs)] +[API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log + ### Creating a Discord Client Finally, we can create a connection to Discord. Since we are writing -a bot, we will be using a [DiscordSocketClient], along with socket +a bot, we will be using a [DiscordSocketClient] along with socket entities. See the [terminology](terminology.md) if you're unsure of the differences. @@ -110,22 +118,24 @@ To do so, create an instance of [DiscordSocketClient] in your async main, passing in a configuration object only if necessary. For most users, the default will work fine. -Before connecting, we should hook the client's log event to the +Before connecting, we should hook the client's `Log` event to the log handler that was just created. Events in Discord.Net work similarly to other events in C#, so hook this event the way that you typically would. -Next, you will need to 'login to Discord' with the `LoginAsync` method. +Next, you will need to "login to Discord" with the `LoginAsync` +method. You may create a variable to hold your bot's token (this can be found on your bot's application page on the [Discord Applications Portal]). + ![Token](images/intro-token.png) >[!IMPORTANT] Your bot's token can be used to gain total access to your bot, so -**do __NOT__ share this token with anyone!** It may behoove you to -store this token in an external file if you plan on distributing the -source code for your bot. +**do __NOT__ share this token with anyone else!** It may behoove you +to store this token in an external file if you plan on distributing +the source code for your bot. We may now invoke the client's `StartAsync` method, which will start connection/reconnection logic. It is important to note that @@ -134,14 +144,9 @@ start connection/reconnection logic. It is important to note that Any methods that rely on the client's state should go in an event handler. ->[!NOTE] -Connection logic is incomplete as of the current build. Events will -soon be added to indicate when the client's state is ready for use; -(rewrite this section when possible) - Finally, we will want to block the async main method from returning until after the application is exited. To do this, we can await an -infinite delay, or any other blocking method, such as reading from +infinite delay or any other blocking method, such as reading from the console. The following lines can now be added: @@ -154,51 +159,55 @@ online in Discord. >[!TIP] Encountering a `PlatformNotSupportedException` when starting your bot? This means that you are targeting a platform where .NET's default -WebSocket client is not supported. Refer to the [installing guide] +WebSocket client is not supported. Refer to the [installation guide] for how to fix this. -[TAP]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async -[API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient -[installing guide]: installing.md#installing-on-net-standard-11 +[installation guide]: installing.md#installing-on-net-standard-11 ### Handling a 'ping' +>[!WARNING] +Please note that this is *not* a proper way to create a command. +Use the `CommandService` provided by the library instead, as explained +in the [Command Guide] section. + Now that we have learned how to open a connection to Discord, we can begin handling messages that users are sending. To start out, our bot will listen for any message where the content -is equal to `!ping`, and respond back with `Pong!`. +is equal to `!ping` and respond back with "Pong!". -Since we want to listen for new messages, the event to hook in to +Since we want to listen for new messages, the event to hook into is [MessageReceived]. In your program, add a method that matches the signature of the -MessageReceived event - it must be a method (`Func`) that returns the -type `Task`, and takes a single parameter, a [SocketMessage]. Also, +`MessageReceived` event - it must be a method (`Func`) that returns +the type `Task` and takes a single parameter, a [SocketMessage]. Also, since we will be sending data to Discord in this method, we will flag it as `async`. -In this method, we will add an `if` block, to determine if the message +In this method, we will add an `if` block to determine if the message content fits the rules of our scenario - recall that it must be equal to `!ping`. Inside the branch of this condition, we will want to send a message -back to the channel from which the message came - `Pong!`. To find the -channel, look for the `Channel` property on the message parameter. +back to the channel from which the message comes from - "Pong!". To +find the channel, look for the `Channel` property on the message +parameter. Next, we will want to send a message to this channel. Since the channel object is of type [SocketMessageChannel], we can invoke the `SendMessageAsync` instance method. For the message content, send back -a string containing 'Pong!'. +a string containing "Pong!". You should have now added the following lines: [!code-csharp[Message](samples/intro/message.cs)] -Now, your first bot is complete. You may continue to add on to this -if you desire, but for any bot that will be carrying out multiple -commands, it is strongly encouraged to use the command framework, as +Now your first bot is complete. You may continue to add on to this +if you desire, but for any bots that will be carrying out multiple +commands, it is strongly recommended to use the command framework as shown below. For your reference, you may view the [completed program]. @@ -207,13 +216,14 @@ For your reference, you may view the [completed program]. [SocketMessage]: xref:Discord.WebSocket.SocketMessage [SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel [completed program]: samples/intro/complete.cs +[Command Guide]: ../commands/commands.md # Building a bot with commands This section will show you how to write a program that is ready for -[commands](commands/commands.md). Note that this will not be explaining _how_ -to write commands or services, it will only be covering the general -structure. +[Commands](../commands/commands.md). Note that we will not be +explaining _how_ to write Commands or Services, it will only be +covering the general structure. For reference, view an [annotated example] of this structure. @@ -224,4 +234,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 +**todo:** diagram of bot structure \ No newline at end of file diff --git a/docs/guides/getting_started/terminology.md b/docs/guides/getting_started/terminology.md index a51003e34..74f7a6259 100644 --- a/docs/guides/getting_started/terminology.md +++ b/docs/guides/getting_started/terminology.md @@ -7,32 +7,34 @@ title: Terminology ## Preface -Most terms for objects remain the same between 0.9 and 1.0. The major difference is that the ``Server`` is now called ``Guild``, to stay in line with Discord internally +Most terms for objects remain the same between 0.9 and 1.0. The major +difference is that the ``Server`` is now called ``Guild`` to stay in +line with Discord internally. ## Implementation Specific Entities -Discord.Net 1.0 is split into a core library, and three different -implementations - Discord.Net.Core, Discord.Net.Rest, Discord.Net.Rpc, -and Discord.Net.WebSockets. +Discord.Net 1.0 is split into a core library and three different +implementations - `Discord.Net.Core`, `Discord.Net.Rest`, +`Discord.Net.Rpc`, and `Discord.Net.WebSockets`. -As a bot developer, you will only need to use Discord.Net.WebSockets, +As a bot developer, you will only need to use `Discord.Net.WebSockets`, but you should be aware of the differences between them. -`Discord.Net.Core` provides a set of interfaces that model Discord's +`Discord.Net.Core` provides a set of interfaces that models Discord's API. These interfaces are consistent throughout all implementations of Discord.Net, and if you are writing an implementation-agnostic library or addon, you can rely on the core interfaces to ensure that your addon will run on all platforms. `Discord.Net.Rest` provides a set of concrete classes to be used -**strictly** with the REST portion of Discord's API. Entities in -this implementation are prefixed with `Rest`, e.g. `RestChannel`. +**strictly** with the REST portion of Discord's API. Entities in this +implementation are prefixed with `Rest` (e.g. `RestChannel`). -`Discord.Net.Rpc` provides a set of concrete classes that are used with -Discord's RPC API. Entities in this implementation are prefixed with -`Rpc`, e.g. `RpcChannel`. +`Discord.Net.Rpc` provides a set of concrete classes that are used +with Discord's RPC API. Entities in this implementation are prefixed +with `Rpc` (e.g. `RpcChannel`). -`Discord.Net.WebSocket` provides a set of concrete classes that are used -primarily with Discord's WebSocket API, or entities that are kept in -cache. When developing bots, you will be using this implementation. All -entities are prefixed with `Socket`, e.g. `SocketChannel`. \ No newline at end of file +`Discord.Net.WebSocket` provides a set of concrete classes that are +used primarily with Discord's WebSocket API or entities that are kept +in cache. When developing bots, you will be using this implementation. +All entities are prefixed with `Socket` (e.g. `SocketChannel`). \ No newline at end of file diff --git a/docs/guides/voice/sending-voice.md b/docs/guides/voice/sending-voice.md index c3ec8d9d7..024a98b95 100644 --- a/docs/guides/voice/sending-voice.md +++ b/docs/guides/voice/sending-voice.md @@ -17,7 +17,7 @@ when developing on .NET Core, this is where you execute `dotnet run` from; typically the same directory as your csproj). For Windows Users, precompiled binaries are available for your -convienence [here](https://discord.foxbot.me/binaries/) +convienence [here](https://discord.foxbot.me/binaries/). For Linux Users, you will need to compile [Sodium] and [Opus] from source, or install them from your package manager. @@ -31,7 +31,7 @@ Joining a channel is the first step to sending audio, and will return an [IAudioClient] to send data with. To join a channel, simply await [ConnectAsync] on any instance of an -@Discord.IVoiceChannel. +@Discord.IAudioChannel. [!code-csharp[Joining a Channel](samples/joining_audio.cs)] @@ -44,7 +44,7 @@ guild. To switch channels within a guild, invoke [ConnectAsync] on another voice channel in the guild. [IAudioClient]: xref:Discord.Audio.IAudioClient -[ConnectAsync]: xref:Discord.IVoiceChannel#Discord_IVoiceChannel_ConnectAsync +[ConnectAsync]: xref:Discord.IAudioChannel#Discord_IAudioChannel_ConnectAsync_Action_IAudioClient__ ## Transmitting Audio @@ -84,7 +84,7 @@ Channels should be left at `2`, unless you specified a different value for `-ac 2` when creating FFmpeg. [AudioOutStream]: xref:Discord.Audio.AudioOutStream -[IAudioClient.CreatePCMStream]: xref:Discord.Audio.IAudioClient#Discord_Audio_IAudioClient_CreatePCMStream_System_Int32_System_Int32_System_Nullable_System_Int32__System_Int32_ +[IAudioClient.CreatePCMStream]: xref:Discord.Audio.IAudioClient#Discord_Audio_IAudioClient_CreateDirectPCMStream_Discord_Audio_AudioApplication_System_Nullable_System_Int32__System_Int32_ Finally, audio will need to be piped from FFmpeg's stdout into your AudioOutStream. This step can be as complex as you'd like it to be, but From 71e8b88514288e63162300faa6a01d4e281ad775 Mon Sep 17 00:00:00 2001 From: Vollrat <29968697+Vollrat@users.noreply.github.com> Date: Tue, 3 Oct 2017 21:34:38 -0230 Subject: [PATCH 07/27] Improve upon Property Summaries in CommandServiceConfig (#839) * Improve Boolean Property Summaries Having the `CaseSensitiveCommands` property summary asking a question whenever Intellisense is invoked seems a bit nonessential instead of *properly* explaining what exactly it does. It would be better if instead, it stated it's use to be more comprehensible to the reader. ### Changes - Edits the summaries of `CaseSensitiveCommands` and `ThrowOnError` to follow a more methodical convention for boolean property summaries (`Determines whether X ...` rather than `Should X be ... ?`). This is just a small change to improve upon the current documentation, so it shouldn't conflict with anything. * "DefaultRunMode should also be "Gets or sets blah blah blah" to be consistent." --- src/Discord.Net.Commands/CommandServiceConfig.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index 5dcd50cd8..b53b0248c 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -2,17 +2,18 @@ { public class CommandServiceConfig { - /// The default RunMode commands should have, if one is not specified on the Command attribute or builder. + /// Gets or sets the default RunMode commands should have, if one is not specified on the Command attribute or builder. public RunMode DefaultRunMode { get; set; } = RunMode.Sync; public char SeparatorChar { get; set; } = ' '; - /// Should commands be case-sensitive? + + /// Determines whether commands should be case-sensitive. public bool CaseSensitiveCommands { get; set; } = false; /// Gets or sets the minimum log level severity that will be sent to the Log event. public LogSeverity LogLevel { get; set; } = LogSeverity.Info; - /// Gets or sets whether RunMode.Sync commands should push exceptions up to the caller. + /// Determines whether RunMode.Sync commands should push exceptions up to the caller. public bool ThrowOnError { get; set; } = true; } -} \ No newline at end of file +} From 88c9964b54068099685a64b2bb11e8df8254f9f0 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Wed, 4 Oct 2017 02:07:54 +0200 Subject: [PATCH 08/27] Minor Quickstart tweaks (#783) * Remove saving Console.ForegroundColor to a local * Add message handler checks * Add comment explaining that there's two differently named methods to add modules * Add comment about the Commands package * Add Exception property to log handler --- .../samples/intro/structure.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index 00ce7a6c9..789ceff76 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -8,12 +8,6 @@ using Discord.WebSocket; class Program { - private readonly DiscordSocketClient _client; - - // Keep the CommandService and IServiceCollection around for use with commands. - private readonly IServiceCollection _map = new ServiceCollection(); - private readonly CommandService _commands = new CommandService(); - // Program entry point static void Main(string[] args) { @@ -22,6 +16,13 @@ class Program new Program().MainAsync().GetAwaiter().GetResult(); } + private readonly DiscordSocketClient _client; + + // Keep the CommandService and IServiceCollection around for use with commands. + // These two types require you install the Discord.Net.Commands package. + private readonly IServiceCollection _map = new ServiceCollection(); + private readonly CommandService _commands = new CommandService(); + private Program() { _client = new DiscordSocketClient(new DiscordSocketConfig @@ -48,7 +49,6 @@ class Program // that ask for a Func. private static Task Logger(LogMessage message) { - var cc = Console.ForegroundColor; switch (message.Severity) { case LogSeverity.Critical: @@ -66,8 +66,8 @@ class Program Console.ForegroundColor = ConsoleColor.DarkGray; break; } - Console.WriteLine($"{DateTime.Now,-19} [{message.Severity,8}] {message.Source}: {message.Message}"); - Console.ForegroundColor = cc; + Console.WriteLine($"{DateTime.Now,-19} [{message.Severity,8}] {message.Source}: {message.Message} {message.Exception}"); + Console.ResetColor(); // If you get an error saying 'CompletedTask' doesn't exist, // your project is targeting .NET 4.5.2 or lower. You'll need @@ -105,10 +105,11 @@ class Program _services = _map.BuildServiceProvider(); // Either search the program and add all Module classes that can be found. - // Module classes *must* be marked 'public' or they will be ignored. + // 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(); + // Note that the first one is 'Modules' (plural) and the second is 'Module' (singular). // Subscribe a handler to see if a message invokes a command. _client.MessageReceived += HandleCommandAsync; @@ -120,6 +121,11 @@ class Program var msg = arg as SocketUserMessage; if (msg == null) return; + // We don't want the bot to respond to itself or other bots. + // NOTE: Selfbots should invert this first check and remove the second + // as they should ONLY be allowed to respond to messages from the same account. + if (msg.Author.Id == _client.CurrentUser.Id || msg.Author.IsBot) return; + // Create a number to track where the prefix ends and the command begins int pos = 0; // Replace the '!' with whatever character From d8c4b7537bb467b75e8856083040c89c8fefd183 Mon Sep 17 00:00:00 2001 From: Alan Schapira Date: Wed, 4 Oct 2017 01:09:06 +0100 Subject: [PATCH 09/27] Client is global variable (#789) In the previous section of the tutorial https://github.com/RogueException/Discord.Net/blob/dev/docs/guides/getting_started/samples/intro/client.cs, the client it a global variable with an underscore --- docs/guides/getting_started/samples/intro/message.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/message.cs b/docs/guides/getting_started/samples/intro/message.cs index d3cda46e5..d6fd90778 100644 --- a/docs/guides/getting_started/samples/intro/message.cs +++ b/docs/guides/getting_started/samples/intro/message.cs @@ -1,7 +1,7 @@ public async Task MainAsync() { // client.Log ... - client.MessageReceived += MessageReceived; + _client.MessageReceived += MessageReceived; // ... } @@ -11,4 +11,4 @@ private async Task MessageReceived(SocketMessage message) { await message.Channel.SendMessageAsync("Pong!"); } -} \ No newline at end of file +} From 14fbe40cbc7a7ce6f16a1ea8d6592dff8418cb3a Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 9 Oct 2017 19:45:12 -0400 Subject: [PATCH 10/27] Add Async suffix to command-related Tasks (#804) commit b7fb44a94fb1e75f696f281d6b201eba3f48e864 Author: Hsu Still <341464@gmail.com> Date: Sat Sep 2 14:34:18 2017 +0800 Fix more async naming violation commit e6912e2d020c69325826dbfa62c07cd1ef2cc45f Author: Hsu Still <341464@gmail.com> Date: Sat Sep 2 14:23:04 2017 +0800 Fix incorrect null xmldocs string commit da8d23222d207853375c3512232d1d7fd3629cad Author: Hsu Still <341464@gmail.com> Date: Sat Sep 2 14:17:12 2017 +0800 Fix CheckPreconditionsAsync commit 992407407a42fec9087c9ed18e0bf5de30dff82c Author: Hsu Still <341464@gmail.com> Date: Sat Sep 2 14:07:12 2017 +0800 Add Async suffix to abstract Task methods --- .../Attributes/ParameterPreconditionAttribute.cs | 2 +- .../Attributes/PreconditionAttribute.cs | 4 ++-- .../Preconditions/RequireBotPermissionAttribute.cs | 2 +- .../Preconditions/RequireContextAttribute.cs | 2 +- .../Attributes/Preconditions/RequireNsfwAttribute.cs | 2 +- .../Preconditions/RequireOwnerAttribute.cs | 2 +- .../Preconditions/RequireUserPermissionAttribute.cs | 2 +- src/Discord.Net.Commands/CommandParser.cs | 6 +++--- src/Discord.Net.Commands/Info/CommandInfo.cs | 12 ++++++------ src/Discord.Net.Commands/Info/ParameterInfo.cs | 6 +++--- .../Readers/ChannelTypeReader.cs | 2 +- src/Discord.Net.Commands/Readers/EnumTypeReader.cs | 2 +- .../Readers/MessageTypeReader.cs | 2 +- .../Readers/NullableTypeReader.cs | 4 ++-- .../Readers/PrimitiveTypeReader.cs | 2 +- src/Discord.Net.Commands/Readers/RoleTypeReader.cs | 2 +- src/Discord.Net.Commands/Readers/TypeReader.cs | 2 +- src/Discord.Net.Commands/Readers/UserTypeReader.cs | 2 +- 18 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs index 49dae6080..209822583 100644 --- a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs @@ -7,6 +7,6 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] public abstract class ParameterPreconditionAttribute : Attribute { - public abstract Task CheckPermissions(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services); + public abstract Task CheckPermissionsAsync(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services); } } \ No newline at end of file diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index 3727510d9..367adebf0 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -8,11 +8,11 @@ namespace Discord.Commands { /// /// 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 = + /// 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); + public abstract Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs index b2cd3811c..6be142a45 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -41,7 +41,7 @@ namespace Discord.Commands GuildPermission = null; } - public override async Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override async Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { IGuildUser guildUser = null; if (context.Guild != null) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index a221eb4a9..5fa0fb1b9 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -38,7 +38,7 @@ namespace Discord.Commands Contexts = contexts; } - public override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { bool isValid = false; diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs index b3cf25365..c8e3bfa82 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs @@ -9,7 +9,7 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireNsfwAttribute : PreconditionAttribute { - public override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { if (context.Channel is ITextChannel text && text.IsNsfw) return Task.FromResult(PreconditionResult.FromSuccess()); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs index 2055a358e..e370aeec4 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -12,7 +12,7 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireOwnerAttribute : PreconditionAttribute { - public override async Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override async Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { switch (context.Client.TokenType) { diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index f5e3a9fc5..0179aa0ac 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -42,7 +42,7 @@ namespace Discord.Commands GuildPermission = null; } - public override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { var guildUser = context.User as IGuildUser; diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 394f8589d..28e36d54d 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -14,7 +14,7 @@ namespace Discord.Commands QuotedParameter } - public static async Task ParseArgs(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos) + public static async Task ParseArgsAsync(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos) { ParameterInfo curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); @@ -111,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, services).ConfigureAwait(false); + var typeReaderResult = await curParam.ParseAsync(context, argString, services).ConfigureAwait(false); if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches) return ParseResult.FromError(typeReaderResult); @@ -134,7 +134,7 @@ namespace Discord.Commands if (curParam != null && curParam.IsRemainder) { - var typeReaderResult = await curParam.Parse(context, argBuilder.ToString(), services).ConfigureAwait(false); + var typeReaderResult = await curParam.ParseAsync(context, argBuilder.ToString(), services).ConfigureAwait(false); if (!typeReaderResult.IsSuccess) return ParseResult.FromError(typeReaderResult); argList.Add(typeReaderResult); diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index c94be525f..9ca7ffff3 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -78,7 +78,7 @@ namespace Discord.Commands { foreach (PreconditionAttribute precondition in preconditionGroup) { - var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); + var result = await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false); if (!result.IsSuccess) return result; } @@ -87,7 +87,7 @@ namespace Discord.Commands { var results = new List(); foreach (PreconditionAttribute precondition in preconditionGroup) - results.Add(await precondition.CheckPermissions(context, this, services).ConfigureAwait(false)); + results.Add(await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false)); if (!results.Any(p => p.IsSuccess)) return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results); @@ -117,7 +117,7 @@ namespace Discord.Commands return ParseResult.FromError(preconditionResult); string input = searchResult.Text.Substring(startIndex); - return await CommandParser.ParseArgs(this, context, services, input, 0).ConfigureAwait(false); + return await CommandParser.ParseArgsAsync(this, context, services, input, 0).ConfigureAwait(false); } public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) @@ -163,11 +163,11 @@ namespace Discord.Commands switch (RunMode) { case RunMode.Sync: //Always sync - return await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); + return await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false); case RunMode.Async: //Always async var t2 = Task.Run(async () => { - await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); + await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false); }); break; } @@ -179,7 +179,7 @@ namespace Discord.Commands } } - private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider services) + private async Task ExecuteAsyncInternalAsync(ICommandContext context, object[] args, IServiceProvider services) { await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); try diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index e417b1ab6..4a56415e5 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -48,7 +48,7 @@ namespace Discord.Commands foreach (var precondition in Preconditions) { - var result = await precondition.CheckPermissions(context, this, arg, services).ConfigureAwait(false); + var result = await precondition.CheckPermissionsAsync(context, this, arg, services).ConfigureAwait(false); if (!result.IsSuccess) return result; } @@ -56,10 +56,10 @@ namespace Discord.Commands return PreconditionResult.FromSuccess(); } - public async Task Parse(ICommandContext context, string input, IServiceProvider services = null) + public async Task ParseAsync(ICommandContext context, string input, IServiceProvider services = null) { services = services ?? EmptyServiceProvider.Instance; - return await _reader.Read(context, input, services).ConfigureAwait(false); + return await _reader.ReadAsync(context, input, services).ConfigureAwait(false); } public override string ToString() => Name; diff --git a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs index 72c62282e..3136eb2cb 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, IServiceProvider services) + public override async Task ReadAsync(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 383b8e63c..c097e6189 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -44,7 +44,7 @@ namespace Discord.Commands _enumsByValue = byValueBuilder.ToImmutable(); } - public override Task Read(ICommandContext context, string input, IServiceProvider services) + public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { object enumValue; diff --git a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs index 895713e4f..fe576c3c6 100644 --- a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs @@ -7,7 +7,7 @@ namespace Discord.Commands internal class MessageTypeReader : TypeReader where T : class, IMessage { - public override async Task Read(ICommandContext context, string input, IServiceProvider services) + public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { ulong id; diff --git a/src/Discord.Net.Commands/Readers/NullableTypeReader.cs b/src/Discord.Net.Commands/Readers/NullableTypeReader.cs index 07976fb69..109689e15 100644 --- a/src/Discord.Net.Commands/Readers/NullableTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/NullableTypeReader.cs @@ -24,11 +24,11 @@ namespace Discord.Commands _baseTypeReader = baseTypeReader; } - public override async Task Read(ICommandContext context, string input, IServiceProvider services) + public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase)) return TypeReaderResult.FromSuccess(new T?()); - return await _baseTypeReader.Read(context, input, services); ; + return await _baseTypeReader.ReadAsync(context, input, services); } } } diff --git a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs index 2656741f0..b19a6bd69 100644 --- a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs @@ -30,7 +30,7 @@ namespace Discord.Commands _score = score; } - public override Task Read(ICommandContext context, string input, IServiceProvider services) + public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { if (_tryParse(input, out T value)) return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score))); diff --git a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs index 17786e6f0..703374c05 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, IServiceProvider services) + public override Task ReadAsync(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 2c4644376..af45a0aac 100644 --- a/src/Discord.Net.Commands/Readers/TypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TypeReader.cs @@ -5,6 +5,6 @@ namespace Discord.Commands { public abstract class TypeReader { - public abstract Task Read(ICommandContext context, string input, IServiceProvider services); + public abstract Task ReadAsync(ICommandContext context, string input, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index c71dac2d2..ca337aaf6 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, IServiceProvider services) + public override async Task ReadAsync(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? From 759db3414627bfd33091108e2dcf15ca14ac01c2 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Sat, 21 Oct 2017 18:51:20 +0100 Subject: [PATCH 11/27] Remove payload compression, use stream compression (#853) --- .../API/Gateway/IdentifyParams.cs | 2 - .../DiscordSocketApiClient.cs | 43 +++++++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs index e87c58221..af16f22f5 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs @@ -13,8 +13,6 @@ namespace Discord.API.Gateway public IDictionary Properties { get; set; } [JsonProperty("large_threshold")] public int LargeThreshold { get; set; } - [JsonProperty("compress")] - public bool UseCompression { get; set; } [JsonProperty("shard")] public Optional ShardingParams { get; set; } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 7d680eaf2..72781204c 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -29,7 +29,11 @@ namespace Discord.API private CancellationTokenSource _connectCancelToken; private string _gatewayUrl; private bool _isExplicitUrl; - + + //Store our decompression streams for zlib shared state + private MemoryStream _compressed; + private DeflateStream _decompressor; + internal IWebSocketClient WebSocketClient { get; } public ConnectionState ConnectionState { get; private set; } @@ -43,14 +47,29 @@ namespace Discord.API _isExplicitUrl = true; WebSocketClient = webSocketProvider(); //WebSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .NET Framework 4.6+) + WebSocketClient.BinaryMessage += async (data, index, count) => { - using (var compressed = new MemoryStream(data, index + 2, count - 2)) using (var decompressed = new MemoryStream()) { - using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) - zlib.CopyTo(decompressed); + if (data[0] == 0x78) + { + //Strip the zlib header + _compressed.Write(data, index + 2, count - 2); + _compressed.SetLength(count - 2); + } + else + { + _compressed.Write(data, index, count); + _compressed.SetLength(count); + } + + //Reset positions so we don't run out of memory + _compressed.Position = 0; + _decompressor.CopyTo(decompressed); + _compressed.Position = 0; decompressed.Position = 0; + using (var reader = new StreamReader(decompressed)) using (var jsonReader = new JsonTextReader(reader)) { @@ -76,6 +95,7 @@ namespace Discord.API await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); }; } + internal override void Dispose(bool disposing) { if (!_isDisposed) @@ -84,6 +104,8 @@ namespace Discord.API { _connectCancelToken?.Dispose(); (WebSocketClient as IDisposable)?.Dispose(); + _decompressor?.Dispose(); + _compressed?.Dispose(); } _isDisposed = true; } @@ -105,6 +127,12 @@ namespace Discord.API if (WebSocketClient == null) throw new NotSupportedException("This client is not configured with websocket support."); + //Re-create streams to reset the zlib state + _compressed?.Dispose(); + _decompressor?.Dispose(); + _compressed = new MemoryStream(); + _decompressor = new DeflateStream(_compressed, CompressionMode.Decompress); + ConnectionState = ConnectionState.Connecting; try { @@ -115,7 +143,7 @@ namespace Discord.API if (!_isExplicitUrl) { var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false); - _gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}"; + _gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream"; } await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false); @@ -191,7 +219,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); return await SendAsync("GET", () => "gateway/bot", new BucketIds(), options: options).ConfigureAwait(false); } - public async Task SendIdentifyAsync(int largeThreshold = 100, bool useCompression = true, int shardID = 0, int totalShards = 1, RequestOptions options = null) + public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var props = new Dictionary @@ -202,8 +230,7 @@ namespace Discord.API { Token = AuthToken, Properties = props, - LargeThreshold = largeThreshold, - UseCompression = useCompression, + LargeThreshold = largeThreshold }; if (totalShards > 1) msg.ShardingParams = new int[] { shardID, totalShards }; From f9963380a7a33c826507bebf555294b72bace633 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Wed, 25 Oct 2017 17:39:26 -0700 Subject: [PATCH 12/27] Proposed Solution for #674 Permissions Changes (#743) * Initial commit of changes. Changed permissions from bitwise index to use bitwise flags instead. Modified relevant methods involved * Revised enum value naming * Added FlagsAttribute to ChannelPermission, GuildPermission * Added comments per Joe4evr suggestion * Added underlines to hex value digits for readability per Joe4evr suggestion * updated names to better reflect actual permission names as per SubZero0 suggestion * fix for 236775c2d8aca9481d6c51c674f5727f97adec04 * Replaced Math.Pow with left shift operator * Cleaned up the formatting of ChannelPermission and GuildPermission enums to make it easier to read --- .../Entities/Permissions/ChannelPermission.cs | 62 ++++++++-------- .../Permissions/ChannelPermissions.cs | 28 ++++---- .../Entities/Permissions/GuildPermission.cs | 70 ++++++++++--------- .../Entities/Permissions/GuildPermissions.cs | 35 ++++++---- .../Permissions/OverwritePermissions.cs | 37 +++++----- src/Discord.Net.Core/Utils/Permissions.cs | 66 ++++++++--------- 6 files changed, 153 insertions(+), 145 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index a93f02497..3e438f43f 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -1,40 +1,36 @@ -namespace Discord +using System; + +namespace Discord { - public enum ChannelPermission : byte + [FlagsAttribute] + public enum ChannelPermission : ulong { - //General - CreateInstantInvite = 0, - //KickMembers = 1, - //BanMembers = 2, - //Administrator = 3, - ManageChannel = 4, - //ManageGuild = 5, + // General + CreateInstantInvite = 0x00_00_00_01, + ManageChannels = 0x00_00_00_10, - //Text - AddReactions = 6, - ReadMessages = 10, - SendMessages = 11, - SendTTSMessages = 12, - ManageMessages = 13, - EmbedLinks = 14, - AttachFiles = 15, - ReadMessageHistory = 16, - MentionEveryone = 17, - UseExternalEmojis = 18, + // Text + AddReactions = 0x00_00_00_40, + ReadMessages = 0x00_00_04_00, + SendMessages = 0x00_00_08_00, + SendTTSMessages = 0x00_00_10_00, + ManageMessages = 0x00_00_20_00, + EmbedLinks = 0x00_00_40_00, + AttachFiles = 0x00_00_80_00, + ReadMessageHistory = 0x00_01_00_00, + MentionEveryone = 0x00_02_00_00, + UseExternalEmojis = 0x00_04_00_00, - //Voice - Connect = 20, - Speak = 21, - MuteMembers = 22, - DeafenMembers = 23, - MoveMembers = 24, - UseVAD = 25, + // Voice + Connect = 0x00_10_00_00, + Speak = 0x00_20_00_00, + MuteMembers = 0x00_40_00_00, + DeafenMembers = 0x00_80_00_00, + MoveMembers = 0x01_00_00_00, + UseVAD = 0x02_00_00_00, - //General2 - //ChangeNickname = 26, - //ManageNicknames = 27, - ManagePermissions = 28, - ManageWebhooks = 29, - //ManageEmojis = 30 + // More General + ManageRoles = 0x10_00_00_00, + ManageWebhooks = 0x20_00_00_00, } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 94596e0e6..22e85263c 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -36,7 +36,7 @@ namespace Discord /// If True, a user may create invites. public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); /// If True, a user may create, delete and modify this channel. - public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannel); + public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannels); /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); @@ -72,8 +72,8 @@ namespace Discord /// If True, a user may use voice-activity-detection rather than push-to-talk. public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); - /// If True, a user may adjust permissions. This also implictly grants all other permissions. - public bool ManagePermissions => Permissions.GetValue(RawValue, ChannelPermission.ManagePermissions); + /// If True, a user may adjust role permissions. This also implictly grants all other permissions. + public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles); /// If True, a user may edit the webhooks for this channel. public bool ManageWebhooks => Permissions.GetValue(RawValue, ChannelPermission.ManageWebhooks); @@ -85,12 +85,12 @@ namespace Discord bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, - bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null, bool? manageWebhooks = null) + bool? moveMembers = null, bool? useVoiceActivation = null, bool? manageRoles = null, bool? manageWebhooks = null) { ulong value = initialValue; Permissions.SetValue(ref value, createInstantInvite, ChannelPermission.CreateInstantInvite); - Permissions.SetValue(ref value, manageChannel, ChannelPermission.ManageChannel); + Permissions.SetValue(ref value, manageChannel, ChannelPermission.ManageChannels); Permissions.SetValue(ref value, addReactions, ChannelPermission.AddReactions); Permissions.SetValue(ref value, readMessages, ChannelPermission.ReadMessages); Permissions.SetValue(ref value, sendMessages, ChannelPermission.SendMessages); @@ -107,7 +107,7 @@ namespace Discord Permissions.SetValue(ref value, deafenMembers, ChannelPermission.DeafenMembers); Permissions.SetValue(ref value, moveMembers, ChannelPermission.MoveMembers); Permissions.SetValue(ref value, useVoiceActivation, ChannelPermission.UseVAD); - Permissions.SetValue(ref value, managePermissions, ChannelPermission.ManagePermissions); + Permissions.SetValue(ref value, manageRoles, ChannelPermission.ManageRoles); Permissions.SetValue(ref value, manageWebhooks, ChannelPermission.ManageWebhooks); RawValue = value; @@ -119,10 +119,10 @@ namespace Discord bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, - bool moveMembers = false, bool useVoiceActivation = false, bool managePermissions = false, bool manageWebhooks = false) + bool moveMembers = false, bool useVoiceActivation = false, bool manageRoles = false, bool manageWebhooks = false) : this(0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, - speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks) + speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { } /// Creates a new ChannelPermissions from this one, changing the provided non-null permissions. @@ -131,21 +131,21 @@ namespace Discord bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool useExternalEmojis = false, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, - bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null, bool? manageWebhooks = null) + bool? moveMembers = null, bool? useVoiceActivation = null, bool? manageRoles = null, bool? manageWebhooks = null) => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, - speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks); + speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks); public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); public List ToList() { var perms = new List(); - ulong x = 1; - for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) + for (byte i = 0; i < Permissions.MaxBits; i++) { - if ((RawValue & x) != 0) - perms.Add((ChannelPermission)i); + ulong flag = ((ulong)1 << i); + if ((RawValue & flag) != 0) + perms.Add((ChannelPermission)flag); } return perms; } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 3975c1b8b..8469fd304 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -1,40 +1,44 @@ -namespace Discord +using System; + +namespace Discord { - public enum GuildPermission : byte + [FlagsAttribute] + public enum GuildPermission : ulong { - //General - CreateInstantInvite = 0, - KickMembers = 1, - BanMembers = 2, - Administrator = 3, - ManageChannels = 4, - ManageGuild = 5, + // General + CreateInstantInvite = 0x00_00_00_01, + KickMembers = 0x00_00_00_02, + BanMembers = 0x00_00_00_04, + Administrator = 0x00_00_00_08, + ManageChannels = 0x00_00_00_10, + ManageGuild = 0x00_00_00_20, - //Text - AddReactions = 6, - ReadMessages = 10, - SendMessages = 11, - SendTTSMessages = 12, - ManageMessages = 13, - EmbedLinks = 14, - AttachFiles = 15, - ReadMessageHistory = 16, - MentionEveryone = 17, - UseExternalEmojis = 18, + // Text + AddReactions = 0x00_00_00_40, + ViewAuditLog = 0x00_00_00_80, + ReadMessages = 0x00_00_04_00, + SendMessages = 0x00_00_08_00, + SendTTSMessages = 0x00_00_10_00, + ManageMessages = 0x00_00_20_00, + EmbedLinks = 0x00_00_40_00, + AttachFiles = 0x00_00_80_00, + ReadMessageHistory = 0x00_01_00_00, + MentionEveryone = 0x00_02_00_00, + UseExternalEmojis = 0x00_04_00_00, - //Voice - Connect = 20, - Speak = 21, - MuteMembers = 22, - DeafenMembers = 23, - MoveMembers = 24, - UseVAD = 25, + // Voice + Connect = 0x00_10_00_00, + Speak = 0x00_20_00_00, + MuteMembers = 0x00_40_00_00, + DeafenMembers = 0x00_80_00_00, + MoveMembers = 0x01_00_00_00, + UseVAD = 0x02_00_00_00, - //General2 - ChangeNickname = 26, - ManageNicknames = 27, - ManageRoles = 28, - ManageWebhooks = 29, - ManageEmojis = 30 + // General 2 + ChangeNickname = 0x04_00_00_00, + ManageNicknames = 0x08_00_00_00, + ManageRoles = 0x10_00_00_00, + ManageWebhooks = 0x20_00_00_00, + ManageEmojis = 0x40_00_00_00 } } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index c5f1efab0..030ccd587 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -11,7 +11,7 @@ namespace Discord /// Gets a GuildPermissions that grants all guild permissions for webhook users. public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); /// Gets a GuildPermissions that grants all guild permissions. - public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_0111111110001_111111); + public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_0111111110011_111111); /// Gets a packed value representing all the permissions in this GuildPermissions. public ulong RawValue { get; } @@ -31,6 +31,9 @@ namespace Discord /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); + /// If true, a user may view the audit log. + public bool ViewAuditLog => Permissions.GetValue(RawValue, GuildPermission.ViewAuditLog); + /// If True, a user may join channels. public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); /// If True, a user may send messages. @@ -78,11 +81,11 @@ namespace Discord public GuildPermissions(ulong rawValue) { RawValue = rawValue; } private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null, - bool? banMembers = null, bool? administrator = null, bool? manageChannel = null, bool? manageGuild = null, - bool? addReactions = null, + bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, + bool? addReactions = null, bool? viewAuditLog = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, - bool? userExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) { @@ -92,9 +95,10 @@ namespace Discord Permissions.SetValue(ref value, banMembers, GuildPermission.BanMembers); Permissions.SetValue(ref value, kickMembers, GuildPermission.KickMembers); Permissions.SetValue(ref value, administrator, GuildPermission.Administrator); - Permissions.SetValue(ref value, manageChannel, GuildPermission.ManageChannels); + Permissions.SetValue(ref value, manageChannels, GuildPermission.ManageChannels); Permissions.SetValue(ref value, manageGuild, GuildPermission.ManageGuild); Permissions.SetValue(ref value, addReactions, GuildPermission.AddReactions); + Permissions.SetValue(ref value, viewAuditLog, GuildPermission.ViewAuditLog); Permissions.SetValue(ref value, readMessages, GuildPermission.ReadMessages); Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages); Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages); @@ -103,7 +107,7 @@ namespace Discord Permissions.SetValue(ref value, attachFiles, GuildPermission.AttachFiles); Permissions.SetValue(ref value, readMessageHistory, GuildPermission.ReadMessageHistory); Permissions.SetValue(ref value, mentionEveryone, GuildPermission.MentionEveryone); - Permissions.SetValue(ref value, userExternalEmojis, GuildPermission.UseExternalEmojis); + Permissions.SetValue(ref value, useExternalEmojis, GuildPermission.UseExternalEmojis); Permissions.SetValue(ref value, connect, GuildPermission.Connect); Permissions.SetValue(ref value, speak, GuildPermission.Speak); Permissions.SetValue(ref value, muteMembers, GuildPermission.MuteMembers); @@ -122,26 +126,26 @@ namespace Discord /// Creates a new GuildPermissions with the provided permissions. public GuildPermissions(bool createInstantInvite = false, bool kickMembers = false, bool banMembers = false, bool administrator = false, bool manageChannels = false, bool manageGuild = false, - bool addReactions = false, + bool addReactions = false, bool viewAuditLog = false, bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, bool moveMembers = false, bool useVoiceActivation = false, bool? changeNickname = false, bool? manageNicknames = false, bool manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false) - : this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, + : this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, viewAuditLog, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, manageWebhooks, manageEmojis) { } /// Creates a new GuildPermissions from this one, changing the provided non-null permissions. public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null, bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, - bool? addReactions = null, + bool? addReactions = null, bool? viewAuditLog = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) - => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, + => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, viewAuditLog, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); @@ -151,11 +155,14 @@ namespace Discord public List ToList() { var perms = new List(); - ulong x = 1; - for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) + + // bitwise operations on raw value + // each of the GuildPermissions increments by 2^i from 0 to MaxBits + for (byte i = 0; i < Permissions.MaxBits; i++) { - if ((RawValue & x) != 0) - perms.Add((GuildPermission)i); + ulong flag = ((ulong)1 << i); + if ((RawValue & flag) != 0) + perms.Add((GuildPermission)flag); } return perms; } diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index c3f8b2bab..c3e296e2c 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -23,7 +23,7 @@ namespace Discord /// If Allowed, a user may create invites. public PermValue CreateInstantInvite => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); /// If Allowed, a user may create, delete and modify this channel. - public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannel); + public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannels); /// If Allowed, a user may add reactions. public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); /// If Allowed, a user may join channels. @@ -58,8 +58,8 @@ namespace Discord /// If Allowed, a user may use voice-activity-detection rather than push-to-talk. public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); - /// If Allowed, a user may adjust permissions. This also implictly grants all other permissions. - public PermValue ManagePermissions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManagePermissions); + /// If Allowed, a user may adjust role permissions. This also implictly grants all other permissions. + public PermValue ManageRoles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageRoles); /// If True, a user may edit the webhooks for this channel. public PermValue ManageWebhooks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageWebhooks); @@ -75,11 +75,11 @@ namespace Discord PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, - PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? managePermissions = null, + PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? manageRoles = null, PermValue? manageWebhooks = null) { Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); - Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannel); + Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannels); Permissions.SetValue(ref allowValue, ref denyValue, addReactions, ChannelPermission.AddReactions); Permissions.SetValue(ref allowValue, ref denyValue, readMessages, ChannelPermission.ReadMessages); Permissions.SetValue(ref allowValue, ref denyValue, sendMessages, ChannelPermission.SendMessages); @@ -96,7 +96,7 @@ namespace Discord Permissions.SetValue(ref allowValue, ref denyValue, deafenMembers, ChannelPermission.DeafenMembers); Permissions.SetValue(ref allowValue, ref denyValue, moveMembers, ChannelPermission.MoveMembers); Permissions.SetValue(ref allowValue, ref denyValue, useVoiceActivation, ChannelPermission.UseVAD); - Permissions.SetValue(ref allowValue, ref denyValue, managePermissions, ChannelPermission.ManagePermissions); + Permissions.SetValue(ref allowValue, ref denyValue, manageRoles, ChannelPermission.ManageRoles); Permissions.SetValue(ref allowValue, ref denyValue, manageWebhooks, ChannelPermission.ManageWebhooks); AllowValue = allowValue; @@ -109,10 +109,10 @@ namespace Discord PermValue readMessages = PermValue.Inherit, PermValue sendMessages = PermValue.Inherit, PermValue sendTTSMessages = PermValue.Inherit, PermValue manageMessages = PermValue.Inherit, PermValue embedLinks = PermValue.Inherit, PermValue attachFiles = PermValue.Inherit, PermValue readMessageHistory = PermValue.Inherit, PermValue mentionEveryone = PermValue.Inherit, PermValue useExternalEmojis = PermValue.Inherit, PermValue connect = PermValue.Inherit, PermValue speak = PermValue.Inherit, PermValue muteMembers = PermValue.Inherit, PermValue deafenMembers = PermValue.Inherit, - PermValue moveMembers = PermValue.Inherit, PermValue useVoiceActivation = PermValue.Inherit, PermValue managePermissions = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit) + PermValue moveMembers = PermValue.Inherit, PermValue useVoiceActivation = PermValue.Inherit, PermValue manageRoles = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit) : this(0, 0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, - moveMembers, useVoiceActivation, managePermissions, manageWebhooks) { } + moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { } /// Creates a new OverwritePermissions from this one, changing the provided non-null permissions. public OverwritePermissions Modify(PermValue? createInstantInvite = null, PermValue? manageChannel = null, @@ -120,30 +120,31 @@ namespace Discord PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, - PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? managePermissions = null, PermValue? manageWebhooks = null) + PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? manageRoles = null, PermValue? manageWebhooks = null) => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, - moveMembers, useVoiceActivation, managePermissions, manageWebhooks); + moveMembers, useVoiceActivation, manageRoles, manageWebhooks); public List ToAllowList() { var perms = new List(); - ulong x = 1; - for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) + for (byte i = 0; i < Permissions.MaxBits; i++) { - if ((AllowValue & x) != 0) - perms.Add((ChannelPermission)i); + // first operand must be long or ulong to shift >31 bits + ulong flag = ((ulong)1 << i); + if ((AllowValue & flag) != 0) + perms.Add((ChannelPermission)flag); } return perms; } public List ToDenyList() { var perms = new List(); - ulong x = 1; - for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) + for (byte i = 0; i < Permissions.MaxBits; i++) { - if ((DenyValue & x) != 0) - perms.Add((ChannelPermission)i); + ulong flag = ((ulong)1 << i); + if ((DenyValue & flag) != 0) + perms.Add((ChannelPermission)flag); } return perms; } diff --git a/src/Discord.Net.Core/Utils/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs index c2b7e83ea..a7de90623 100644 --- a/src/Discord.Net.Core/Utils/Permissions.cs +++ b/src/Discord.Net.Core/Utils/Permissions.cs @@ -7,84 +7,84 @@ namespace Discord public const int MaxBits = 53; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission bit) - => GetValue(allow, deny, (byte)bit); + public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission flag) + => GetValue(allow, deny, (ulong)flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PermValue GetValue(ulong allow, ulong deny, GuildPermission bit) - => GetValue(allow, deny, (byte)bit); + public static PermValue GetValue(ulong allow, ulong deny, GuildPermission flag) + => GetValue(allow, deny, (ulong)flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PermValue GetValue(ulong allow, ulong deny, byte bit) + public static PermValue GetValue(ulong allow, ulong deny, ulong flag) { - if (HasBit(allow, bit)) + if (HasFlag(allow, flag)) return PermValue.Allow; - else if (HasBit(deny, bit)) + else if (HasFlag(deny, flag)) return PermValue.Deny; else return PermValue.Inherit; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GetValue(ulong value, ChannelPermission bit) - => GetValue(value, (byte)bit); + public static bool GetValue(ulong value, ChannelPermission flag) + => GetValue(value, (ulong)flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GetValue(ulong value, GuildPermission bit) - => GetValue(value, (byte)bit); + public static bool GetValue(ulong value, GuildPermission flag) + => GetValue(value, (ulong)flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GetValue(ulong value, byte bit) => HasBit(value, bit); + public static bool GetValue(ulong value, ulong flag) => HasFlag(value, flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission bit) - => SetValue(ref rawValue, value, (byte)bit); + public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission flag) + => SetValue(ref rawValue, value, (ulong)flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong rawValue, bool? value, GuildPermission bit) - => SetValue(ref rawValue, value, (byte)bit); + public static void SetValue(ref ulong rawValue, bool? value, GuildPermission flag) + => SetValue(ref rawValue, value, (ulong)flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong rawValue, bool? value, byte bit) + public static void SetValue(ref ulong rawValue, bool? value, ulong flag) { if (value.HasValue) { if (value == true) - SetBit(ref rawValue, bit); + SetFlag(ref rawValue, flag); else - UnsetBit(ref rawValue, bit); + UnsetFlag(ref rawValue, flag); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission bit) - => SetValue(ref allow, ref deny, value, (byte)bit); + public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission flag) + => SetValue(ref allow, ref deny, value, (ulong)flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission bit) - => SetValue(ref allow, ref deny, value, (byte)bit); + public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission flag) + => SetValue(ref allow, ref deny, value, (ulong)flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, byte bit) + public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ulong flag) { if (value.HasValue) { switch (value) { case PermValue.Allow: - SetBit(ref allow, bit); - UnsetBit(ref deny, bit); + SetFlag(ref allow, flag); + UnsetFlag(ref deny, flag); break; case PermValue.Deny: - UnsetBit(ref allow, bit); - SetBit(ref deny, bit); + UnsetFlag(ref allow, flag); + SetFlag(ref deny, flag); break; default: - UnsetBit(ref allow, bit); - UnsetBit(ref deny, bit); + UnsetFlag(ref allow, flag); + UnsetFlag(ref deny, flag); break; } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool HasBit(ulong value, byte bit) => (value & (1U << bit)) != 0; + private static bool HasFlag(ulong value, ulong flag) => (value & flag) != 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetBit(ref ulong value, byte bit) => value |= (1U << bit); + public static void SetFlag(ref ulong value, ulong flag) => value |= flag; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit); + public static void UnsetFlag(ref ulong value, ulong flag) => value &= ~flag; public static ChannelPermissions ToChannelPerms(IGuildChannel channel, ulong guildPermissions) => new ChannelPermissions(guildPermissions & ChannelPermissions.All(channel).RawValue); From 5218e6be97aa20428104366afb34bf6e0800c451 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sun, 5 Nov 2017 21:57:24 -0500 Subject: [PATCH 13/27] Add IEmbed#ToEmbedBuilder extension method (#863) * Add IEmbed#ToEmbedBuilder extension method * Implementing reviewed changes. * Switch to object initializers for author and footer. --- .../Extensions/EmbedBuilderExtensions.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs b/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs index cee9a136e..2eb4ed473 100644 --- a/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs @@ -1,3 +1,5 @@ +using System; + namespace Discord { public static class EmbedBuilderExtensions @@ -19,5 +21,38 @@ namespace Discord public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IGuildUser user) => builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.GetAvatarUrl()); + + public static EmbedBuilder ToEmbedBuilder(this IEmbed embed) + { + if (embed.Type != EmbedType.Rich) + throw new InvalidOperationException($"Only {nameof(EmbedType.Rich)} embeds may be built."); + + var builder = new EmbedBuilder + { + Author = new EmbedAuthorBuilder + { + Name = embed.Author?.Name, + IconUrl = embed.Author?.IconUrl, + Url = embed.Author?.Url + }, + Color = embed.Color ?? Color.Default, + Description = embed.Description, + Footer = new EmbedFooterBuilder + { + Text = embed.Footer?.Text, + IconUrl = embed.Footer?.IconUrl + }, + ImageUrl = embed.Image?.Url, + ThumbnailUrl = embed.Thumbnail?.Url, + Timestamp = embed.Timestamp, + Title = embed.Title, + Url = embed.Url + }; + + foreach (var field in embed.Fields) + builder.AddField(field.Name, field.Value, field.Inline); + + return builder; + } } } From dc151e8998647cc3eaacc9afa931073c6923554f Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sun, 5 Nov 2017 22:05:09 -0500 Subject: [PATCH 14/27] Update VS Code csproj example to target version 1.0.2. (#849) * Update target version to 1.0.2. * Change to target latest 1.x stable. --- docs/guides/getting_started/samples/project.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/getting_started/samples/project.csproj b/docs/guides/getting_started/samples/project.csproj index 8daf71877..feb0b0c40 100644 --- a/docs/guides/getting_started/samples/project.csproj +++ b/docs/guides/getting_started/samples/project.csproj @@ -7,7 +7,7 @@ - + From da335b95c4b46478646274e4d8be484e66849179 Mon Sep 17 00:00:00 2001 From: enzosk8 <32940513+enzosk8@users.noreply.github.com> Date: Sun, 5 Nov 2017 19:05:19 -0800 Subject: [PATCH 15/27] Fix general typos (#852) --- docs/guides/getting_started/intro.md | 2 +- docs/guides/getting_started/samples/intro/structure.cs | 4 ++-- src/Discord.Net.Core/Utils/Optional.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestChannel.cs | 4 ++-- .../Entities/Channels/RestGuildChannel.cs | 8 ++++---- .../Entities/Channels/SocketGuildChannel.cs | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/guides/getting_started/intro.md b/docs/guides/getting_started/intro.md index 02f04bec4..db086df21 100644 --- a/docs/guides/getting_started/intro.md +++ b/docs/guides/getting_started/intro.md @@ -74,7 +74,7 @@ async main. [!code-csharp[Async Context](samples/intro/async-context.cs)] -As a result of this, your program will now start and immidiately +As a result of this, your program will now start and immediately jump into an async context. This will allow us to create a connection to Discord later on without needing to worry about setting up the correct async implementation. diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index 789ceff76..c15e672ee 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -80,7 +80,7 @@ class Program private async Task MainAsync() { - // Centralize the logic for commands into a seperate method. + // Centralize the logic for commands into a separate method. await InitCommands(); // Login and connect. @@ -138,7 +138,7 @@ class Program var context = new SocketCommandContext(_client, msg); // Execute the command. (result does not indicate a return value, - // rather an object stating if the command executed succesfully). + // rather an object stating if the command executed successfully). var result = await _commands.ExecuteAsync(context, pos, _services); // Uncomment the following lines if you want the bot diff --git a/src/Discord.Net.Core/Utils/Optional.cs b/src/Discord.Net.Core/Utils/Optional.cs index df927b7ea..eb3cbdca2 100644 --- a/src/Discord.Net.Core/Utils/Optional.cs +++ b/src/Discord.Net.Core/Utils/Optional.cs @@ -10,7 +10,7 @@ namespace Discord public static Optional Unspecified => default(Optional); private readonly T _value; - /// Gets the value for this paramter. + /// Gets the value for this parameter. public T Value { get diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 342e57717..04cc5a937 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -48,8 +48,8 @@ namespace Discord.Rest string IChannel.Name => null; Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); //Overriden + => Task.FromResult(null); //Overridden IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => AsyncEnumerable.Empty>(); //Overriden + => AsyncEnumerable.Empty>(); //Overridden } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 07832a3a9..5e335446f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -154,14 +154,14 @@ namespace Discord.Rest => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => AsyncEnumerable.Empty>(); //Overriden //Overriden in Text/Voice + => AsyncEnumerable.Empty>(); //Overridden //Overridden in Text/Voice Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); //Overriden in Text/Voice + => Task.FromResult(null); //Overridden in Text/Voice //IChannel IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => AsyncEnumerable.Empty>(); //Overriden in Text/Voice + => AsyncEnumerable.Empty>(); //Overridden in Text/Voice Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); //Overriden in Text/Voice + => Task.FromResult(null); //Overridden in Text/Voice } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 1fe9a741f..16453b9fb 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -154,8 +154,8 @@ namespace Discord.WebSocket //IChannel IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overriden in Text/Voice + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); //Overriden in Text/Voice + => Task.FromResult(GetUser(id)); //Overridden in Text/Voice } } From e30fd29085b59e6b891cc987841e970e3e93c8bd Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 6 Nov 2017 04:06:28 +0100 Subject: [PATCH 16/27] Tweaks to audio docs (#867) * Tweaks to audio docs * Make it more obvious that -1 means infinity --- .../getting_started/samples/intro/structure.cs | 3 ++- docs/guides/voice/samples/audio_create_ffmpeg.cs | 9 ++++----- docs/guides/voice/samples/audio_ffmpeg.cs | 12 +++++++----- docs/guides/voice/samples/joining_audio.cs | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index c15e672ee..bdfc12b67 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Discord; @@ -88,7 +89,7 @@ class Program await _client.StartAsync(); // Wait infinitely so your bot actually stays connected. - await Task.Delay(-1); + await Task.Delay(Timeout.Infinite); } private IServiceProvider _services; diff --git a/docs/guides/voice/samples/audio_create_ffmpeg.cs b/docs/guides/voice/samples/audio_create_ffmpeg.cs index e24af088b..dda560efe 100644 --- a/docs/guides/voice/samples/audio_create_ffmpeg.cs +++ b/docs/guides/voice/samples/audio_create_ffmpeg.cs @@ -1,11 +1,10 @@ private Process CreateStream(string path) { - var ffmpeg = new ProcessStartInfo + return Process.Start(new ProcessStartInfo { FileName = "ffmpeg", - Arguments = $"-i {path} -ac 2 -f s16le -ar 48000 pipe:1", + Arguments = $"-hide_banner -loglevel panic -i \"{path}\" -ac 2 -f s16le -ar 48000 pipe:1", UseShellExecute = false, RedirectStandardOutput = true, - }; - return Process.Start(ffmpeg); -} \ No newline at end of file + }); +} diff --git a/docs/guides/voice/samples/audio_ffmpeg.cs b/docs/guides/voice/samples/audio_ffmpeg.cs index b9430ac11..d36fbbc20 100644 --- a/docs/guides/voice/samples/audio_ffmpeg.cs +++ b/docs/guides/voice/samples/audio_ffmpeg.cs @@ -1,9 +1,11 @@ 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); - await output.CopyToAsync(discord); - await discord.FlushAsync(); + using (var ffmpeg = CreateStream(path)) + using (var output = ffmpeg.StandardOutput.BaseStream) + using (var discord = client.CreatePCMStream(AudioApplication.Mixed)) + { + try { await output.CopyToAsync(discord); } + finally { await discord.FlushAsync(); } + } } diff --git a/docs/guides/voice/samples/joining_audio.cs b/docs/guides/voice/samples/joining_audio.cs index 0cc36978a..4cec67540 100644 --- a/docs/guides/voice/samples/joining_audio.cs +++ b/docs/guides/voice/samples/joining_audio.cs @@ -7,4 +7,4 @@ public async Task JoinChannel(IVoiceChannel channel = null) // For the next step with transmitting audio, you would want to pass this Audio Client in to a service. var audioClient = await channel.ConnectAsync(); -} \ No newline at end of file +} From dec7cb2b5b8016e5de28477396d0228241b463a4 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Sun, 5 Nov 2017 19:06:41 -0800 Subject: [PATCH 17/27] Fix GuildPermission Modify, Add Missing Permission to AllowAll (#866) * Use named parameters in GuildPermission constructor, fix ordering of parmeters in Modify constructor call * fix missing constructor parameter * Added missing Webhook permission for the all text permissions value * Resolves #869 Add UseExternalEmojis permission to GuildPermissions.All --- .../Permissions/ChannelPermissions.cs | 2 +- .../Entities/Permissions/GuildPermissions.cs | 43 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 22e85263c..4c11d0db0 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -10,7 +10,7 @@ namespace Discord /// Gets a blank ChannelPermissions that grants no permissions. public static readonly ChannelPermissions None = new ChannelPermissions(); /// Gets a ChannelPermissions that grants all permissions for text channels. - public static readonly ChannelPermissions Text = new ChannelPermissions(0b00100_0000000_1111111110001_010001); + public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); /// Gets a ChannelPermissions that grants all permissions for voice channels. public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001); /// Gets a ChannelPermissions that grants all permissions for direct message channels. diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 030ccd587..4ee3b0fc6 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -11,7 +11,7 @@ namespace Discord /// Gets a GuildPermissions that grants all guild permissions for webhook users. public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); /// Gets a GuildPermissions that grants all guild permissions. - public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_0111111110011_111111); + public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_11111111110011_111111); /// Gets a packed value representing all the permissions in this GuildPermissions. public ulong RawValue { get; } @@ -28,7 +28,7 @@ namespace Discord public bool ManageChannels => Permissions.GetValue(RawValue, GuildPermission.ManageChannels); /// If True, a user may adjust guild properties. public bool ManageGuild => Permissions.GetValue(RawValue, GuildPermission.ManageGuild); - + /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); /// If true, a user may view the audit log. @@ -80,13 +80,13 @@ namespace Discord /// Creates a new GuildPermissions with the provided packed value. public GuildPermissions(ulong rawValue) { RawValue = rawValue; } - private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null, - bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, + private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null, + bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, bool? addReactions = null, bool? viewAuditLog = null, - bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, - bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, - bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, - bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, + bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, + bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, + bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) { ulong value = initialValue; @@ -124,31 +124,36 @@ namespace Discord } /// Creates a new GuildPermissions with the provided permissions. - public GuildPermissions(bool createInstantInvite = false, bool kickMembers = false, + public GuildPermissions(bool createInstantInvite = false, bool kickMembers = false, bool banMembers = false, bool administrator = false, bool manageChannels = false, bool manageGuild = false, bool addReactions = false, bool viewAuditLog = false, bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, - bool moveMembers = false, bool useVoiceActivation = false, bool? changeNickname = false, bool? manageNicknames = false, + bool moveMembers = false, bool useVoiceActivation = false, bool? changeNickname = false, bool? manageNicknames = false, bool manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false) - : this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, viewAuditLog, - readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, - manageWebhooks, manageEmojis) { } + : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, kickMembers: kickMembers, banMembers: banMembers, + administrator: administrator, manageChannels: manageChannels, manageGuild: manageGuild, addReactions: addReactions, + viewAuditLog: viewAuditLog, readMessages: readMessages, sendMessages: sendMessages, sendTTSMessages: sendTTSMessages, + manageMessages: manageMessages, embedLinks: embedLinks, attachFiles: attachFiles, readMessageHistory: readMessageHistory, + mentionEveryone: mentionEveryone, useExternalEmojis: useExternalEmojis, connect: connect, speak: speak, muteMembers: muteMembers, + deafenMembers: deafenMembers, moveMembers: moveMembers, useVoiceActivation: useVoiceActivation, changeNickname: changeNickname, + manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, manageEmojis: manageEmojis) + { } /// Creates a new GuildPermissions from this one, changing the provided non-null permissions. - public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null, + public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null, bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, bool? addReactions = null, bool? viewAuditLog = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, - bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, + bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) - => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, viewAuditLog, - readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, - speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles, - manageWebhooks, manageEmojis); + => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, + viewAuditLog, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, + readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, + useVoiceActivation, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); public bool Has(GuildPermission permission) => Permissions.GetValue(RawValue, permission); From 676be4081bef69ccaaf99635375a8528adf60eee Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 6 Nov 2017 20:40:59 -0500 Subject: [PATCH 18/27] Ignore messages with an ID of 0 when calculating age --- src/Discord.Net.Core/Utils/Preconditions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index bec8de9dc..300f584e4 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -188,7 +188,8 @@ namespace Discord var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14))); for (var i = 0; i < collection.Length; i++) { - if (collection[i] <= minimum) + if (collection[i] == 0) continue; + if (collection[i] <= minimum) throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); } } From 8d533930faf98665c3f13827f66e102c866d4926 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 10 Nov 2017 15:26:58 -0500 Subject: [PATCH 19/27] Move CommandExecuted invoking up a scope for generic Task commands Resovles #870 --- src/Discord.Net.Commands/Info/CommandInfo.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 9ca7ffff3..6bb621f94 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -199,10 +199,13 @@ namespace Discord.Commands return result; } else + { await task.ConfigureAwait(false); + var result = ExecuteResult.FromSuccess(); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); + } var executeResult = ExecuteResult.FromSuccess(); - await Module.Service._commandExecutedEvent.InvokeAsync(this, context, executeResult).ConfigureAwait(false); return executeResult; } catch (Exception ex) From 9979a027d54c355fb344eee975ded1ed67a6d291 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 10 Nov 2017 15:39:33 -0500 Subject: [PATCH 20/27] Change default InviteAge to 24 hours Resolves #859 --- src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs | 2 +- src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs | 2 +- .../Entities/Channels/SocketGuildChannel.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs index 3d08a8c51..c7cf0b3c2 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -20,7 +20,7 @@ namespace Discord /// The time (in seconds) until the invite expires. Set to null to never expire. /// The max amount of times this invite may be used. Set to null to have unlimited uses. /// If true, a user accepting this invite will be kicked from the guild after closing their client. - Task CreateInviteAsync(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); + Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); /// Returns a collection of all invites to this channel. Task> GetInvitesAsync(RequestOptions options = null); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 5e335446f..1ce1c8368 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -119,7 +119,7 @@ namespace Discord.Rest public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); - public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); public override string ToString() => Name; diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs index 48eb8ec3e..401263555 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs @@ -51,7 +51,7 @@ namespace Discord.Rpc public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); - public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); public override string ToString() => Name; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 16453b9fb..8e24a5196 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -113,7 +113,7 @@ namespace Discord.WebSocket public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); - public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); public new virtual SocketGuildUser GetUser(ulong id) => null; From cf8de42b624074f736ec15f76f20056a8d1f784a Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Mon, 20 Nov 2017 11:54:10 -0800 Subject: [PATCH 21/27] Add GuildPermissions and ChannelPermissions Unit Tests (#873) * initial commit * Add GuildPermission tests for constructor and modify parameters * Fixed GuildPermission All value. Previous value had an additional digit that would still resolve to correct permission flags, but raw value would be incorrect. This matches the result of the sum of all GuildPermission flags * Added raw value check to guild permission modify tests * Add ChannelPermissions tests --- .../Entities/Permissions/GuildPermissions.cs | 2 +- .../Tests.ChannelPermissions.cs | 324 ++++++++++++++++++ .../Tests.GuildPermissions.cs | 304 ++++++++++++++++ 3 files changed, 629 insertions(+), 1 deletion(-) create mode 100644 test/Discord.Net.Tests/Tests.ChannelPermissions.cs create mode 100644 test/Discord.Net.Tests/Tests.GuildPermissions.cs diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 4ee3b0fc6..a880e62ca 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -11,7 +11,7 @@ namespace Discord /// Gets a GuildPermissions that grants all guild permissions for webhook users. public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); /// Gets a GuildPermissions that grants all guild permissions. - public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_11111111110011_111111); + public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_1111111110011_111111); /// Gets a packed value representing all the permissions in this GuildPermissions. public ulong RawValue { get; } diff --git a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs new file mode 100644 index 000000000..c5b22e277 --- /dev/null +++ b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs @@ -0,0 +1,324 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Discord +{ + public partial class Tests + { + [Fact] + public async Task TestChannelPermission() + { + var perm = new ChannelPermissions(); + + // check initial values + Assert.Equal((ulong)0, perm.RawValue); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // permissions list empty by default + Assert.Empty(perm.ToList()); + + // test modify with no parameters + var copy = perm.Modify(); + Assert.Equal((ulong)0, copy.RawValue); + + // test the values that are returned by ChannelPermission.All + Assert.Equal((ulong)0, ChannelPermissions.None.RawValue); + + // for text channels + ulong textChannel = (ulong)( ChannelPermission.CreateInstantInvite + | ChannelPermission.ManageChannels + | ChannelPermission.AddReactions + | ChannelPermission.ReadMessages + | ChannelPermission.SendMessages + | ChannelPermission.SendTTSMessages + | ChannelPermission.ManageMessages + | ChannelPermission.EmbedLinks + | ChannelPermission.AttachFiles + | ChannelPermission.ReadMessageHistory + | ChannelPermission.MentionEveryone + | ChannelPermission.UseExternalEmojis + | ChannelPermission.ManageRoles + | ChannelPermission.ManageWebhooks); + + Assert.Equal(textChannel, ChannelPermissions.Text.RawValue); + + // voice channels + ulong voiceChannel = (ulong)( + ChannelPermission.CreateInstantInvite + | ChannelPermission.ManageChannels + | ChannelPermission.Connect + | ChannelPermission.Speak + | ChannelPermission.MuteMembers + | ChannelPermission.DeafenMembers + | ChannelPermission.MoveMembers + | ChannelPermission.UseVAD + | ChannelPermission.ManageRoles); + + Assert.Equal(voiceChannel, ChannelPermissions.Voice.RawValue); + + // DM Channels + ulong dmChannel = (ulong)( + ChannelPermission.ReadMessages + | ChannelPermission.SendMessages + | ChannelPermission.EmbedLinks + | ChannelPermission.AttachFiles + | ChannelPermission.ReadMessageHistory + | ChannelPermission.UseExternalEmojis + | ChannelPermission.Connect + | ChannelPermission.Speak + | ChannelPermission.UseVAD + ); + Assert.Equal(dmChannel, ChannelPermissions.DM.RawValue); + + // group channel + ulong groupChannel = (ulong)( + ChannelPermission.SendMessages + | ChannelPermission.EmbedLinks + | ChannelPermission.AttachFiles + | ChannelPermission.SendTTSMessages + | ChannelPermission.Connect + | ChannelPermission.Speak + | ChannelPermission.UseVAD + ); + Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue); + } + + public async Task TestChannelPermissionModify() + { + // test channel permission modify + + var perm = new ChannelPermissions(); + + // ensure that the permission is initially false + Assert.False(perm.CreateInstantInvite); + + // ensure that when modified it works + perm = perm.Modify(createInstantInvite: true); + Assert.True(perm.CreateInstantInvite); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.CreateInstantInvite); + + // set false again, move on to next permission + perm = perm.Modify(createInstantInvite: false); + Assert.False(perm.CreateInstantInvite); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.ManageChannel); + + perm = perm.Modify(manageChannel: true); + Assert.True(perm.ManageChannel); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageChannels); + + perm = perm.Modify(manageChannel: false); + Assert.False(perm.ManageChannel); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.AddReactions); + + perm = perm.Modify(addReactions: true); + Assert.True(perm.AddReactions); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.AddReactions); + + perm = perm.Modify(addReactions: false); + Assert.False(perm.AddReactions); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.ReadMessages); + + perm = perm.Modify(readMessages: true); + Assert.True(perm.ReadMessages); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessages); + + perm = perm.Modify(readMessages: false); + Assert.False(perm.ReadMessages); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.SendMessages); + + perm = perm.Modify(sendMessages: true); + Assert.True(perm.SendMessages); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendMessages); + + perm = perm.Modify(sendMessages: false); + Assert.False(perm.SendMessages); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.SendTTSMessages); + + perm = perm.Modify(sendTTSMessages: true); + Assert.True(perm.SendTTSMessages); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendTTSMessages); + + perm = perm.Modify(sendTTSMessages: false); + Assert.False(perm.SendTTSMessages); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.ManageMessages); + + perm = perm.Modify(manageMessages: true); + Assert.True(perm.ManageMessages); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageMessages); + + perm = perm.Modify(manageMessages: false); + Assert.False(perm.ManageMessages); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.EmbedLinks); + + perm = perm.Modify(embedLinks: true); + Assert.True(perm.EmbedLinks); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.EmbedLinks); + + perm = perm.Modify(embedLinks: false); + Assert.False(perm.EmbedLinks); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.AttachFiles); + + perm = perm.Modify(attachFiles: true); + Assert.True(perm.AttachFiles); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.AttachFiles); + + perm = perm.Modify(attachFiles: false); + Assert.False(perm.AttachFiles); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.ReadMessageHistory); + + perm = perm.Modify(readMessageHistory: true); + Assert.True(perm.ReadMessageHistory); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessageHistory); + + perm = perm.Modify(readMessageHistory: false); + Assert.False(perm.ReadMessageHistory); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.MentionEveryone); + + perm = perm.Modify(mentionEveryone: true); + Assert.True(perm.MentionEveryone); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.MentionEveryone); + + perm = perm.Modify(mentionEveryone: false); + Assert.False(perm.MentionEveryone); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.UseExternalEmojis); + + perm = perm.Modify(useExternalEmojis: true); + Assert.True(perm.UseExternalEmojis); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseExternalEmojis); + + perm = perm.Modify(useExternalEmojis: false); + Assert.False(perm.UseExternalEmojis); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.Connect); + + perm = perm.Modify(connect: true); + Assert.True(perm.Connect); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.Connect); + + perm = perm.Modify(connect: false); + Assert.False(perm.Connect); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.Speak); + + perm = perm.Modify(speak: true); + Assert.True(perm.Speak); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.Speak); + + perm = perm.Modify(speak: false); + Assert.False(perm.Speak); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.MuteMembers); + + perm = perm.Modify(muteMembers: true); + Assert.True(perm.MuteMembers); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.MuteMembers); + + perm = perm.Modify(muteMembers: false); + Assert.False(perm.MuteMembers); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.DeafenMembers); + + perm = perm.Modify(deafenMembers: true); + Assert.True(perm.DeafenMembers); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.DeafenMembers); + + perm = perm.Modify(deafenMembers: false); + Assert.False(perm.DeafenMembers); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.MoveMembers); + + perm = perm.Modify(moveMembers: true); + Assert.True(perm.MoveMembers); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.MoveMembers); + + perm = perm.Modify(moveMembers: false); + Assert.False(perm.MoveMembers); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.UseVAD); + + perm = perm.Modify(useVoiceActivation: true); + Assert.True(perm.UseVAD); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseVAD); + + perm = perm.Modify(useVoiceActivation: false); + Assert.False(perm.UseVAD); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.ManageRoles); + + perm = perm.Modify(manageRoles: true); + Assert.True(perm.ManageRoles); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageRoles); + + perm = perm.Modify(manageRoles: false); + Assert.False(perm.ManageRoles); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + + // individual permission test + Assert.False(perm.ManageWebhooks); + + perm = perm.Modify(manageWebhooks: true); + Assert.True(perm.ManageWebhooks); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageWebhooks); + + perm = perm.Modify(manageWebhooks: false); + Assert.False(perm.ManageWebhooks); + Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); + } + + [Fact] + public async Task TestChannelTypeResolution() + { + ITextChannel someChannel = null; + // null channels will throw exception + Assert.Throws(() => ChannelPermissions.All(someChannel)); + } + } +} diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs new file mode 100644 index 000000000..a79706b9c --- /dev/null +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -0,0 +1,304 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Discord +{ + public partial class Tests + { + [Fact] + public async Task TestGuildPermission() + { + // Test Guild Permission Constructors + var perm = new GuildPermissions(); + + // the default raw value is 0 + Assert.Equal((ulong)0, perm.RawValue); + // also check that it is the same as none + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // permissions list is empty by default + Assert.Empty(perm.ToList()); + Assert.NotNull(perm.ToList()); + + // Test modify with no parameters + var copy = perm.Modify(); + // ensure that the raw values match + Assert.Equal((ulong)0, copy.RawValue); + + // test GuildPermissions.All + ulong sumOfAllGuildPermissions = 0; + foreach(var v in Enum.GetValues(typeof(GuildPermission))) + { + sumOfAllGuildPermissions |= (ulong)v; + } + + // assert that the raw values match + Assert.Equal(sumOfAllGuildPermissions, GuildPermissions.All.RawValue); + Assert.Equal((ulong)0, GuildPermissions.None.RawValue); + + // assert that GuildPermissions.All contains the same number of permissions as the + // GuildPermissions enum + Assert.Equal(Enum.GetValues(typeof(GuildPermission)).Length, GuildPermissions.All.ToList().Count); + + // assert that webhook has the same raw value + ulong webHookPermissions = (ulong)( + GuildPermission.SendMessages | GuildPermission.SendTTSMessages | GuildPermission.EmbedLinks | + GuildPermission.AttachFiles); + Assert.Equal(webHookPermissions, GuildPermissions.Webhook.RawValue); + } + + [Fact] + public async Task TestGuildPermissionModify() + { + var perm = new GuildPermissions(); + + // tests each of the parameters of Modify one by one + + // test modify with each of the parameters + // test initially false state + Assert.False(perm.CreateInstantInvite); + + // ensure that when we modify it the parameter works + perm = perm.Modify(createInstantInvite: true); + Assert.True(perm.CreateInstantInvite); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.CreateInstantInvite); + + // set it false again, then move on to the next permission + perm = perm.Modify(createInstantInvite: false); + Assert.False(perm.CreateInstantInvite); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(kickMembers: true); + Assert.True(perm.KickMembers); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.KickMembers); + + perm = perm.Modify(kickMembers: false); + Assert.False(perm.KickMembers); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(banMembers: true); + Assert.True(perm.BanMembers); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.BanMembers); + + perm = perm.Modify(banMembers: false); + Assert.False(perm.BanMembers); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(administrator: true); + Assert.True(perm.Administrator); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.Administrator); + + perm = perm.Modify(administrator: false); + Assert.False(perm.Administrator); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(manageChannels: true); + Assert.True(perm.ManageChannels); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageChannels); + + perm = perm.Modify(manageChannels: false); + Assert.False(perm.ManageChannels); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(manageGuild: true); + Assert.True(perm.ManageGuild); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageGuild); + + perm = perm.Modify(manageGuild: false); + Assert.False(perm.ManageGuild); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + + // individual permission test + perm = perm.Modify(addReactions: true); + Assert.True(perm.AddReactions); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.AddReactions); + + perm = perm.Modify(addReactions: false); + Assert.False(perm.AddReactions); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + + // individual permission test + perm = perm.Modify(viewAuditLog: true); + Assert.True(perm.ViewAuditLog); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ViewAuditLog); + + perm = perm.Modify(viewAuditLog: false); + Assert.False(perm.ViewAuditLog); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + + // individual permission test + perm = perm.Modify(readMessages: true); + Assert.True(perm.ReadMessages); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessages); + + perm = perm.Modify(readMessages: false); + Assert.False(perm.ReadMessages); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + + // individual permission test + perm = perm.Modify(sendMessages: true); + Assert.True(perm.SendMessages); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendMessages); + + perm = perm.Modify(sendMessages: false); + Assert.False(perm.SendMessages); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(embedLinks: true); + Assert.True(perm.EmbedLinks); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.EmbedLinks); + + perm = perm.Modify(embedLinks: false); + Assert.False(perm.EmbedLinks); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(attachFiles: true); + Assert.True(perm.AttachFiles); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.AttachFiles); + + perm = perm.Modify(attachFiles: false); + Assert.False(perm.AttachFiles); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(readMessageHistory: true); + Assert.True(perm.ReadMessageHistory); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessageHistory); + + perm = perm.Modify(readMessageHistory: false); + Assert.False(perm.ReadMessageHistory); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(mentionEveryone: true); + Assert.True(perm.MentionEveryone); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.MentionEveryone); + + perm = perm.Modify(mentionEveryone: false); + Assert.False(perm.MentionEveryone); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(useExternalEmojis: true); + Assert.True(perm.UseExternalEmojis); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseExternalEmojis); + + perm = perm.Modify(useExternalEmojis: false); + Assert.False(perm.UseExternalEmojis); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(connect: true); + Assert.True(perm.Connect); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.Connect); + + perm = perm.Modify(connect: false); + Assert.False(perm.Connect); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(speak: true); + Assert.True(perm.Speak); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.Speak); + + perm = perm.Modify(speak: false); + Assert.False(perm.Speak); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(muteMembers: true); + Assert.True(perm.MuteMembers); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.MuteMembers); + + perm = perm.Modify(muteMembers: false); + Assert.False(perm.MuteMembers); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(deafenMembers: true); + Assert.True(perm.DeafenMembers); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.DeafenMembers); + + perm = perm.Modify(deafenMembers: false); + Assert.False(perm.DeafenMembers); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(moveMembers: true); + Assert.True(perm.MoveMembers); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.MoveMembers); + + perm = perm.Modify(moveMembers: false); + Assert.False(perm.MoveMembers); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(useVoiceActivation: true); + Assert.True(perm.UseVAD); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseVAD); + + perm = perm.Modify(useVoiceActivation: false); + Assert.False(perm.UseVAD); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(changeNickname: true); + Assert.True(perm.ChangeNickname); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ChangeNickname); + + perm = perm.Modify(changeNickname: false); + Assert.False(perm.ChangeNickname); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(manageNicknames: true); + Assert.True(perm.ManageNicknames); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageNicknames); + + perm = perm.Modify(manageNicknames: false); + Assert.False(perm.ManageNicknames); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(manageRoles: true); + Assert.True(perm.ManageRoles); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageRoles); + + perm = perm.Modify(manageRoles: false); + Assert.False(perm.ManageRoles); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(manageWebhooks: true); + Assert.True(perm.ManageWebhooks); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageWebhooks); + + perm = perm.Modify(manageWebhooks: false); + Assert.False(perm.ManageWebhooks); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + // individual permission test + perm = perm.Modify(manageEmojis: true); + Assert.True(perm.ManageEmojis); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageEmojis); + + perm = perm.Modify(manageEmojis: false); + Assert.False(perm.ManageEmojis); + Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); + + } + + } +} From b4bf046ad483d1daa6f5a8b73f249602ca489e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Brawa=C5=84ski?= Date: Mon, 20 Nov 2017 21:02:42 +0100 Subject: [PATCH 22/27] Implemented emoji endpoints (#835) * Implemented emoji endpoints. * Fixed: now using API entities for REST client. * Removed emoji listing endpoint, as per @foxbot's request. --- .../Entities/Emotes/EmoteProperties.cs | 10 +++++ .../Entities/Guilds/IGuild.cs | 9 ++++ .../API/Rest/CreateGuildEmoteParams.cs | 16 +++++++ .../API/Rest/ModifyGuildEmoteParams.cs | 14 ++++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 44 +++++++++++++++++++ .../Entities/Guilds/GuildHelper.cs | 41 +++++++++++++++++ .../Entities/Guilds/RestGuild.cs | 10 +++++ .../Entities/Guilds/SocketGuild.cs | 10 +++++ 8 files changed, 154 insertions(+) create mode 100644 src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs create mode 100644 src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs diff --git a/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs b/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs new file mode 100644 index 000000000..be24d306c --- /dev/null +++ b/src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Discord +{ + public class EmoteProperties + { + public Optional Name { get; set; } + public Optional> Roles { get; set; } + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 3ded9e038..6b2d24cc6 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -117,5 +117,14 @@ namespace Discord Task DownloadUsersAsync(); /// Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); + + /// Gets a specific emote from this guild. + Task GetEmoteAsync(ulong id, RequestOptions options = null); + /// Creates a new emote in this guild. + Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null); + /// Modifies an existing emote in this guild. + Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null); + /// Deletes an existing emote from this guild. + Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); } } \ No newline at end of file diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs new file mode 100644 index 000000000..308199820 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs @@ -0,0 +1,16 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + internal class CreateGuildEmoteParams + { + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("image")] + public Image Image { get; set; } + [JsonProperty("roles")] + public Optional RoleIds { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs new file mode 100644 index 000000000..a2295dd5d --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs @@ -0,0 +1,14 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + internal class ModifyGuildEmoteParams + { + [JsonProperty("name")] + public Optional Name { get; set; } + [JsonProperty("roles")] + public Optional RoleIds { get; set; } + } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 6d551aa95..4e65b19d2 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1066,6 +1066,50 @@ namespace Discord.API return await SendJsonAsync>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false); } + //Guild emoji + public async Task GetGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options); + } + + public async Task CreateGuildEmoteAsync(ulong guildId, Rest.CreateGuildEmoteParams args, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.NotNull(args.Image.Stream, nameof(args.Image)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + return await SendJsonAsync("POST", () => $"guilds/{guildId}/emojis", args, ids, options: options); + } + + public async Task ModifyGuildEmoteAsync(ulong guildId, ulong emoteId, ModifyGuildEmoteParams args, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); + Preconditions.NotNull(args, nameof(args)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/emojis/{emoteId}", args, ids, options: options); + } + + public async Task DeleteGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + await SendAsync("DELETE", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options); + } + //Users public async Task GetUserAsync(ulong userId, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 2fa29928c..58b7ed7f9 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -253,5 +253,46 @@ namespace Discord.Rest model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args, options).ConfigureAwait(false); return model.Pruned; } + + //Emotes + public static async Task GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) + { + var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options); + return emote.ToEntity(); + } + public static async Task CreateEmoteAsync(IGuild guild, BaseDiscordClient client, string name, Image image, Optional> roles, + RequestOptions options) + { + var apiargs = new CreateGuildEmoteParams + { + Name = name, + Image = image.ToModel() + }; + if (roles.IsSpecified) + apiargs.RoleIds = roles.Value?.Select(xr => xr.Id)?.ToArray(); + + var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options); + return emote.ToEntity(); + } + public static async Task ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action func, + RequestOptions options) + { + if (func == null) throw new ArgumentNullException(nameof(func)); + + var props = new EmoteProperties(); + func(props); + + var apiargs = new ModifyGuildEmoteParams + { + Name = props.Name + }; + if (props.Roles.IsSpecified) + apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id)?.ToArray(); + + var emote = await client.ApiClient.ModifyGuildEmoteAsync(guild.Id, id, apiargs, options); + return emote.ToEntity(); + } + public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) + => client.ApiClient.DeleteGuildEmoteAsync(guild.Id, id, options); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index aee305951..de4b89e39 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -260,6 +260,16 @@ namespace Discord.Rest public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; + //Emotes + public Task GetEmoteAsync(ulong id, RequestOptions options = null) + => GuildHelper.GetEmoteAsync(this, Discord, id, options); + public Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null) + => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); + public Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null) + => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); + public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) + => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); + //IGuild bool IGuild.Available => Available; IAudioClient IGuild.AudioClient => null; diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 6001e4799..b639a9cf7 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -433,6 +433,16 @@ namespace Discord.WebSocket _downloaderPromise.TrySetResultAsync(true); } + //Emotes + public Task GetEmoteAsync(ulong id, RequestOptions options = null) + => GuildHelper.GetEmoteAsync(this, Discord, id, options); + public Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null) + => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); + public Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null) + => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); + public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) + => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); + //Voice States internal async Task AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model) { From e5dfb6c3e54f09e51223b5706ce96ddec97c8291 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 21 Nov 2017 16:33:46 -0500 Subject: [PATCH 23/27] Fix null channel being passed in RequirePermission preconditions (#886) * Fix null channel being passed in RequirePermission preconditions * c#7 pattern matching --- .../Preconditions/RequireBotPermissionAttribute.cs | 6 ++---- .../Preconditions/RequireUserPermissionAttribute.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs index 6be142a45..104252799 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -57,13 +57,11 @@ namespace Discord.Commands if (ChannelPermission.HasValue) { - var guildChannel = context.Channel as IGuildChannel; - ChannelPermissions perms; - if (guildChannel != null) + if (context.Channel is IGuildChannel guildChannel) perms = guildUser.GetPermissions(guildChannel); else - perms = ChannelPermissions.All(guildChannel); + perms = ChannelPermissions.All(context.Channel); if (!perms.Has(ChannelPermission.Value)) return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}"); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index 0179aa0ac..14121f35b 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -56,13 +56,11 @@ namespace Discord.Commands if (ChannelPermission.HasValue) { - var guildChannel = context.Channel as IGuildChannel; - ChannelPermissions perms; - if (guildChannel != null) + if (context.Channel is IGuildChannel guildChannel) perms = guildUser.GetPermissions(guildChannel); else - perms = ChannelPermissions.All(guildChannel); + perms = ChannelPermissions.All(context.Channel); if (!perms.Has(ChannelPermission.Value)) return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}")); From c461201fa5ea617d3edb0ded41d2e75e9352779a Mon Sep 17 00:00:00 2001 From: Christopher F Date: Wed, 22 Nov 2017 19:39:26 -0500 Subject: [PATCH 24/27] Fix async warnings --- test/Discord.Net.Tests/Tests.ChannelPermissions.cs | 6 +++--- test/Discord.Net.Tests/Tests.GuildPermissions.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs index c5b22e277..92234e88b 100644 --- a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs +++ b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs @@ -7,7 +7,7 @@ namespace Discord public partial class Tests { [Fact] - public async Task TestChannelPermission() + public void TestChannelPermission() { var perm = new ChannelPermissions(); @@ -84,7 +84,7 @@ namespace Discord Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue); } - public async Task TestChannelPermissionModify() + public void TestChannelPermissionModify() { // test channel permission modify @@ -314,7 +314,7 @@ namespace Discord } [Fact] - public async Task TestChannelTypeResolution() + public void TestChannelTypeResolution() { ITextChannel someChannel = null; // null channels will throw exception diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs index a79706b9c..dc51600cf 100644 --- a/test/Discord.Net.Tests/Tests.GuildPermissions.cs +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -7,7 +7,7 @@ namespace Discord public partial class Tests { [Fact] - public async Task TestGuildPermission() + public void TestGuildPermission() { // Test Guild Permission Constructors var perm = new GuildPermissions(); @@ -49,7 +49,7 @@ namespace Discord } [Fact] - public async Task TestGuildPermissionModify() + public void TestGuildPermissionModify() { var perm = new GuildPermissions(); From bbad052ec7b5dc89e43825064aaf7d0e31bdbbf3 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Wed, 22 Nov 2017 20:09:07 -0500 Subject: [PATCH 25/27] Update invite link thank you @SinisterRectus :slight_smile: --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b58d4579..bd0ef20c7 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![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) -[![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/0SBTUU1wZTVjAMPx) +[![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/jkrBmQR) An unofficial .NET API Wrapper for the Discord client (http://discordapp.com). -Check out the [documentation](https://discord.foxbot.me/docs/) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx). +Check out the [documentation](https://discord.foxbot.me/docs/) or join the [Discord API Chat](https://discord.gg/jkrBmQR). ## Installation ### Stable (NuGet) From 39b5d0e74cc416a0d84d741c1d5585155c1d2075 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Wed, 22 Nov 2017 20:13:10 -0500 Subject: [PATCH 26/27] Bumped version to 2.0.0-beta --- Discord.Net.targets | 4 ++-- appveyor.yml | 2 +- src/Discord.Net/Discord.Net.nuspec | 38 +++++++++++++++--------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 95eccd790..3f623c619 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,7 +1,7 @@ - 2.0.0-alpha - + 2.0.0 + beta RogueException discord;discordapp https://github.com/RogueException/Discord.Net diff --git a/appveyor.yml b/appveyor.yml index d94e2ad68..3bf70c09c 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="-$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 309532615..f904f4126 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 2.0.0-alpha$suffix$ + 2.0.0-beta$suffix$ Discord.Net Discord.Net Contributors RogueException @@ -13,28 +13,28 @@ false - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + From 678a7238e6172ae539a5746fb5bc1b2c734258a0 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Thu, 7 Dec 2017 16:47:01 -0500 Subject: [PATCH 27/27] Allow users to opt-in to proxies (#888) * Allow users to opt-in to proxies * Allow opting in to proxies on the WebSocket --- src/Discord.Net.Rest/Net/DefaultRestClient.cs | 4 ++-- .../Net/DefaultRestClientProvider.cs | 23 +++++++++++------- .../Net/DefaultWebSocketClient.cs | 7 ++++-- .../Net/DefaultWebSocketClientProvider.cs | 24 ++++++++++++------- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index a54107829..637099fd6 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -22,7 +22,7 @@ namespace Discord.Net.Rest private CancellationToken _cancelToken; private bool _isDisposed; - public DefaultRestClient(string baseUrl) + public DefaultRestClient(string baseUrl, bool useProxy = false) { _baseUrl = baseUrl; @@ -30,7 +30,7 @@ namespace Discord.Net.Rest { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, UseCookies = false, - UseProxy = false + UseProxy = useProxy, }); SetHeader("accept-encoding", "gzip, deflate"); diff --git a/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs b/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs index 311a53562..e0e776549 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs @@ -4,16 +4,21 @@ namespace Discord.Net.Rest { public static class DefaultRestClientProvider { - public static readonly RestClientProvider Instance = url => + public static readonly RestClientProvider Instance = Create(); + + public static RestClientProvider Create(bool useProxy = false) { - try - { - return new DefaultRestClient(url); - } - catch (PlatformNotSupportedException ex) + return url => { - throw new PlatformNotSupportedException("The default RestClientProvider is not supported on this platform.", ex); - } - }; + try + { + return new DefaultRestClient(url, useProxy); + } + catch (PlatformNotSupportedException ex) + { + throw new PlatformNotSupportedException("The default RestClientProvider is not supported on this platform.", ex); + } + }; + } } } diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs index 282ae210a..a250acec9 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Net; using System.Net.WebSockets; using System.Text; using System.Threading; @@ -23,18 +24,20 @@ namespace Discord.Net.WebSockets private readonly SemaphoreSlim _lock; private readonly Dictionary _headers; private ClientWebSocket _client; + private IWebProxy _proxy; private Task _task; private CancellationTokenSource _cancelTokenSource; private CancellationToken _cancelToken, _parentToken; private bool _isDisposed, _isDisconnecting; - public DefaultWebSocketClient() + public DefaultWebSocketClient(IWebProxy proxy = null) { _lock = new SemaphoreSlim(1, 1); _cancelTokenSource = new CancellationTokenSource(); _cancelToken = CancellationToken.None; _parentToken = CancellationToken.None; _headers = new Dictionary(); + _proxy = proxy; } private void Dispose(bool disposing) { @@ -70,7 +73,7 @@ namespace Discord.Net.WebSockets _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; _client = new ClientWebSocket(); - _client.Options.Proxy = null; + _client.Options.Proxy = _proxy; _client.Options.KeepAliveInterval = TimeSpan.Zero; foreach (var header in _headers) { diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs index 04b3f8388..68bd67c5b 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs @@ -1,21 +1,27 @@ using System; +using System.Net; namespace Discord.Net.WebSockets { public static class DefaultWebSocketProvider { #if DEFAULTWEBSOCKET - public static readonly WebSocketProvider Instance = () => + public static readonly WebSocketProvider Instance = Create(); + + public static WebSocketProvider Create(IWebProxy proxy = null) { - try - { - return new DefaultWebSocketClient(); - } - catch (PlatformNotSupportedException ex) + return () => { - throw new PlatformNotSupportedException("The default WebSocketProvider is not supported on this platform.", ex); - } - }; + try + { + return new DefaultWebSocketClient(proxy); + } + catch (PlatformNotSupportedException ex) + { + throw new PlatformNotSupportedException("The default WebSocketProvider is not supported on this platform.", ex); + } + }; + } #else public static readonly WebSocketProvider Instance = () => {