diff --git a/src/Discord.Net.API/Rest/GetBotGatewayResponse.cs b/src/Discord.Net.API/Rest/GetBotGatewayResponse.cs
new file mode 100644
index 000000000..d6d2bed00
--- /dev/null
+++ b/src/Discord.Net.API/Rest/GetBotGatewayResponse.cs
@@ -0,0 +1,13 @@
+#pragma warning disable CS1591
+using Newtonsoft.Json;
+
+namespace Discord.API.Rest
+{
+ public class GetBotGatewayResponse
+ {
+ [JsonProperty("url")]
+ public string Url { get; set; }
+ [JsonProperty("shards")]
+ public int Shards { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs
index bb7077472..78a5b0e1e 100644
--- a/src/Discord.Net.Core/DiscordConfig.cs
+++ b/src/Discord.Net.Core/DiscordConfig.cs
@@ -24,5 +24,8 @@ namespace Discord
/// Gets or sets the minimum log level severity that will be sent to the LogMessage event.
public LogSeverity LogLevel { get; set; } = LogSeverity.Info;
+
+ /// Gets or sets whether the initial log entry should be printed.
+ internal bool DisplayInitialLog { get; set; } = true;
}
}
diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs
index 1a0348d34..c36031539 100644
--- a/src/Discord.Net.Rest/BaseDiscordClient.cs
+++ b/src/Discord.Net.Rest/BaseDiscordClient.cs
@@ -11,7 +11,7 @@ namespace Discord.Rest
public abstract class BaseDiscordClient : IDiscordClient
{
public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } }
- private readonly AsyncEvent> _logEvent = new AsyncEvent>();
+ internal readonly AsyncEvent> _logEvent = new AsyncEvent>();
public event Func LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } }
private readonly AsyncEvent> _loggedInEvent = new AsyncEvent>();
@@ -38,7 +38,7 @@ namespace Discord.Rest
_connectionLock = new SemaphoreSlim(1, 1);
_restLogger = LogManager.CreateLogger("Rest");
_queueLogger = LogManager.CreateLogger("Queue");
- _isFirstLogin = true;
+ _isFirstLogin = config.DisplayInitialLog;
ApiClient.RequestQueue.RateLimitTriggered += async (id, info) =>
{
diff --git a/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs
new file mode 100644
index 000000000..627b9b390
--- /dev/null
+++ b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs
@@ -0,0 +1,31 @@
+using Discord.WebSocket;
+
+namespace Discord.Commands
+{
+ public class ShardedCommandContext : ICommandContext
+ {
+ public DiscordShardedClient Client { get; }
+ public SocketGuild Guild { get; }
+ public ISocketMessageChannel Channel { get; }
+ public SocketUser User { get; }
+ public SocketUserMessage Message { get; }
+
+ public bool IsPrivate => Channel is IPrivateChannel;
+
+ public ShardedCommandContext(DiscordShardedClient client, SocketUserMessage msg)
+ {
+ Client = client;
+ Guild = (msg.Channel as SocketGuildChannel)?.Guild;
+ Channel = msg.Channel;
+ User = msg.Author;
+ Message = msg;
+ }
+
+ //ICommandContext
+ IDiscordClient ICommandContext.Client => Client;
+ IGuild ICommandContext.Guild => Guild;
+ IMessageChannel ICommandContext.Channel => Channel;
+ IUser ICommandContext.User => User;
+ IUserMessage ICommandContext.Message => Message;
+ }
+}
diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs
new file mode 100644
index 000000000..449d30599
--- /dev/null
+++ b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Discord.WebSocket
+{
+ //TODO: Add event docstrings
+ public partial class DiscordShardedClient
+ {
+ //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, Task> MessageDeleted
+ {
+ add { _messageDeletedEvent.Add(value); }
+ remove { _messageDeletedEvent.Remove(value); }
+ }
+ private readonly AsyncEvent, Task>> _messageDeletedEvent = new AsyncEvent, Task>>();
+ public event Func, SocketMessage, Task> MessageUpdated
+ {
+ add { _messageUpdatedEvent.Add(value); }
+ remove { _messageUpdatedEvent.Remove(value); }
+ }
+ private readonly AsyncEvent, SocketMessage, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, Task>>();
+ public event Func, SocketReaction, Task> ReactionAdded
+ {
+ add { _reactionAddedEvent.Add(value); }
+ remove { _reactionAddedEvent.Remove(value); }
+ }
+ private readonly AsyncEvent, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, SocketReaction, Task>>();
+ public event Func, SocketReaction, Task> ReactionRemoved
+ {
+ add { _reactionRemovedEvent.Add(value); }
+ remove { _reactionRemovedEvent.Remove(value); }
+ }
+ private readonly AsyncEvent, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, SocketReaction, Task>>();
+ public event Func, Task> ReactionsCleared
+ {
+ add { _reactionsClearedEvent.Add(value); }
+ remove { _reactionsClearedEvent.Remove(value); }
+ }
+ private readonly AsyncEvent, Task>> _reactionsClearedEvent = new AsyncEvent, 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
+ {
+ add { _recipientRemovedEvent.Add(value); }
+ remove { _recipientRemovedEvent.Remove(value); }
+ }
+ private readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>();
+ }
+}
diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
new file mode 100644
index 000000000..403f1e239
--- /dev/null
+++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
@@ -0,0 +1,364 @@
+using Discord.API;
+using Discord.Rest;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Discord.WebSocket
+{
+ public partial class DiscordShardedClient : BaseDiscordClient, IDiscordClient
+ {
+ private readonly DiscordSocketConfig _baseConfig;
+ private int[] _shardIds;
+ private Dictionary _shardIdsToIndex;
+ private DiscordSocketClient[] _shards;
+ private int _totalShards;
+ private bool _automaticShards;
+
+ /// Gets the estimated round-trip latency, in milliseconds, to the gateway server.
+ public int Latency { get; private set; }
+ internal UserStatus Status => _shards[0].Status;
+ internal Game? Game => _shards[0].Game;
+
+ public 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 IReadOnlyCollection Shards => _shards;
+ public IReadOnlyCollection VoiceRegions => _shards[0].VoiceRegions;
+
+ /// Creates a new REST/WebSocket discord client.
+ public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { }
+ /// Creates a new REST/WebSocket discord client.
+ public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { }
+ /// Creates a new REST/WebSocket discord client.
+ public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { }
+ /// Creates a new REST/WebSocket discord client.
+ public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) { }
+ private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client)
+ : base(config, client)
+ {
+ if (config.ShardId != null)
+ throw new ArgumentException($"{nameof(config.ShardId)} must not be set.");
+ if (ids != null && config.TotalShards == null)
+ throw new ArgumentException($"Custom ids are not supported when {nameof(config.TotalShards)} is not specified.");
+
+ _shardIdsToIndex = new Dictionary();
+ config.DisplayInitialLog = false;
+ _baseConfig = config;
+
+ if (config.TotalShards == null)
+ _automaticShards = true;
+ else
+ {
+ _totalShards = config.TotalShards.Value;
+ _shardIds = ids ?? Enumerable.Range(0, _totalShards).ToArray();
+ _shards = new DiscordSocketClient[_shardIds.Length];
+ for (int i = 0; i < _shardIds.Length; i++)
+ {
+ _shardIdsToIndex.Add(_shardIds[i], i);
+ var newConfig = config.Clone();
+ newConfig.ShardId = _shardIds[i];
+ _shards[i] = new DiscordSocketClient(newConfig);
+ RegisterEvents(_shards[i]);
+ }
+ }
+ }
+ private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config)
+ => new API.DiscordSocketApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, config.WebSocketProvider);
+
+ protected override async Task OnLoginAsync(TokenType tokenType, string token)
+ {
+ if (_automaticShards)
+ {
+ var response = await ApiClient.GetBotGatewayAsync().ConfigureAwait(false);
+ _shardIds = Enumerable.Range(0, response.Shards).ToArray();
+ _totalShards = _shardIds.Length;
+ _shards = new DiscordSocketClient[_shardIds.Length];
+ for (int i = 0; i < _shardIds.Length; i++)
+ {
+ _shardIdsToIndex.Add(_shardIds[i], i);
+ var newConfig = _baseConfig.Clone();
+ newConfig.ShardId = _shardIds[i];
+ newConfig.TotalShards = _totalShards;
+ _shards[i] = new DiscordSocketClient(newConfig);
+ RegisterEvents(_shards[i]);
+ }
+ }
+
+ //Assume threadsafe: already in a connection lock
+ for (int i = 0; i < _shards.Length; i++)
+ await _shards[i].LoginAsync(tokenType, token, false);
+ }
+ protected override async Task OnLogoutAsync()
+ {
+ //Assume threadsafe: already in a connection lock
+ for (int i = 0; i < _shards.Length; i++)
+ await _shards[i].LogoutAsync();
+
+ CurrentUser = null;
+ if (_automaticShards)
+ {
+ _shardIds = new int[0];
+ _shardIdsToIndex.Clear();
+ _totalShards = 0;
+ _shards = null;
+ }
+ }
+
+ ///
+ public async Task ConnectAsync(bool waitForGuilds = true)
+ {
+ await _connectionLock.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ await ConnectInternalAsync(waitForGuilds).ConfigureAwait(false);
+ }
+ catch
+ {
+ await DisconnectInternalAsync().ConfigureAwait(false);
+ throw;
+ }
+ finally { _connectionLock.Release(); }
+ }
+ private async Task ConnectInternalAsync(bool waitForGuilds)
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ {
+ await _shards[i].ConnectAsync(waitForGuilds).ConfigureAwait(false);
+ if (i == 0)
+ CurrentUser = _shards[i].CurrentUser;
+ }
+ }
+ ///
+ public async Task DisconnectAsync()
+ {
+ await _connectionLock.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ await DisconnectInternalAsync().ConfigureAwait(false);
+ }
+ finally { _connectionLock.Release(); }
+ }
+ private async Task DisconnectInternalAsync()
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ await _shards[i].DisconnectAsync();
+ }
+
+ public DiscordSocketClient GetShard(int id)
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ {
+ if (_shards[i].ShardId == id)
+ return _shards[i];
+ }
+ return null;
+ }
+ private int GetShardIdFor(ulong guildId)
+ => (int)((guildId >> 22) % (uint)_totalShards);
+ private int GetShardIdFor(IGuild guild)
+ => GetShardIdFor(guild.Id);
+ private DiscordSocketClient GetShardFor(ulong guildId)
+ {
+ int id = GetShardIdFor(guildId);
+ if (_shardIdsToIndex.TryGetValue(id, out id))
+ return _shards[id];
+ return null;
+ }
+ private DiscordSocketClient GetShardFor(IGuild guild)
+ => GetShardFor(guild.Id);
+
+ ///
+ public async Task GetApplicationInfoAsync()
+ => await _shards[0].GetApplicationInfoAsync().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);
+
+ ///
+ public SocketChannel GetChannel(ulong id)
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ {
+ var channel = _shards[i].GetChannel(id);
+ if (channel != null)
+ return channel;
+ }
+ return null;
+ }
+ private IEnumerable GetPrivateChannels()
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ {
+ foreach (var channel in _shards[i].PrivateChannels)
+ yield return channel;
+ }
+ }
+ private int GetPrivateChannelCount()
+ {
+ int result = 0;
+ for (int i = 0; i < _shards.Length; i++)
+ result += _shards[i].PrivateChannels.Count;
+ return result;
+ }
+
+ ///
+ public Task> GetConnectionsAsync()
+ => ClientHelper.GetConnectionsAsync(this);
+
+ private IEnumerable GetGuilds()
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ {
+ foreach (var guild in _shards[i].Guilds)
+ yield return guild;
+ }
+ }
+ private int GetGuildCount()
+ {
+ int result = 0;
+ for (int i = 0; i < _shards.Length; i++)
+ result += _shards[i].Guilds.Count;
+ return result;
+ }
+
+ ///
+ public Task GetInviteAsync(string inviteId)
+ => ClientHelper.GetInviteAsync(this, inviteId);
+
+ ///
+ public SocketUser GetUser(ulong id)
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ {
+ var user = _shards[i].GetUser(id);
+ if (user != null)
+ return user;
+ }
+ return null;
+ }
+ ///
+ public SocketUser GetUser(string username, string discriminator)
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ {
+ var user = _shards[i].GetUser(username, discriminator);
+ if (user != null)
+ return user;
+ }
+ return null;
+ }
+
+ ///
+ public RestVoiceRegion GetVoiceRegion(string id)
+ => _shards[0].GetVoiceRegion(id);
+
+ /// Downloads the users list for all large guilds.
+ public async Task DownloadAllUsersAsync()
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ await _shards[i].DownloadAllUsersAsync().ConfigureAwait(false);
+ }
+ /// Downloads the users list for the provided guilds, if they don't have a complete list.
+ public async Task DownloadUsersAsync(IEnumerable guilds)
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ {
+ int id = _shardIds[i];
+ var arr = guilds.Where(x => GetShardIdFor(x) == id).ToArray();
+ if (arr.Length > 0)
+ await _shards[i].DownloadUsersAsync(arr);
+ }
+ }
+
+ public 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)
+ {
+ for (int i = 0; i < _shards.Length; i++)
+ await _shards[i].SetGameAsync(name, streamUrl, streamType).ConfigureAwait(false);
+ }
+
+ private void RegisterEvents(DiscordSocketClient client)
+ {
+ client.Log += (msg) => _logEvent.InvokeAsync(msg);
+ client.ChannelCreated += (channel) => _channelCreatedEvent.InvokeAsync(channel);
+ client.ChannelDestroyed += (channel) => _channelDestroyedEvent.InvokeAsync(channel);
+ client.ChannelUpdated += (oldChannel, newChannel) => _channelUpdatedEvent.InvokeAsync(oldChannel, newChannel);
+
+ client.MessageReceived += (msg) => _messageReceivedEvent.InvokeAsync(msg);
+ client.MessageDeleted += (id, msg) => _messageDeletedEvent.InvokeAsync(id, msg);
+ client.MessageUpdated += (oldMsg, newMsg) => _messageUpdatedEvent.InvokeAsync(oldMsg, newMsg);
+ client.ReactionAdded += (id, msg, reaction) => _reactionAddedEvent.InvokeAsync(id, msg, reaction);
+ client.ReactionRemoved += (id, msg, reaction) => _reactionRemovedEvent.InvokeAsync(id, msg, reaction);
+ client.ReactionsCleared += (id, msg) => _reactionsClearedEvent.InvokeAsync(id, msg);
+
+ client.RoleCreated += (role) => _roleCreatedEvent.InvokeAsync(role);
+ client.RoleDeleted += (role) => _roleDeletedEvent.InvokeAsync(role);
+ client.RoleUpdated += (oldRole, newRole) => _roleUpdatedEvent.InvokeAsync(oldRole, newRole);
+
+ client.JoinedGuild += (guild) => _joinedGuildEvent.InvokeAsync(guild);
+ client.LeftGuild += (guild) => _leftGuildEvent.InvokeAsync(guild);
+ client.GuildAvailable += (guild) => _guildAvailableEvent.InvokeAsync(guild);
+ client.GuildUnavailable += (guild) => _guildUnavailableEvent.InvokeAsync(guild);
+ client.GuildMembersDownloaded += (guild) => _guildMembersDownloadedEvent.InvokeAsync(guild);
+ client.GuildUpdated += (oldGuild, newGuild) => _guildUpdatedEvent.InvokeAsync(oldGuild, newGuild);
+
+ client.UserJoined += (user) => _userJoinedEvent.InvokeAsync(user);
+ client.UserLeft += (user) => _userLeftEvent.InvokeAsync(user);
+ client.UserBanned += (user, guild) => _userBannedEvent.InvokeAsync(user, guild);
+ client.UserUnbanned += (user, guild) => _userUnbannedEvent.InvokeAsync(user, guild);
+ client.UserUpdated += (oldUser, newUser) => _userUpdatedEvent.InvokeAsync(oldUser, newUser);
+ client.UserPresenceUpdated += (guild, user, oldPresence, newPresence) => _userPresenceUpdatedEvent.InvokeAsync(guild, user, oldPresence, newPresence);
+ client.UserVoiceStateUpdated += (user, oldVoiceState, newVoiceState) => _userVoiceStateUpdatedEvent.InvokeAsync(user, oldVoiceState, newVoiceState);
+ client.CurrentUserUpdated += (oldUser, newUser) => _selfUpdatedEvent.InvokeAsync(oldUser, newUser);
+ client.UserIsTyping += (oldUser, newUser) => _userIsTypingEvent.InvokeAsync(oldUser, newUser);
+ client.RecipientAdded += (user) => _recipientAddedEvent.InvokeAsync(user);
+ client.RecipientAdded += (user) => _recipientRemovedEvent.InvokeAsync(user);
+ }
+
+ //IDiscordClient
+ Task IDiscordClient.ConnectAsync()
+ => ConnectAsync();
+
+ async Task IDiscordClient.GetApplicationInfoAsync()
+ => await GetApplicationInfoAsync().ConfigureAwait(false);
+
+ Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode)
+ => Task.FromResult(GetChannel(id));
+ Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode)
+ => Task.FromResult>(PrivateChannels);
+
+ async Task> IDiscordClient.GetConnectionsAsync()
+ => await GetConnectionsAsync().ConfigureAwait(false);
+
+ async Task IDiscordClient.GetInviteAsync(string inviteId)
+ => await GetInviteAsync(inviteId).ConfigureAwait(false);
+
+ Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode)
+ => Task.FromResult(GetGuild(id));
+ Task> IDiscordClient.GetGuildsAsync(CacheMode mode)
+ => Task.FromResult>(Guilds);
+ async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon)
+ => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false);
+
+ Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode)
+ => Task.FromResult(GetUser(id));
+ Task IDiscordClient.GetUserAsync(string username, string discriminator)
+ => Task.FromResult(GetUser(username, discriminator));
+
+ Task> IDiscordClient.GetVoiceRegionsAsync()
+ => Task.FromResult>(VoiceRegions);
+ Task IDiscordClient.GetVoiceRegionAsync(string id)
+ => Task.FromResult(GetVoiceRegion(id));
+ }
+}
diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
index 1ab67d724..bcc8e40b7 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
@@ -178,6 +178,11 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
return await SendAsync("GET", () => "gateway", new BucketIds(), options: options).ConfigureAwait(false);
}
+ public async Task GetBotGatewayAsync(RequestOptions options = null)
+ {
+ options = RequestOptions.CreateOrClone(options);
+ return await SendAsync("GET", () => "gateway/bot", new BucketIds(), options: options).ConfigureAwait(false);
+ }
public async Task SendIdentifyAsync(int largeThreshold = 100, bool useCompression = true, int shardID = 0, int totalShards = 1, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index c0bbb750b..a93ba5fcf 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -3,7 +3,6 @@ using Discord.API.Gateway;
using Discord.Audio;
using Discord.Logging;
using Discord.Net.Converters;
-using Discord.Net.Queue;
using Discord.Net.Udp;
using Discord.Net.WebSockets;
using Discord.Rest;
@@ -65,6 +64,7 @@ namespace Discord.WebSocket
public new SocketSelfUser CurrentUser { get { return base.CurrentUser as SocketSelfUser; } private set { base.CurrentUser = value; } }
public IReadOnlyCollection Guilds => State.Guilds;
public IReadOnlyCollection PrivateChannels => State.PrivateChannels;
+ public IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection();
/// Creates a new REST/WebSocket discord client.
public DiscordSocketClient() : this(new DiscordSocketConfig()) { }
@@ -73,8 +73,8 @@ namespace Discord.WebSocket
private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client)
: base(config, client)
{
- ShardId = config.ShardId;
- TotalShards = config.TotalShards;
+ ShardId = config.ShardId ?? 0;
+ TotalShards = config.TotalShards ?? 1;
MessageCacheSize = config.MessageCacheSize;
LargeThreshold = config.LargeThreshold;
AudioMode = config.AudioMode;
@@ -85,7 +85,7 @@ namespace Discord.WebSocket
State = new ClientState(0, 0);
_nextAudioId = 1;
- _gatewayLogger = LogManager.CreateLogger("Gateway");
+ _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : "Shard #" + ShardId);
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
_serializer.Error += (s, e) =>
@@ -206,7 +206,7 @@ namespace Discord.WebSocket
await _connectTask.Task.ConfigureAwait(false);
await _gatewayLogger.DebugAsync("Sending Status").ConfigureAwait(false);
- await SendStatus().ConfigureAwait(false);
+ await SendStatusAsync().ConfigureAwait(false);
await _gatewayLogger.DebugAsync("Raising Event").ConfigureAwait(false);
if (!isReconnecting)
@@ -424,11 +424,7 @@ namespace Discord.WebSocket
public Task DownloadAllUsersAsync()
=> DownloadUsersAsync(State.Guilds.Where(x => !x.HasAllMembers));
/// Downloads the users list for the provided guilds, if they don't have a complete list.
- public Task DownloadUsersAsync(IEnumerable guilds)
- => DownloadUsersAsync(guilds.Select(x => x as SocketGuild).Where(x => x != null));
- public Task DownloadUsersAsync(params IGuild[] guilds)
- => DownloadUsersAsync(guilds.Select(x => x as SocketGuild).Where(x => x != null));
- private async Task DownloadUsersAsync(IEnumerable guilds)
+ public async Task DownloadUsersAsync(IEnumerable guilds)
{
var cachedGuilds = guilds.ToImmutableArray();
if (cachedGuilds.Length == 0) return;
@@ -474,25 +470,25 @@ namespace Discord.WebSocket
}
}
- public async Task SetStatus(UserStatus status)
+ public async Task SetStatusAsync(UserStatus status)
{
Status = status;
if (status == UserStatus.AFK)
_statusSince = DateTimeOffset.UtcNow;
else
_statusSince = null;
- await SendStatus().ConfigureAwait(false);
+ await SendStatusAsync().ConfigureAwait(false);
}
- public async Task SetGame(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming)
+ public async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming)
{
if (name != null)
Game = new Game(name, streamUrl, streamType);
else
Game = null;
CurrentUser.Presence = new SocketPresence(Status, Game);
- await SendStatus().ConfigureAwait(false);
+ await SendStatusAsync().ConfigureAwait(false);
}
- private async Task SendStatus()
+ private async Task SendStatusAsync()
{
var game = Game;
var status = Status;
@@ -1803,7 +1799,7 @@ namespace Discord.WebSocket
=> Task.FromResult(GetUser(username, discriminator));
Task> IDiscordClient.GetVoiceRegionsAsync()
- => Task.FromResult>(_voiceRegions.ToReadOnlyCollection());
+ => Task.FromResult>(VoiceRegions);
Task IDiscordClient.GetVoiceRegionAsync(string id)
=> Task.FromResult(GetVoiceRegion(id));
}
diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs
index 04001a5aa..17640e25b 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs
@@ -14,9 +14,9 @@ namespace Discord.WebSocket
public int ConnectionTimeout { get; set; } = 30000;
/// Gets or sets the id for this shard. Must be less than TotalShards.
- public int ShardId { get; set; } = 0;
+ public int? ShardId { get; set; } = null;
/// Gets or sets the total number of shards for this application.
- public int TotalShards { get; set; } = 1;
+ public int? TotalShards { get; set; } = null;
/// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely.
public int MessageCacheSize { get; set; } = 0;
@@ -54,5 +54,7 @@ namespace Discord.WebSocket
};
#endif
}
+
+ internal DiscordSocketConfig Clone() => MemberwiseClone() as DiscordSocketConfig;
}
}