| @@ -11,36 +11,52 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> Provides a connection to the DiscordApp service. </summary> | |||||
| public partial class DiscordClient | public partial class DiscordClient | ||||
| { | { | ||||
| public const int ReconnectDelay = 1000; //Time in milliseconds to wait after an unexpected disconnect before retrying | |||||
| public const int FailedReconnectDelay = 10000; //Time in milliseconds to wait after a failed reconnect attempt | |||||
| private DiscordWebSocket _webSocket; | private DiscordWebSocket _webSocket; | ||||
| private bool _isReady; | |||||
| private ManualResetEventSlim _isStopping; | |||||
| /// <summary> Returns the User object for the current logged in user. </summary> | |||||
| public User User { get; private set; } | |||||
| public string UserId { get; private set; } | public string UserId { get; private set; } | ||||
| public User User => _users[UserId]; | |||||
| /// <summary> Returns a collection of all users the client can see across all servers. </summary> | |||||
| /// <remarks> This collection does not guarantee any ordering. </remarks> | |||||
| public IEnumerable<User> Users => _users; | public IEnumerable<User> Users => _users; | ||||
| private AsyncCache<User, API.Models.UserReference> _users; | private AsyncCache<User, API.Models.UserReference> _users; | ||||
| /// <summary> Returns a collection of all servers the client is a member of. </summary> | |||||
| /// <remarks> This collection does not guarantee any ordering. </remarks> | |||||
| public IEnumerable<Server> Servers => _servers; | public IEnumerable<Server> Servers => _servers; | ||||
| private AsyncCache<Server, API.Models.ServerReference> _servers; | private AsyncCache<Server, API.Models.ServerReference> _servers; | ||||
| /// <summary> Returns a collection of all channels the client can see across all servers. </summary> | |||||
| /// <remarks> This collection does not guarantee any ordering. </remarks> | |||||
| public IEnumerable<Channel> Channels => _channels; | public IEnumerable<Channel> Channels => _channels; | ||||
| private AsyncCache<Channel, API.Models.ChannelReference> _channels; | private AsyncCache<Channel, API.Models.ChannelReference> _channels; | ||||
| /// <summary> Returns a collection of all messages the client has in cache. </summary> | |||||
| /// <remarks> This collection does not guarantee any ordering. </remarks> | |||||
| public IEnumerable<Message> Messages => _messages; | public IEnumerable<Message> Messages => _messages; | ||||
| private AsyncCache<Message, API.Models.MessageReference> _messages; | private AsyncCache<Message, API.Models.MessageReference> _messages; | ||||
| /// <summary> Returns a collection of all roles the client can see across all servers. </summary> | |||||
| /// <remarks> This collection does not guarantee any ordering. </remarks> | |||||
| public IEnumerable<Role> Roles => _roles; | public IEnumerable<Role> Roles => _roles; | ||||
| private AsyncCache<Role, API.Models.Role> _roles; | private AsyncCache<Role, API.Models.Role> _roles; | ||||
| private ManualResetEventSlim _isStopping; | |||||
| /// <summary> Returns true if the user has successfully logged in and the websocket connection has been established. </summary> | |||||
| public bool IsConnected => _isReady; | public bool IsConnected => _isReady; | ||||
| private bool _isReady; | |||||
| /// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary> | |||||
| public int ReconnectDelay { get; set; } = 1000; | |||||
| /// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary> | |||||
| public int FailedReconnectDelay { get; set; } = 10000; | |||||
| /// <summary> Initializes a new instance of the DiscordClient class. </summary> | |||||
| public DiscordClient() | public DiscordClient() | ||||
| { | { | ||||
| _isStopping = new ManualResetEventSlim(false); | _isStopping = new ManualResetEventSlim(false); | ||||
| @@ -50,7 +66,7 @@ namespace Discord | |||||
| (server, model) => | (server, model) => | ||||
| { | { | ||||
| server.Name = model.Name; | server.Name = model.Name; | ||||
| if (!server.Channels.Any()) //Assume a default channel exists with the same id as the server. Not sure if this is safe? | |||||
| if (!server.Channels.Any()) //A default channel always exists with the same id as the server. | |||||
| { | { | ||||
| var defaultChannel = new ChannelReference() { Id = server.DefaultChannelId, GuildId = server.Id }; | var defaultChannel = new ChannelReference() { Id = server.DefaultChannelId, GuildId = server.Id }; | ||||
| _channels.Update(defaultChannel.Id, defaultChannel.GuildId, defaultChannel); | _channels.Update(defaultChannel.Id, defaultChannel.GuildId, defaultChannel); | ||||
| @@ -173,8 +189,9 @@ namespace Discord | |||||
| await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, true); | await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, true); | ||||
| break; | break; | ||||
| } | } | ||||
| catch (Exception) | |||||
| catch (Exception ex) | |||||
| { | { | ||||
| RaiseOnDebugMessage($"Reconnect Failed: {ex.Message}"); | |||||
| //Net is down? We can keep trying to reconnect until the user runs Disconnect() | //Net is down? We can keep trying to reconnect until the user runs Disconnect() | ||||
| await Task.Delay(FailedReconnectDelay); | await Task.Delay(FailedReconnectDelay); | ||||
| } | } | ||||
| @@ -194,7 +211,7 @@ namespace Discord | |||||
| _users.Clear(); | _users.Clear(); | ||||
| UserId = data.User.Id; | UserId = data.User.Id; | ||||
| _users.Update(data.User.Id, data.User); | |||||
| User = _users.Update(data.User.Id, data.User); | |||||
| foreach (var server in data.Guilds) | foreach (var server in data.Guilds) | ||||
| _servers.Update(server.Id, server); | _servers.Update(server.Id, server); | ||||
| foreach (var channel in data.PrivateChannels) | foreach (var channel in data.PrivateChannels) | ||||
| @@ -416,101 +433,131 @@ namespace Discord | |||||
| }; | }; | ||||
| _webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message); | _webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message); | ||||
| } | } | ||||
| //Collections | |||||
| /// <summary> Returns the user with the specified id, or null if none was found. </summary> | |||||
| public User GetUser(string id) => _users[id]; | public User GetUser(string id) => _users[id]; | ||||
| public User FindUser(string name) | |||||
| { | |||||
| return _users | |||||
| .Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)) | |||||
| .FirstOrDefault(); | |||||
| } | |||||
| public User FindUser(string name, string discriminator) | |||||
| /// <summary> Returns the user with the specified name and discriminator, or null if none was found. </summary> | |||||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||||
| public User GetUser(string name, string discriminator) | |||||
| { | { | ||||
| if (name.StartsWith("@")) | if (name.StartsWith("@")) | ||||
| name = name.Substring(1); | name = name.Substring(1); | ||||
| return _users | return _users | ||||
| .Where(x => | |||||
| .Where(x => | |||||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && | string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && | ||||
| x.Discriminator == discriminator | x.Discriminator == discriminator | ||||
| ) | |||||
| ) | |||||
| .FirstOrDefault(); | .FirstOrDefault(); | ||||
| } | } | ||||
| public Membership FindMember(string serverId, string name) | |||||
| => FindMember(GetServer(serverId), name); | |||||
| public Membership FindMember(Server server, string name) | |||||
| /// <summary> Returns all users with the specified name. </summary> | |||||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||||
| /*public IEnumerable<User> FindUsers(string name) | |||||
| { | { | ||||
| if (server == null) | |||||
| return null; | |||||
| if (name.StartsWith("<@") && name.EndsWith(">")) | |||||
| if (name.StartsWith("@")) | |||||
| { | { | ||||
| var user = GetUser(name.Substring(2, name.Length - 3)); | |||||
| if (user == null) | |||||
| return null; | |||||
| return server.GetMembership(user.Id); | |||||
| } | |||||
| string name2 = name.Substring(1); | |||||
| return _users.Where(x => | |||||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||||
| } | |||||
| else | |||||
| { | |||||
| return _users.Where(x => | |||||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||||
| } | |||||
| }*/ | |||||
| /// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||||
| public IEnumerable<Membership> FindUsers(string serverId, string name) | |||||
| => FindUsers(GetServer(serverId), name); | |||||
| /// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||||
| public IEnumerable<Membership> FindUsers(Server server, string name) | |||||
| { | |||||
| if (server == null) | |||||
| return new Membership[0]; | |||||
| if (name.StartsWith("@")) | if (name.StartsWith("@")) | ||||
| name = name.Substring(1); | |||||
| return server.Members | |||||
| .Where(x => string.Equals(x.User.Name, name, StringComparison.OrdinalIgnoreCase)) | |||||
| .FirstOrDefault(); | |||||
| { | |||||
| string name2 = name.Substring(1); | |||||
| return server.Members.Where(x => | |||||
| { | |||||
| var user = x.User; | |||||
| return string.Equals(user.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(user.Name, name2, StringComparison.OrdinalIgnoreCase); | |||||
| }); | |||||
| } | |||||
| else | |||||
| { | |||||
| return server.Members.Where(x => | |||||
| string.Equals(x.User.Name, name, StringComparison.OrdinalIgnoreCase)); | |||||
| } | |||||
| } | } | ||||
| /// <summary> Returns the server with the specified id, or null if none was found. </summary> | |||||
| public Server GetServer(string id) => _servers[id]; | public Server GetServer(string id) => _servers[id]; | ||||
| public Server FindServer(string name) | |||||
| /// <summary> Returns all servers with the specified name. </summary> | |||||
| /// <remarks> Search is case-insensitive. </remarks> | |||||
| public IEnumerable<Server> FindServers(string name) | |||||
| { | { | ||||
| return _servers | |||||
| .Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)) | |||||
| .FirstOrDefault(); | |||||
| return _servers.Where(x => | |||||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||||
| } | } | ||||
| /// <summary> Returns the channel with the specified id, or null if none was found. </summary> | |||||
| public Channel GetChannel(string id) => _channels[id]; | public Channel GetChannel(string id) => _channels[id]; | ||||
| public Channel FindChannel(string name) | |||||
| /// <summary> Returns all channels with the specified server and name. </summary> | |||||
| /// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||||
| public IEnumerable<Channel> FindChannels(Server server, string name) | |||||
| => FindChannels(server.Id, name); | |||||
| /// <summary> Returns all channels with the specified server and name. </summary> | |||||
| /// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||||
| public IEnumerable<Channel> FindChannels(string serverId, string name) | |||||
| { | { | ||||
| if (name.StartsWith("<#") && name.EndsWith(">")) | |||||
| return GetChannel(name.Substring(2, name.Length - 3)); | |||||
| if (name.StartsWith("#")) | if (name.StartsWith("#")) | ||||
| name = name.Substring(1); | |||||
| return _channels | |||||
| .Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)) | |||||
| .FirstOrDefault(); | |||||
| } | |||||
| public Channel FindChannel(Server server, string name) | |||||
| => FindChannel(server.Id, name); | |||||
| public Channel FindChannel(string serverId, string name) | |||||
| { | |||||
| if (name.StartsWith("<#") && name.EndsWith(">")) | |||||
| return GetChannel(name.Substring(2, name.Length - 3)); | |||||
| if (name.StartsWith("#")) | |||||
| name = name.Substring(1); | |||||
| return _channels | |||||
| .Where(x => | |||||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && | |||||
| x.ServerId == serverId | |||||
| ) | |||||
| .FirstOrDefault(); | |||||
| { | |||||
| string name2 = name.Substring(1); | |||||
| return _channels.Where(x => x.ServerId == serverId && | |||||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||||
| } | |||||
| else | |||||
| { | |||||
| return _channels.Where(x => x.ServerId == serverId && | |||||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||||
| } | |||||
| } | } | ||||
| /// <summary> Returns the role with the specified id, or null if none was found. </summary> | |||||
| public Role GetRole(string id) => _roles[id]; | public Role GetRole(string id) => _roles[id]; | ||||
| public Role FindRole(Server server, string name) | |||||
| => FindRole(server.Id, name); | |||||
| public Role FindRole(string serverId, string name) | |||||
| /// <summary> Returns all roles with the specified server and name. </summary> | |||||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||||
| public IEnumerable<Role> FindRoles(Server server, string name) | |||||
| => FindRoles(server.Id, name); | |||||
| /// <summary> Returns all roles with the specified server and name. </summary> | |||||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||||
| public IEnumerable<Role> FindRoles(string serverId, string name) | |||||
| { | { | ||||
| return _roles | |||||
| .Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)) | |||||
| .FirstOrDefault(); | |||||
| if (name.StartsWith("@")) | |||||
| { | |||||
| string name2 = name.Substring(1); | |||||
| return _roles.Where(x => x.ServerId == serverId && | |||||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||||
| } | |||||
| else | |||||
| { | |||||
| return _roles.Where(x => x.ServerId == serverId && | |||||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||||
| } | |||||
| } | } | ||||
| /// <summary> Returns the message with the specified id, or null if none was found. </summary> | |||||
| public Message GetMessage(string id) => _messages[id]; | public Message GetMessage(string id) => _messages[id]; | ||||
| public Task<Message[]> DownloadMessages(Channel channel, int count) | |||||
| /// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||||
| public Task<Message[]> DownloadMessages(Channel channel, int count, string beforeMessageId = null) | |||||
| => DownloadMessages(channel.Id, count); | => DownloadMessages(channel.Id, count); | ||||
| public async Task<Message[]> DownloadMessages(string channelId, int count) | |||||
| /// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||||
| public async Task<Message[]> DownloadMessages(string channelId, int count, string beforeMessageId = null) | |||||
| { | { | ||||
| Channel channel = GetChannel(channelId); | Channel channel = GetChannel(channelId); | ||||
| if (channel != null && channel.Type == ChannelTypes.Text) | if (channel != null && channel.Type == ChannelTypes.Text) | ||||