| @@ -11,14 +11,10 @@ namespace Discord.Commands | |||
| public string ArgText { get; } | |||
| public int? Permissions { get; } | |||
| public string[] Args { get; } | |||
| public User User => Message.User; | |||
| public string UserId => Message.UserId; | |||
| public Member Member => Message.Member; | |||
| public Channel Channel => Message.Channel; | |||
| public string ChannelId => Message.ChannelId; | |||
| public Server Server => Message.Channel.Server; | |||
| public string ServerId => Message.Channel.ServerId; | |||
| public CommandEventArgs(Message message, Command command, string commandText, string argText, int? permissions, string[] args) | |||
| { | |||
| @@ -8,7 +8,7 @@ namespace Discord.Commands | |||
| { | |||
| private readonly DiscordClient _client; | |||
| private List<Command> _commands; | |||
| private Func<User, Server, int> _getPermissions; | |||
| private Func<Member, int> _getPermissions; | |||
| public IEnumerable<Command> Commands => _commands; | |||
| @@ -17,7 +17,7 @@ namespace Discord.Commands | |||
| public bool RequireCommandCharInPublic { get; set; } | |||
| public bool RequireCommandCharInPrivate { get; set; } | |||
| public CommandsPlugin(DiscordClient client, Func<User, Server, int> getPermissions = null) | |||
| public CommandsPlugin(DiscordClient client, Func<Member, int> getPermissions = null) | |||
| { | |||
| _client = client; | |||
| _getPermissions = getPermissions; | |||
| @@ -96,7 +96,7 @@ namespace Discord.Commands | |||
| argText = msg.Substring(args[cmd.Parts.Length].Index); | |||
| //Check Permissions | |||
| int permissions = _getPermissions != null ? _getPermissions(e.Message.User, e.Message.Channel?.Server) : 0; | |||
| int permissions = _getPermissions != null ? _getPermissions(e.Message.Member) : 0; | |||
| var eventArgs = new CommandEventArgs(e.Message, cmd, msg, argText, permissions, newArgs); | |||
| if (permissions < cmd.MinPerms) | |||
| { | |||
| @@ -202,6 +202,9 @@ | |||
| <Compile Include="..\Discord.Net\DiscordWSClientConfig.cs"> | |||
| <Link>DiscordWSClientConfig.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Helpers\AsyncCollection.cs"> | |||
| <Link>Helpers\AsyncCollection.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Helpers\EpochTime.cs"> | |||
| <Link>Helpers\EpochTime.cs</Link> | |||
| </Compile> | |||
| @@ -214,14 +217,8 @@ | |||
| <Compile Include="..\Discord.Net\Helpers\Mention.cs"> | |||
| <Link>Helpers\Mention.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Helpers\MentionHelper.cs"> | |||
| <Link>Helpers\MentionHelper.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Helpers\Shared\CollectionHelper.cs"> | |||
| <Link>Helpers\Shared\CollectionHelper.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Helpers\Shared\TaskHelper.cs"> | |||
| <Link>Helpers\Shared\TaskHelper.cs</Link> | |||
| <Link>Helpers\TaskHelper.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Helpers\TimeoutException.cs"> | |||
| <Link>Helpers\TimeoutException.cs</Link> | |||
| @@ -229,8 +226,8 @@ | |||
| <Compile Include="..\Discord.Net\HttpException.cs"> | |||
| <Link>HttpException.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Models\AsyncCollection.cs"> | |||
| <Link>Models\AsyncCollection.cs</Link> | |||
| <Compile Include="..\Discord.Net\Models\CachedObject.cs"> | |||
| <Link>Models\CachedObject.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Models\Channel.cs"> | |||
| <Link>Models\Channel.cs</Link> | |||
| @@ -238,12 +235,12 @@ | |||
| <Compile Include="..\Discord.Net\Models\Color.cs"> | |||
| <Link>Models\Color.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Models\GlobalUser.cs"> | |||
| <Link>Models\GlobalUser.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Models\Invite.cs"> | |||
| <Link>Models\Invite.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Models\Member.cs"> | |||
| <Link>Models\Member.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Models\Message.cs"> | |||
| <Link>Models\Message.cs</Link> | |||
| </Compile> | |||
| @@ -110,11 +110,11 @@ namespace Discord | |||
| } | |||
| //Invites | |||
| public Task<CreateInviteResponse> CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool withXkcdPass) | |||
| public Task<CreateInviteResponse> CreateInvite(string channelId, int maxAge, int maxUses, bool tempMembership, bool hasXkcd) | |||
| { | |||
| if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
| var request = new CreateInviteRequest { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = isTemporary, WithXkcdPass = withXkcdPass }; | |||
| var request = new CreateInviteRequest { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = tempMembership, WithXkcdPass = hasXkcd }; | |||
| return _rest.Post<CreateInviteResponse>(Endpoints.ChannelInvites(channelId), request); | |||
| } | |||
| public Task<GetInviteResponse> GetInvite(string inviteIdOrXkcd) | |||
| @@ -7,14 +7,11 @@ namespace Discord | |||
| { | |||
| public class BanEventArgs : EventArgs | |||
| { | |||
| public User User { get; } | |||
| public string UserId { get; } | |||
| public Server Server { get; } | |||
| public string ServerId => Server.Id; | |||
| internal BanEventArgs(User user, string userId, Server server) | |||
| internal BanEventArgs(string userId, Server server) | |||
| { | |||
| User = user; | |||
| UserId = userId; | |||
| Server = server; | |||
| } | |||
| @@ -26,57 +23,31 @@ namespace Discord | |||
| private void RaiseBanAdded(string userId, Server server) | |||
| { | |||
| if (BanAdded != null) | |||
| RaiseEvent(nameof(BanAdded), () => BanAdded(this, new BanEventArgs(_users[userId], userId, server))); | |||
| RaiseEvent(nameof(BanAdded), () => BanAdded(this, new BanEventArgs(userId, server))); | |||
| } | |||
| public event EventHandler<BanEventArgs> BanRemoved; | |||
| private void RaiseBanRemoved(string userId, Server server) | |||
| { | |||
| if (BanRemoved != null) | |||
| RaiseEvent(nameof(BanRemoved), () => BanRemoved(this, new BanEventArgs(_users[userId], userId, server))); | |||
| RaiseEvent(nameof(BanRemoved), () => BanRemoved(this, new BanEventArgs(userId, server))); | |||
| } | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(Member member) | |||
| => Ban(member?.ServerId, member?.UserId); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(Server server, User user) | |||
| => Ban(server?.Id, user?.Id); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(Server server, string userId) | |||
| => Ban(server?.Id, userId); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(string server, User user) | |||
| => Ban(server, user?.Id); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(string serverId, string userId) | |||
| { | |||
| CheckReady(); | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
| if (member == null) throw new ArgumentNullException(nameof(member)); | |||
| return _api.Ban(serverId, userId); | |||
| return _api.Ban(member.ServerId, member.Id); | |||
| } | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(Member member) | |||
| => Unban(member?.ServerId, member?.UserId); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(Server server, User user) | |||
| => Unban(server?.Id, user?.Id); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(Server server, string userId) | |||
| => Unban(server?.Id, userId); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(string server, User user) | |||
| => Unban(server, user?.Id); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public async Task Unban(string serverId, string userId) | |||
| public async Task Unban(Member member) | |||
| { | |||
| CheckReady(); | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
| if (member == null) throw new ArgumentNullException(nameof(member)); | |||
| try { await _api.Unban(serverId, userId).ConfigureAwait(false); } | |||
| try { await _api.Unban(member.ServerId, member.Id).ConfigureAwait(false); } | |||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
| } | |||
| } | |||
| @@ -18,9 +18,7 @@ namespace Discord | |||
| public class ChannelEventArgs : EventArgs | |||
| { | |||
| public Channel Channel { get; } | |||
| public string ChannelId => Channel.Id; | |||
| public Server Server => Channel.Server; | |||
| public string ServerId => Channel.ServerId; | |||
| internal ChannelEventArgs(Channel channel) { Channel = channel; } | |||
| } | |||
| @@ -49,29 +47,30 @@ namespace Discord | |||
| RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); | |||
| } | |||
| /// <summary> Returns the channel with the specified id, or null if none was found. </summary> | |||
| public Channel GetChannel(string id) => _channels[id]; | |||
| /// <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, string type = null) => FindChannels(server?.Id, name, type); | |||
| public Channel GetChannel(string id) | |||
| { | |||
| if (id == null) throw new ArgumentNullException(nameof(id)); | |||
| return _channels[id]; | |||
| } | |||
| /// <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, string type = null) | |||
| public IEnumerable<Channel> FindChannels(Server server, string name, string type = null) | |||
| { | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| IEnumerable<Channel> result; | |||
| if (name.StartsWith("#")) | |||
| { | |||
| string name2 = name.Substring(1); | |||
| result = _channels.Where(x => x.ServerId == serverId && | |||
| result = _channels.Where(x => x.Server == server && | |||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || | |||
| string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
| } | |||
| else | |||
| { | |||
| result = _channels.Where(x => x.ServerId == serverId && | |||
| result = _channels.Where(x => x.Server == server && | |||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
| } | |||
| @@ -82,55 +81,44 @@ namespace Discord | |||
| } | |||
| /// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
| public Task<Channel> CreateChannel(Server server, string name, string type = ChannelTypes.Text) | |||
| => CreateChannel(server?.Id, name, type); | |||
| /// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
| public async Task<Channel> CreateChannel(string serverId, string name, string type = ChannelTypes.Text) | |||
| public async Task<Channel> CreateChannel(Server server, string name, string type = ChannelTypes.Text) | |||
| { | |||
| CheckReady(); | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||
| if (type == null) throw new ArgumentNullException(nameof(type)); | |||
| CheckReady(); | |||
| var response = await _api.CreateChannel(serverId, name, type).ConfigureAwait(false); | |||
| var response = await _api.CreateChannel(server.Id, name, type).ConfigureAwait(false); | |||
| var channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); | |||
| channel.Update(response); | |||
| return channel; | |||
| } | |||
| /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
| public Task<Channel> CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId); | |||
| /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
| public Task<Channel> CreatePMChannel(User user) => CreatePMChannel(user, user?.Id); | |||
| /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
| public Task<Channel> CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); | |||
| private async Task<Channel> CreatePMChannel(User user, string userId) | |||
| public async Task<Channel> CreatePMChannel(Member member) | |||
| { | |||
| if (member == null) throw new ArgumentNullException(nameof(member)); | |||
| CheckReady(); | |||
| if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
| Channel channel = null; | |||
| if (user != null) | |||
| channel = user.PrivateChannel; | |||
| if (member != null) | |||
| channel = member.GlobalUser.PrivateChannel; | |||
| if (channel == null) | |||
| { | |||
| var response = await _api.CreatePMChannel(CurrentUserId, userId).ConfigureAwait(false); | |||
| user = _users.GetOrAdd(response.Recipient?.Id); | |||
| user.Update(response.Recipient); | |||
| var response = await _api.CreatePMChannel(_userId, member.Id).ConfigureAwait(false); | |||
| var recipient = _members.GetOrAdd(response.Recipient?.Id, _servers.PMServer.Id); | |||
| recipient.Update(response.Recipient); | |||
| channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); | |||
| channel.Update(response); | |||
| } | |||
| return channel; | |||
| } | |||
| /// <summary> Edits the provided channel, changing only non-null attributes. </summary> | |||
| public Task EditChannel(string channelId, string name = null, string topic = null, int? position = null) | |||
| => EditChannel(_channels[channelId], name: name, topic: topic, position: position); | |||
| /// <summary> Edits the provided channel, changing only non-null attributes. </summary> | |||
| public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null) | |||
| { | |||
| CheckReady(); | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| CheckReady(); | |||
| await _api.EditChannel(channel.Id, name: name, topic: topic); | |||
| @@ -155,34 +143,29 @@ namespace Discord | |||
| channels[i] = channels[i - 1]; | |||
| channels[newPos] = channel; | |||
| } | |||
| await _api.ReorderChannels(channel.ServerId, channels.Skip(minPos).Select(x => x.Id), minPos); | |||
| Channel after = minPos > 0 ? channels.Skip(minPos - 1).FirstOrDefault() : null; | |||
| await ReorderChannels(channel.Server, channels.Skip(minPos), after); | |||
| } | |||
| } | |||
| public Task ReorderChannels(Server server, IEnumerable<object> channels, int startPos = 0) | |||
| => ReorderChannels(server.Id, channels, startPos); | |||
| public Task ReorderChannels(string serverId, IEnumerable<object> channels, int startPos = 0) | |||
| /// <summary> Reorders the provided channels in the server's channel list and places them after a certain channel. </summary> | |||
| public Task ReorderChannels(Server server, IEnumerable<Channel> channels, Channel after = null) | |||
| { | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| if (channels == null) throw new ArgumentNullException(nameof(channels)); | |||
| if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); | |||
| var channelIds = CollectionHelper.FlattenChannels(channels); | |||
| return _api.ReorderChannels(serverId, channelIds, startPos); | |||
| return _api.ReorderChannels(server.Id, channels.Select(x => x.Id), after.Position); | |||
| } | |||
| /// <summary> Destroys the provided channel. </summary> | |||
| public Task<Channel> DestroyChannel(Channel channel) | |||
| => DestroyChannel(channel?.Id); | |||
| /// <summary> Destroys the provided channel. </summary> | |||
| public async Task<Channel> DestroyChannel(string channelId) | |||
| public async Task<Channel> DestroyChannel(Channel channel) | |||
| { | |||
| CheckReady(); | |||
| if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| try { await _api.DestroyChannel(channelId).ConfigureAwait(false); } | |||
| try { await _api.DestroyChannel(channel.Id).ConfigureAwait(false); } | |||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
| return _channels.TryRemove(channelId); | |||
| return _channels.TryRemove(channel.Id); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,4 +1,3 @@ | |||
| using Discord.Net; | |||
| using System; | |||
| using System.Net; | |||
| using System.Threading.Tasks; | |||
| @@ -14,6 +13,14 @@ namespace Discord | |||
| CheckReady(); | |||
| if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); | |||
| //Remove trailing slash | |||
| if (inviteIdOrXkcd.Length > 0 && inviteIdOrXkcd[inviteIdOrXkcd.Length - 1] == '/') | |||
| inviteIdOrXkcd = inviteIdOrXkcd.Substring(0, inviteIdOrXkcd.Length - 1); | |||
| //Remove leading URL | |||
| int index = inviteIdOrXkcd.LastIndexOf('/'); | |||
| if (index >= 0) | |||
| inviteIdOrXkcd = inviteIdOrXkcd.Substring(index + 1); | |||
| var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); | |||
| var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); | |||
| invite.Update(response); | |||
| @@ -26,44 +33,36 @@ namespace Discord | |||
| /// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
| public Task<Invite> CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
| => CreateInvite(server?.DefaultChannelId, maxAge, maxUses, tempMembership, hasXkcd); | |||
| /// <summary> Creates a new invite to the provided channel. </summary> | |||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
| /// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
| public Task<Invite> CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
| => CreateInvite(channel?.Id, maxAge, maxUses, tempMembership, hasXkcd); | |||
| { | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| return CreateInvite(server.DefaultChannel, maxAge, maxUses, tempMembership, hasXkcd); | |||
| } | |||
| /// <summary> Creates a new invite to the provided channel. </summary> | |||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
| /// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
| public async Task<Invite> CreateInvite(string serverOrChannelId, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
| public async Task<Invite> CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
| { | |||
| CheckReady(); | |||
| if (serverOrChannelId == null) throw new ArgumentNullException(nameof(serverOrChannelId)); | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); | |||
| if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); | |||
| CheckReady(); | |||
| var response = await _api.CreateInvite(serverOrChannelId, maxAge, maxUses, tempMembership, hasXkcd).ConfigureAwait(false); | |||
| var response = await _api.CreateInvite(channel.Id, maxAge: maxAge, maxUses: maxUses, | |||
| tempMembership: tempMembership, hasXkcd: hasXkcd).ConfigureAwait(false); | |||
| var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); | |||
| invite.Update(response); | |||
| return invite; | |||
| } | |||
| /// <summary> Deletes the provided invite. </summary> | |||
| public async Task DestroyInvite(string inviteId) | |||
| public async Task DestroyInvite(Invite invite) | |||
| { | |||
| CheckReady(); | |||
| if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); | |||
| if (invite == null) throw new ArgumentNullException(nameof(invite)); | |||
| try | |||
| { | |||
| //Check if this is a human-readable link and get its ID | |||
| var response = await _api.GetInvite(inviteId).ConfigureAwait(false); | |||
| await _api.DeleteInvite(response.Code).ConfigureAwait(false); | |||
| } | |||
| try { await _api.DeleteInvite(invite.Id).ConfigureAwait(false); } | |||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
| } | |||
| @@ -75,22 +74,5 @@ namespace Discord | |||
| return _api.AcceptInvite(invite.Id); | |||
| } | |||
| /// <summary> Accepts the provided invite. </summary> | |||
| public async Task AcceptInvite(string inviteId) | |||
| { | |||
| CheckReady(); | |||
| if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); | |||
| //Remove trailing slash and any non-code url parts | |||
| if (inviteId.Length > 0 && inviteId[inviteId.Length - 1] == '/') | |||
| inviteId = inviteId.Substring(0, inviteId.Length - 1); | |||
| int index = inviteId.LastIndexOf('/'); | |||
| if (index >= 0) | |||
| inviteId = inviteId.Substring(index + 1); | |||
| //Check if this is a human-readable link and get its ID | |||
| var invite = await GetInvite(inviteId).ConfigureAwait(false); | |||
| await _api.AcceptInvite(invite.Id).ConfigureAwait(false); | |||
| } | |||
| } | |||
| } | |||
| @@ -23,8 +23,7 @@ namespace Discord | |||
| public class MemberEventArgs : EventArgs | |||
| { | |||
| public Member Member { get; } | |||
| public User User => Member.User; | |||
| public string UserId => Member.UserId; | |||
| public string UserId => Member.Id; | |||
| public Server Server => Member.Server; | |||
| public string ServerId => Member.ServerId; | |||
| @@ -66,80 +65,65 @@ namespace Discord | |||
| if (UserIsSpeaking != null) | |||
| RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new MemberIsSpeakingEventArgs(member, channel, isSpeaking))); | |||
| } | |||
| private Member _currentUser; | |||
| internal Members Members => _members; | |||
| private readonly Members _members; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(Server server, User user) => _members[user?.Id, server?.Id]; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(Server server, string userId) => _members[userId, server?.Id]; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(string serverId, User user) => _members[user?.Id, serverId]; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(string serverId, string userId) => _members[userId, serverId]; | |||
| /// <summary> Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public Member GetMember(Server server, string username, string discriminator) | |||
| => GetMember(server?.Id, username, discriminator); | |||
| public Member GetMember(Server server, string userId) | |||
| { | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
| CheckReady(); | |||
| return _members[userId, server.Id]; | |||
| } | |||
| /// <summary> Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public Member GetMember(string serverId, string username, string discriminator) | |||
| public Member GetMember(Server server, string username, string discriminator) | |||
| { | |||
| User user = GetUser(username, discriminator); | |||
| return _members[user?.Id, serverId]; | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| if (username == null) throw new ArgumentNullException(nameof(username)); | |||
| if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); | |||
| CheckReady(); | |||
| Member member = FindMembers(server, username, discriminator, true).FirstOrDefault(); | |||
| return _members[member?.Id, server.Id]; | |||
| } | |||
| /// <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<Member> FindMembers(string serverId, string name) => FindMembers(_servers[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<Member> FindMembers(Server server, string name) | |||
| public IEnumerable<Member> FindMembers(Server server, string name, string discriminator = null, bool exactMatch = false) | |||
| { | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||
| CheckReady(); | |||
| if (name.StartsWith("@")) | |||
| IEnumerable<Member> query; | |||
| if (!exactMatch && name.StartsWith("@")) | |||
| { | |||
| string name2 = name.Substring(1); | |||
| return server.Members.Where(x => | |||
| { | |||
| var user = x.User; | |||
| if (user == null) | |||
| return false; | |||
| return string.Equals(user.Name, name, StringComparison.OrdinalIgnoreCase) || | |||
| string.Equals(user.Name, name2, StringComparison.OrdinalIgnoreCase); | |||
| }); | |||
| query = server.Members.Where(x => | |||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || | |||
| string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
| } | |||
| else | |||
| { | |||
| return server.Members.Where(x => | |||
| { | |||
| var user = x.User; | |||
| if (user == null) | |||
| return false; | |||
| return string.Equals(x.User.Name, name, StringComparison.OrdinalIgnoreCase); | |||
| }); | |||
| query = server.Members.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
| } | |||
| } | |||
| if (discriminator != null) | |||
| query = query.Where(x => x.Discriminator == discriminator); | |||
| return query; | |||
| } | |||
| public Task EditMember(Member member, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
| => EditMember(member?.ServerId, member?.UserId, mute, deaf, roles); | |||
| public Task EditMember(Server server, User user, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
| => EditMember(server?.Id, user?.Id, mute, deaf, roles); | |||
| public Task EditMember(Server server, string userId, bool? mute = null, bool? deaf = null, IEnumerable<string> roles = null) | |||
| => EditMember(server?.Id, userId, mute, deaf, roles); | |||
| public Task EditMember(string serverId, User user, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
| => EditMember(serverId, user?.Id, mute, deaf, roles); | |||
| public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
| public Task EditMember(Member member, bool? mute = null, bool? deaf = null, IEnumerable<Role> roles = null) | |||
| { | |||
| if (member == null) throw new ArgumentNullException(nameof(member)); | |||
| CheckReady(); | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
| var newRoles = CollectionHelper.FlattenRoles(roles); | |||
| return _api.EditMember(serverId, userId, mute: mute, deaf: deaf, roles: newRoles); | |||
| return _api.EditMember(member.ServerId, member.Id, mute: mute, deaf: deaf, roles: roles.Select(x => x.Id)); | |||
| } | |||
| } | |||
| } | |||
| @@ -20,14 +20,9 @@ namespace Discord | |||
| public class MessageEventArgs : EventArgs | |||
| { | |||
| public Message Message { get; } | |||
| public string MessageId => Message.Id; | |||
| public Member Member => Message.Member; | |||
| public Channel Channel => Message.Channel; | |||
| public string ChannelId => Message.ChannelId; | |||
| public Server Server => Message.Server; | |||
| public string ServerId => Message.ServerId; | |||
| public User User => Member.User; | |||
| public string UserId => Message.UserId; | |||
| internal MessageEventArgs(Message msg) { Message = msg; } | |||
| } | |||
| @@ -74,165 +69,127 @@ namespace Discord | |||
| public Message GetMessage(string id) => _messages[id]; | |||
| /// <summary> Sends a message to the provided channel. To include a mention, see the Mention static helper class. </summary> | |||
| public Task<Message[]> SendMessage(Channel channel, string text) | |||
| => SendMessage(channel, text, MentionHelper.GetUserIds(text), false); | |||
| /// <summary> Sends a message to the provided channel. To include a mention, see the Mention static helper class. </summary> | |||
| public Task<Message[]> SendMessage(string channelId, string text) | |||
| => SendMessage(_channels[channelId], text, MentionHelper.GetUserIds(text), false); | |||
| private async Task<Message[]> SendMessage(Channel channel, string text, IEnumerable<object> mentionedUsers = null, bool isTextToSpeech = false) | |||
| public Task<Message> SendMessage(Channel channel, string text) | |||
| { | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| if (text == null) throw new ArgumentNullException(nameof(text)); | |||
| CheckReady(); | |||
| return SendMessage(channel, text, false); | |||
| } | |||
| /// <summary> Sends a text-to-speech message to the provided channel. To include a mention, see the Mention static helper class. </summary> | |||
| public Task<Message> SendTTSMessage(Channel channel, string text) | |||
| { | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| if (text == null) throw new ArgumentNullException(nameof(text)); | |||
| var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); | |||
| CheckReady(); | |||
| int blockCount = (int)Math.Ceiling(text.Length / (double)MaxMessageSize); | |||
| Message[] result = new Message[blockCount]; | |||
| for (int i = 0; i < blockCount; i++) | |||
| return SendMessage(channel, text, false); | |||
| } | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public async Task<Message> SendPrivateMessage(Member member, string text) | |||
| { | |||
| if (member == null) throw new ArgumentNullException(nameof(member)); | |||
| if (text == null) throw new ArgumentNullException(nameof(text)); | |||
| CheckReady(); | |||
| var channel = await CreatePMChannel(member).ConfigureAwait(false); | |||
| return await SendMessage(channel, text).ConfigureAwait(false); | |||
| } | |||
| private async Task<Message> SendMessage(Channel channel, string text, bool isTextToSpeech) | |||
| { | |||
| Message msg; | |||
| var userIds = !channel.IsPrivate ? Mention.GetUserIds(text) : new string[0]; | |||
| if (Config.UseMessageQueue) | |||
| { | |||
| int index = i * MaxMessageSize; | |||
| string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); | |||
| var nonce = GenerateNonce(); | |||
| if (Config.UseMessageQueue) | |||
| { | |||
| var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, CurrentUserId); | |||
| var currentUser = msg.User; | |||
| msg.Update(new MessageInfo | |||
| { | |||
| Content = blockText, | |||
| Timestamp = DateTime.UtcNow, | |||
| Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = CurrentUserId, Username = currentUser.Name }, | |||
| ChannelId = channel.Id, | |||
| IsTextToSpeech = isTextToSpeech | |||
| }); | |||
| msg.IsQueued = true; | |||
| msg.Nonce = nonce; | |||
| result[i] = msg; | |||
| _pendingMessages.Enqueue(msg); | |||
| } | |||
| else | |||
| msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, _userId); | |||
| var currentUser = msg.Member; | |||
| msg.Update(new MessageInfo | |||
| { | |||
| var model = await _api.SendMessage(channel.Id, blockText, mentionedUserIds, nonce, isTextToSpeech).ConfigureAwait(false); | |||
| var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); | |||
| msg.Update(model); | |||
| RaiseMessageSent(msg); | |||
| result[i] = msg; | |||
| } | |||
| await Task.Delay(1000).ConfigureAwait(false); | |||
| Content = text, | |||
| Timestamp = DateTime.UtcNow, | |||
| Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = _userId, Username = currentUser.Name }, | |||
| ChannelId = channel.Id, | |||
| IsTextToSpeech = isTextToSpeech | |||
| }); | |||
| msg.Mentions = userIds.Select(x => _members[x, channel.Server.Id]).Where(x => x != null).ToArray(); | |||
| msg.IsQueued = true; | |||
| msg.Nonce = nonce; | |||
| _pendingMessages.Enqueue(msg); | |||
| } | |||
| return result; | |||
| else | |||
| { | |||
| var model = await _api.SendMessage(channel.Id, text, userIds, null, isTextToSpeech).ConfigureAwait(false); | |||
| msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); | |||
| msg.Update(model); | |||
| RaiseMessageSent(msg); | |||
| } | |||
| return msg; | |||
| } | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public Task<Message[]> SendPrivateMessage(Member member, string text) | |||
| => SendPrivateMessage(member?.UserId, text); | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public Task<Message[]> SendPrivateMessage(User user, string text) | |||
| => SendPrivateMessage(user?.Id, text); | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public async Task<Message[]> SendPrivateMessage(string userId, string text) | |||
| { | |||
| var channel = await CreatePMChannel(userId).ConfigureAwait(false); | |||
| return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); | |||
| } | |||
| /// <summary> Sends a file to the provided channel. </summary> | |||
| public Task SendFile(Channel channel, string filePath) | |||
| => SendFile(channel?.Id, filePath); | |||
| /// <summary> Sends a file to the provided channel. </summary> | |||
| public Task SendFile(string channelId, string filePath) | |||
| { | |||
| CheckReady(); | |||
| if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| if (filePath == null) throw new ArgumentNullException(nameof(filePath)); | |||
| CheckReady(); | |||
| return _api.SendFile(channelId, filePath); | |||
| return _api.SendFile(channel.Id, filePath); | |||
| } | |||
| /// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
| /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
| public Task EditMessage(Message message, string text = null, IEnumerable<object> mentionedUsers = null) | |||
| => EditMessage(message?.ChannelId, message?.Id, text, mentionedUsers); | |||
| /// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
| /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
| public Task EditMessage(Channel channel, string messageId, string text = null, IEnumerable<object> mentionedUsers = null) | |||
| => EditMessage(channel?.Id, messageId, text, mentionedUsers); | |||
| /// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
| /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
| public async Task EditMessage(string channelId, string messageId, string text = null, IEnumerable<object> mentionedUsers = null) | |||
| public async Task EditMessage(Message message, string text) | |||
| { | |||
| if (message == null) throw new ArgumentNullException(nameof(message)); | |||
| CheckReady(); | |||
| if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
| if (messageId == null) throw new ArgumentNullException(nameof(messageId)); | |||
| var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); | |||
| if (text != null && text.Length > MaxMessageSize) | |||
| text = text.Substring(0, MaxMessageSize); | |||
| var model = await _api.EditMessage(messageId, channelId, text, mentionedUserIds).ConfigureAwait(false); | |||
| var msg = _messages[messageId]; | |||
| if (msg != null) | |||
| msg.Update(model); | |||
| var model = await _api.EditMessage(message.Id, message.Channel.Id, text, Mention.GetUserIds(text)).ConfigureAwait(false); | |||
| message.Update(model); | |||
| } | |||
| /// <summary> Deletes the provided message. </summary> | |||
| public Task DeleteMessage(Message msg) | |||
| => DeleteMessage(msg?.ChannelId, msg?.Id); | |||
| /// <summary> Deletes the provided message. </summary> | |||
| public async Task DeleteMessage(string channelId, string msgId) | |||
| public async Task DeleteMessage(Message message) | |||
| { | |||
| if (message == null) throw new ArgumentNullException(nameof(message)); | |||
| CheckReady(); | |||
| if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
| if (msgId == null) throw new ArgumentNullException(nameof(msgId)); | |||
| try | |||
| { | |||
| await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); | |||
| _messages.TryRemove(msgId); | |||
| await _api.DeleteMessage(message.Id, message.Channel.Id).ConfigureAwait(false); | |||
| _messages.TryRemove(message.Id); | |||
| } | |||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
| } | |||
| public async Task DeleteMessages(IEnumerable<Message> msgs) | |||
| { | |||
| CheckReady(); | |||
| if (msgs == null) throw new ArgumentNullException(nameof(msgs)); | |||
| foreach (var msg in msgs) | |||
| { | |||
| try | |||
| { | |||
| await _api.DeleteMessage(msg.Id, msg.ChannelId).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
| } | |||
| } | |||
| public async Task DeleteMessages(string channelId, IEnumerable<string> msgIds) | |||
| public async Task DeleteMessages(IEnumerable<Message> messages) | |||
| { | |||
| if (messages == null) throw new ArgumentNullException(nameof(messages)); | |||
| CheckReady(); | |||
| if (msgIds == null) throw new ArgumentNullException(nameof(msgIds)); | |||
| foreach (var msgId in msgIds) | |||
| foreach (var message in messages) | |||
| { | |||
| try | |||
| { | |||
| await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); | |||
| await _api.DeleteMessage(message.Id, message.Channel.Id).ConfigureAwait(false); | |||
| _messages.TryRemove(message.Id); | |||
| } | |||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
| } | |||
| } | |||
| /// <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, bool cache = true) | |||
| => DownloadMessages(channel.Id, count, beforeMessageId, cache); | |||
| /// <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, bool cache = true) | |||
| public async Task<Message[]> DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) | |||
| { | |||
| CheckReady(); | |||
| if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| if (count < 0) throw new ArgumentNullException(nameof(count)); | |||
| if (count == 0) return new Message[0]; | |||
| CheckReady(); | |||
| Channel channel = _channels[channelId]; | |||
| if (count == 0) return new Message[0]; | |||
| if (channel != null && channel.Type == ChannelTypes.Text) | |||
| { | |||
| try | |||
| @@ -283,7 +240,7 @@ namespace Discord | |||
| SendMessageResponse response = null; | |||
| try | |||
| { | |||
| response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false); | |||
| response = await _api.SendMessage(msg.Channel.Id, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false); | |||
| } | |||
| catch (WebException) { break; } | |||
| catch (HttpException) { hasFailed = true; } | |||
| @@ -8,47 +8,13 @@ namespace Discord | |||
| public partial class DiscordClient | |||
| { | |||
| public Task SetChannelUserPermissions(Channel channel, Member member, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(channel, member?.UserId, PermissionTarget.Member, allow, deny); | |||
| public Task SetChannelUserPermissions(string channelId, Member member, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member, allow, deny); | |||
| public Task SetChannelUserPermissions(Channel channel, User user, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(channel, user?.Id, PermissionTarget.Member, allow, deny); | |||
| public Task SetChannelUserPermissions(string channelId, User user, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member, allow, deny); | |||
| public Task SetChannelUserPermissions(Channel channel, string userId, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(channel, userId, PermissionTarget.Member, allow, deny); | |||
| public Task SetChannelUserPermissions(string channelId, string userId, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Member, allow, deny); | |||
| => SetChannelPermissions(channel, member?.Id, PermissionTarget.Member, allow, deny); | |||
| public Task SetChannelUserPermissions(Channel channel, Member member, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(channel, member?.UserId, PermissionTarget.Member, permissions?.Allow, permissions?.Deny); | |||
| public Task SetChannelUserPermissions(string channelId, Member member, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member, permissions?.Allow, permissions?.Deny); | |||
| public Task SetChannelUserPermissions(Channel channel, User user, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(channel, user?.Id, PermissionTarget.Member, permissions?.Allow, permissions?.Deny); | |||
| public Task SetChannelUserPermissions(string channelId, User user, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member, permissions?.Allow, permissions?.Deny); | |||
| public Task SetChannelUserPermissions(Channel channel, string userId, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(channel, userId, PermissionTarget.Member, permissions?.Allow, permissions?.Deny); | |||
| public Task SetChannelUserPermissions(string channelId, string userId, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Member, permissions?.Allow, permissions?.Deny); | |||
| => SetChannelPermissions(channel, member?.Id, PermissionTarget.Member, permissions?.Allow, permissions?.Deny); | |||
| public Task SetChannelRolePermissions(Channel channel, Role role, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, allow, deny); | |||
| public Task SetChannelRolePermissions(string channelId, Role role, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role, allow, deny); | |||
| public Task SetChannelRolePermissions(Channel channel, string userId, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(channel, userId, PermissionTarget.Role, allow, deny); | |||
| public Task SetChannelRolePermissions(string channelId, string userId, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Role, allow, deny); | |||
| public Task SetChannelRolePermissions(Channel channel, Role role, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, permissions?.Allow, permissions?.Deny); | |||
| public Task SetChannelRolePermissions(string channelId, Role role, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role, permissions?.Allow, permissions?.Deny); | |||
| public Task SetChannelRolePermissions(Channel channel, string userId, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(channel, userId, PermissionTarget.Role, permissions?.Allow, permissions?.Deny); | |||
| public Task SetChannelRolePermissions(string channelId, string userId, DualChannelPermissions permissions = null) | |||
| => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Role, permissions?.Allow, permissions?.Deny); | |||
| private async Task SetChannelPermissions(Channel channel, string targetId, string targetType, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
| { | |||
| CheckReady(); | |||
| @@ -103,34 +69,23 @@ namespace Discord | |||
| } | |||
| public Task RemoveChannelUserPermissions(Channel channel, Member member) | |||
| => RemoveChannelPermissions(channel, member?.UserId, PermissionTarget.Member); | |||
| public Task RemoveChannelUserPermissions(string channelId, Member member) | |||
| => RemoveChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member); | |||
| public Task RemoveChannelUserPermissions(Channel channel, User user) | |||
| => RemoveChannelPermissions(channel, user?.Id, PermissionTarget.Member); | |||
| public Task RemoveChannelUserPermissions(string channelId, User user) | |||
| => RemoveChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member); | |||
| public Task RemoveChannelUserPermissions(Channel channel, string userId) | |||
| => RemoveChannelPermissions(channel, userId, PermissionTarget.Member); | |||
| public Task RemoveChannelUserPermissions(string channelId, string userId) | |||
| => RemoveChannelPermissions(_channels[channelId], userId, PermissionTarget.Member); | |||
| { | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| if (member == null) throw new ArgumentNullException(nameof(member)); | |||
| CheckReady(); | |||
| return RemoveChannelPermissions(channel, member?.Id, PermissionTarget.Member); | |||
| } | |||
| public Task RemoveChannelRolePermissions(Channel channel, Role role) | |||
| => RemoveChannelPermissions(channel, role?.Id, PermissionTarget.Role); | |||
| public Task RemoveChannelRolePermissions(string channelId, Role role) | |||
| => RemoveChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role); | |||
| public Task RemoveChannelRolePermissions(Channel channel, string roleId) | |||
| => RemoveChannelPermissions(channel, roleId, PermissionTarget.Role); | |||
| public Task RemoveChannelRolePermissions(string channelId, string roleId) | |||
| => RemoveChannelPermissions(_channels[channelId], roleId, PermissionTarget.Role); | |||
| private async Task RemoveChannelPermissions(Channel channel, string userOrRoleId, string idType) | |||
| { | |||
| CheckReady(); | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| if (userOrRoleId == null) throw new ArgumentNullException(nameof(userOrRoleId)); | |||
| if (idType == null) throw new ArgumentNullException(nameof(idType)); | |||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||
| CheckReady(); | |||
| return RemoveChannelPermissions(channel, role?.Id, PermissionTarget.Role); | |||
| } | |||
| private async Task RemoveChannelPermissions(Channel channel, string userOrRoleId, string idType) | |||
| { | |||
| try | |||
| { | |||
| var perms = channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).FirstOrDefault(); | |||
| @@ -33,9 +33,7 @@ namespace Discord | |||
| public class RoleEventArgs : EventArgs | |||
| { | |||
| public Role Role { get; } | |||
| public string RoleId => Role.Id; | |||
| public Server Server => Role.Server; | |||
| public string ServerId => Role.ServerId; | |||
| internal RoleEventArgs(Role role) { Role = role; } | |||
| } | |||
| @@ -68,23 +66,20 @@ namespace Discord | |||
| public Role GetRole(string id) => _roles[id]; | |||
| /// <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) | |||
| public IEnumerable<Role> FindRoles(Server server, string name) | |||
| { | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||
| if (name.StartsWith("@")) | |||
| { | |||
| string name2 = name.Substring(1); | |||
| return _roles.Where(x => x.ServerId == serverId && | |||
| return _roles.Where(x => x.Server.Id == server.Id && | |||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
| } | |||
| else | |||
| { | |||
| return _roles.Where(x => x.ServerId == serverId && | |||
| return _roles.Where(x => x.Server.Id == server.Id && | |||
| string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
| } | |||
| } | |||
| @@ -106,16 +101,14 @@ namespace Discord | |||
| return role; | |||
| } | |||
| public Task EditRole(string roleId, string name = null, ServerPermissions permissions = null, Color color = null, bool? hoist = null, int? position = null) | |||
| => EditRole(_roles[roleId], name: name, permissions: permissions, color: color, hoist: hoist, position: position); | |||
| public async Task EditRole(Role role, string name = null, ServerPermissions permissions = null, Color color = null, bool? hoist = null, int? position = null) | |||
| { | |||
| CheckReady(); | |||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||
| CheckReady(); | |||
| //TODO: check this null workaround later, should be fixed on Discord's end soon | |||
| var response = await _api.EditRole(role.ServerId, role.Id, | |||
| var response = await _api.EditRole(role.Server.Id, role.Id, | |||
| name: name ?? role.Name, | |||
| permissions: permissions?.RawValue ?? role.Permissions.RawValue, | |||
| color: color?.RawValue, | |||
| @@ -142,40 +135,26 @@ namespace Discord | |||
| roles[i] = roles[i - 1]; | |||
| roles[newPos] = role; | |||
| } | |||
| await _api.ReorderRoles(role.ServerId, roles.Skip(minPos).Select(x => x.Id), minPos); | |||
| await _api.ReorderRoles(role.Server.Id, roles.Skip(minPos).Select(x => x.Id), minPos); | |||
| } | |||
| } | |||
| public Task DeleteRole(Role role) | |||
| => DeleteRole(role?.ServerId, role?.Id); | |||
| public Task DeleteRole(string serverId, string roleId) | |||
| { | |||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||
| CheckReady(); | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (roleId == null) throw new ArgumentNullException(nameof(roleId)); | |||
| return _api.DeleteRole(serverId, roleId); | |||
| return _api.DeleteRole(role.Server.Id, role.Id); | |||
| } | |||
| public Task ReorderRoles(Server server, IEnumerable<object> roles, int startPos = 0) | |||
| => ReorderChannels(server.Id, roles, startPos); | |||
| public Task ReorderRoles(string serverId, IEnumerable<object> roles, int startPos = 0) | |||
| public Task ReorderRoles(Server server, IEnumerable<Role> roles, int startPos = 0) | |||
| { | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| if (roles == null) throw new ArgumentNullException(nameof(roles)); | |||
| if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); | |||
| CheckReady(); | |||
| var roleIds = roles.Select(x => | |||
| { | |||
| if (x is string) | |||
| return x as string; | |||
| else if (x is Role) | |||
| return (x as Role).Id; | |||
| else | |||
| throw new ArgumentException("Channels must be a collection of string or Role.", nameof(roles)); | |||
| }); | |||
| return _api.ReorderRoles(serverId, roleIds, startPos); | |||
| return _api.ReorderRoles(server.Id, roles.Select(x => x.Id), startPos); | |||
| } | |||
| } | |||
| } | |||
| @@ -6,20 +6,12 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| internal sealed class Users : AsyncCollection<User> | |||
| internal sealed class Users : AsyncCollection<GlobalUser> | |||
| { | |||
| public Users(DiscordClient client, object writerLock) | |||
| : base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } | |||
| public User GetOrAdd(string id) => GetOrAdd(id, () => new User(_client, id)); | |||
| } | |||
| public sealed class UserEventArgs : EventArgs | |||
| { | |||
| public User User { get; } | |||
| public string UserId => User.Id; | |||
| internal UserEventArgs(User user) { User = user; } | |||
| public GlobalUser GetOrAdd(string id) => GetOrAdd(id, () => new GlobalUser(_client, id)); | |||
| } | |||
| public partial class DiscordClient | |||
| @@ -36,11 +28,11 @@ namespace Discord | |||
| if (UserRemoved != null) | |||
| RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<UserEventArgs> UserUpdated; | |||
| private void RaiseUserUpdated(User user) | |||
| public event EventHandler ProfileUpdated; | |||
| private void RaiseProfileUpdated(GlobalUser user) | |||
| { | |||
| if (UserUpdated != null) | |||
| RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); | |||
| if (ProfileUpdated != null) | |||
| RaiseEvent(nameof(ProfileUpdated), () => ProfileUpdated(this, EventArgs.Empty)); | |||
| } | |||
| public event EventHandler<MemberEventArgs> MemberUpdated; | |||
| private void RaiseMemberUpdated(Member member) | |||
| @@ -65,55 +57,14 @@ namespace Discord | |||
| internal Users Users => _users; | |||
| private readonly Users _users; | |||
| /// <summary> Returns the current logged-in user. </summary> | |||
| public User CurrentUser => _currentUser; | |||
| private User _currentUser; | |||
| /// <summary> Returns the user with the specified id, or null if none was found. </summary> | |||
| public User GetUser(string id) => _users[id]; | |||
| /// <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 username, string discriminator) | |||
| { | |||
| if (username == null) throw new ArgumentNullException(nameof(username)); | |||
| if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); | |||
| if (username.StartsWith("@")) | |||
| username = username.Substring(1); | |||
| return _users.Where(x => | |||
| string.Equals(x.Name, username, StringComparison.OrdinalIgnoreCase) && | |||
| x.Discriminator == discriminator | |||
| ) | |||
| .FirstOrDefault(); | |||
| } | |||
| /// <summary> Returns all users with the specified name across all servers. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<User> FindUsers(string name) | |||
| { | |||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||
| if (name.StartsWith("@")) | |||
| { | |||
| 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)); | |||
| } | |||
| } | |||
| public Task<EditUserResponse> EditProfile(string currentPassword = "", | |||
| string username = null, string email = null, string password = null, | |||
| ImageType avatarType = ImageType.Png, byte[] avatar = null) | |||
| { | |||
| if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); | |||
| return _api.EditUser(currentPassword: currentPassword, username: username ?? _currentUser?.Name, email: email ?? _currentUser?.Email, password: password, | |||
| return _api.EditUser(currentPassword: currentPassword, | |||
| username: username ?? _currentUser?.Name, email: email ?? _currentUser?.GlobalUser.Email, password: password, | |||
| avatarType: avatarType, avatar: avatar); | |||
| } | |||
| @@ -47,18 +47,13 @@ namespace Discord | |||
| return client; | |||
| } | |||
| public Task<IDiscordVoiceClient> JoinVoiceServer(Channel channel) | |||
| => JoinVoiceServer(channel?.ServerId, channel?.Id); | |||
| public Task<IDiscordVoiceClient> JoinVoiceServer(Server server, string channelId) | |||
| => JoinVoiceServer(server?.Id, channelId); | |||
| public async Task<IDiscordVoiceClient> JoinVoiceServer(string serverId, string channelId) | |||
| public async Task<IDiscordVoiceClient> JoinVoiceServer(Channel channel) | |||
| { | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| CheckReady(); //checkVoice is done inside the voice client | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
| var client = await CreateVoiceClient(serverId).ConfigureAwait(false); | |||
| await client.JoinChannel(channelId).ConfigureAwait(false); | |||
| var client = await CreateVoiceClient(channel.Server.Id).ConfigureAwait(false); | |||
| await client.JoinChannel(channel.Id).ConfigureAwait(false); | |||
| return client; | |||
| } | |||
| @@ -10,9 +10,9 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> Provides a connection to the DiscordApp service. </summary> | |||
| public partial class DiscordClient : DiscordWSClient | |||
| public sealed partial class DiscordClient : DiscordWSClient | |||
| { | |||
| protected readonly DiscordAPIClient _api; | |||
| private readonly DiscordAPIClient _api; | |||
| private readonly Random _rand; | |||
| private readonly JsonSerializer _serializer; | |||
| private readonly ConcurrentQueue<Message> _pendingMessages; | |||
| @@ -24,6 +24,9 @@ namespace Discord | |||
| public new DiscordClientConfig Config => _config as DiscordClientConfig; | |||
| /// <summary> Returns the current logged-in user. </summary> | |||
| public Member CurrentUser => _currentUser; | |||
| /// <summary> Initializes a new instance of the DiscordClient class. </summary> | |||
| public DiscordClient(DiscordClientConfig config = null) | |||
| : base(config ?? new DiscordClientConfig()) | |||
| @@ -69,71 +72,66 @@ namespace Discord | |||
| } | |||
| } | |||
| }; | |||
| bool showIDs = _config.LogLevel > LogMessageSeverity.Debug; //Hide this for now | |||
| if (_config.LogLevel >= LogMessageSeverity.Info) | |||
| { | |||
| ServerCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Created Server: {e.Server?.Name ?? e.ServerId}"); | |||
| $"Created Server: {e.Server?.Name}"); | |||
| ServerDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Destroyed Server: {e.Server?.Name ?? e.ServerId}"); | |||
| $"Destroyed Server: {e.Server?.Name}"); | |||
| ServerUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Updated Server: {e.Server?.Name ?? e.ServerId}"); | |||
| $"Updated Server: {e.Server?.Name}"); | |||
| ServerAvailable += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Server Available: {e.Server?.Name ?? e.ServerId}"); | |||
| $"Server Available: {e.Server?.Name}"); | |||
| ServerUnavailable += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Server Unavailable: {e.Server?.Name ?? e.ServerId}"); | |||
| $"Server Unavailable: {e.Server?.Name}"); | |||
| ChannelCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Created Channel: {e.Server?.Name ?? e.ServerId}/{e.Channel?.Name ?? e.ChannelId}"); | |||
| $"Created Channel: {e.Server?.Name}/{e.Channel?.Name}"); | |||
| ChannelDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Destroyed Channel: {e.Server?.Name ?? e.ServerId}/{e.Channel?.Name ?? e.ChannelId}"); | |||
| $"Destroyed Channel: {e.Server?.Name}/{e.Channel?.Name}"); | |||
| ChannelUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Updated Channel: {e.Server?.Name ?? e.ServerId}/{e.Channel?.Name ?? e.ChannelId}"); | |||
| $"Updated Channel: {e.Server?.Name}/{e.Channel?.Name}"); | |||
| MessageCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Created Message: {e.Server?.Name ?? e.ServerId}/{e.Channel?.Name ?? e.ChannelId}/{e.MessageId}"); | |||
| $"Created Message: {e.Server?.Name}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
| MessageDeleted += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Deleted Message: {e.Server?.Name ?? e.ServerId}/{e.Channel?.Name ?? e.ChannelId}/{e.MessageId}"); | |||
| $"Deleted Message: {e.Server?.Name}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
| MessageUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Updated Message: {e.Server?.Name ?? e.ServerId}/{e.Channel?.Name ?? e.ChannelId}/{e.MessageId}"); | |||
| $"Updated Message: {e.Server?.Name}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
| RoleCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Created Role: {e.Server?.Name ?? e.ServerId}/{e.Role?.Name ?? e.RoleId}"); | |||
| $"Created Role: {e.Server?.Name}/{e.Role?.Name}"); | |||
| RoleUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Updated Role: {e.Server?.Name ?? e.ServerId}/{e.Role?.Name ?? e.RoleId}"); | |||
| $"Updated Role: {e.Server?.Name}/{e.Role?.Name}"); | |||
| RoleDeleted += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Deleted Role: {e.Server?.Name ?? e.ServerId}/{e.Role?.Name ?? e.RoleId}"); | |||
| $"Deleted Role: {e.Server?.Name}/{e.Role?.Name}"); | |||
| BanAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Added Ban: {e.Server?.Name ?? e.ServerId}/{e.User?.Name ?? e.UserId}"); | |||
| $"Added Ban: {e.Server?.Name }/{e.UserId}"); | |||
| BanRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Removed Ban: {e.Server?.Name ?? e.ServerId}/{e.User?.Name ?? e.UserId}"); | |||
| $"Removed Ban: {e.Server?.Name}/{e.UserId}"); | |||
| UserAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Added Member: {e.Server?.Name ?? e.ServerId}/{e.User?.Name ?? e.UserId}"); | |||
| $"Added Member: {e.Server?.Name}/{e.UserId}"); | |||
| UserRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Removed Member: {e.Server?.Name ?? e.ServerId}/{e.User?.Name ?? e.UserId}"); | |||
| $"Removed Member: {e.Server?.Name}/{e.UserId}"); | |||
| MemberUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Updated Member: {e.Server?.Name ?? e.ServerId}/{e.User?.Name ?? e.UserId}"); | |||
| $"Updated Member: {e.Server?.Name}/{e.UserId}"); | |||
| UserVoiceStateUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Updated Member (Voice State): {e.Server?.Name ?? e.ServerId}/{e.User?.Name ?? e.UserId}"); | |||
| UserUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| $"Updated User: {e.User.Name}"); | |||
| $"Updated Member (Voice State): {e.Server?.Name}/{e.UserId}"); | |||
| ProfileUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
| "Updated Profile"); | |||
| } | |||
| if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||
| { | |||
| UserIsTyping += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | |||
| $"Updated User (Is Typing): {e.Server?.Name ?? e.ServerId}/{e.Channel?.Name ?? e.ChannelId}/{e.User?.Name ?? e.UserId}" + | |||
| (showIDs ? $" ({e.ServerId}/{e.ChannelId}/{e.UserId})" : "")); | |||
| $"Updated User (Is Typing): {e.Server?.Name}/{e.Channel?.Name}/{e.Member?.Name}"); | |||
| MessageReadRemotely += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | |||
| $"Read Message (Remotely): {e.Server?.Name ?? e.ServerId}/{e.Channel?.Name ?? e.ChannelId}/{e.MessageId}" + | |||
| (showIDs ? $" ({e.ServerId}/{e.ChannelId}/{e.MessageId})" : "")); | |||
| $"Read Message (Remotely): {e.Server?.Name}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
| MessageSent += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | |||
| $"Sent Message: {e.Server?.Name ?? e.ServerId}/{e.Channel?.Name ?? e.ChannelId}/{e.MessageId}" + | |||
| (showIDs ? $" ({e.ServerId}/{e.ChannelId}/{e.MessageId})" : "")); | |||
| $"Sent Message: {e.Server?.Name}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
| UserPresenceUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | |||
| $"Updated Member (Presence): {e.Server?.Name ?? e.ServerId}/{e.User?.Name ?? e.UserId}" + | |||
| (showIDs ? $" ({e.ServerId}/{e.UserId})" : "")); | |||
| $"Updated Member (Presence): {e.Server?.Name}/{e.Member?.Name}"); | |||
| _api.RestClient.OnRequest += (s, e) => | |||
| { | |||
| if (e.Payload != null) | |||
| if (e.Payload != null) | |||
| RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms ({e.Payload})"); | |||
| else | |||
| RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms"); | |||
| @@ -141,18 +139,18 @@ namespace Discord | |||
| } | |||
| if (_config.LogLevel >= LogMessageSeverity.Debug) | |||
| { | |||
| _channels.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Channel {e.Item.ServerId}/{e.Item.Id}"); | |||
| _channels.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Channel {e.Item.ServerId}/{e.Item.Id}"); | |||
| _channels.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Channel {e.Item.Server.Id}/{e.Item.Id}"); | |||
| _channels.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Channel {e.Item.Server.Id}/{e.Item.Id}"); | |||
| _channels.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Channels"); | |||
| _members.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Member {e.Item.ServerId}/{e.Item.UserId}"); | |||
| _members.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Member {e.Item.ServerId}/{e.Item.UserId}"); | |||
| _members.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Member {e.Item.Server.Id}/{e.Item.Id}"); | |||
| _members.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Member {e.Item.Server.Id}/{e.Item.Id}"); | |||
| _members.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Members"); | |||
| _messages.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Message {e.Item.ServerId}/{e.Item.ChannelId}/{e.Item.Id}"); | |||
| _messages.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Message {e.Item.ServerId}/{e.Item.ChannelId}/{e.Item.Id}"); | |||
| _messages.ItemRemapped += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Remapped Message {e.Item.ServerId}/{e.Item.ChannelId}/[{e.OldId} -> {e.NewId}]"); | |||
| _messages.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Message {e.Item.Server.Id}/{e.Item.Channel.Id}/{e.Item.Id}"); | |||
| _messages.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Message {e.Item.Server.Id}/{e.Item.Channel.Id}/{e.Item.Id}"); | |||
| _messages.ItemRemapped += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Remapped Message {e.Item.Server.Id}/{e.Item.Channel.Id}/[{e.OldId} -> {e.NewId}]"); | |||
| _messages.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Messages"); | |||
| _roles.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Role {e.Item.ServerId}/{e.Item.Id}"); | |||
| _roles.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Role {e.Item.ServerId}/{e.Item.Id}"); | |||
| _roles.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Role {e.Item.Server.Id}/{e.Item.Id}"); | |||
| _roles.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Role {e.Item.Server.Id}/{e.Item.Id}"); | |||
| _roles.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Roles"); | |||
| _servers.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Server {e.Item.Id}"); | |||
| _servers.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Server {e.Item.Id}"); | |||
| @@ -295,7 +293,7 @@ namespace Discord | |||
| case "READY": //Resync | |||
| { | |||
| var data = e.Payload.ToObject<ReadyEvent>(_serializer); | |||
| _currentUser = _users.GetOrAdd(data.User.Id); | |||
| _currentUser = _members.GetOrAdd(data.User.Id, _servers.PMServer.Id); | |||
| _currentUser.Update(data.User); | |||
| foreach (var model in data.Guilds) | |||
| { | |||
| @@ -307,7 +305,7 @@ namespace Discord | |||
| } | |||
| foreach (var model in data.PrivateChannels) | |||
| { | |||
| var user = _users.GetOrAdd(model.Recipient.Id); | |||
| var user = _members.GetOrAdd(model.Recipient.Id, _servers.PMServer.Id); | |||
| user.Update(model.Recipient); | |||
| var channel = _channels.GetOrAdd(model.Id, null, user.Id); | |||
| channel.Update(model); | |||
| @@ -362,9 +360,9 @@ namespace Discord | |||
| Channel channel; | |||
| if (data.IsPrivate) | |||
| { | |||
| var user = _users.GetOrAdd(data.Recipient.Id); | |||
| user.Update(data.Recipient); | |||
| channel = _channels.GetOrAdd(data.Id, null, user.Id); | |||
| var member = _members.GetOrAdd(data.Recipient.Id, _servers.PMServer.Id); | |||
| member.Update(data.Recipient); | |||
| channel = _channels.GetOrAdd(data.Id, null, member.Id); | |||
| } | |||
| else | |||
| channel = _channels.GetOrAdd(data.Id, data.GuildId, null); | |||
| @@ -396,8 +394,6 @@ namespace Discord | |||
| case "GUILD_MEMBER_ADD": | |||
| { | |||
| var data = e.Payload.ToObject<MemberAddEvent>(_serializer); | |||
| var user = _users.GetOrAdd(data.User.Id); | |||
| user.Update(data.User); | |||
| var member = _members.GetOrAdd(data.User.Id, data.GuildId); | |||
| member.Update(data); | |||
| if (Config.TrackActivity) | |||
| @@ -433,7 +429,7 @@ namespace Discord | |||
| role.Update(data.Data); | |||
| var server = _servers[data.GuildId]; | |||
| if (server != null) | |||
| server.AddRole(data.Data.Id); | |||
| server.AddRole(role); | |||
| RaiseRoleUpdated(role); | |||
| } | |||
| break; | |||
| @@ -442,19 +438,23 @@ namespace Discord | |||
| var data = e.Payload.ToObject<RoleUpdateEvent>(_serializer); | |||
| var role = _roles[data.Data.Id]; | |||
| if (role != null) | |||
| { | |||
| role.Update(data.Data); | |||
| RaiseRoleUpdated(role); | |||
| RaiseRoleUpdated(role); | |||
| } | |||
| } | |||
| break; | |||
| case "GUILD_ROLE_DELETE": | |||
| { | |||
| var data = e.Payload.ToObject<RoleDeleteEvent>(_serializer); | |||
| var server = _servers[data.GuildId]; | |||
| if (server != null) | |||
| server.RemoveRole(data.RoleId); | |||
| var role = _roles.TryRemove(data.RoleId); | |||
| if (role != null) | |||
| { | |||
| RaiseRoleDeleted(role); | |||
| var server = _servers[data.GuildId]; | |||
| if (server != null) | |||
| server.RemoveRole(role); | |||
| } | |||
| } | |||
| break; | |||
| @@ -485,7 +485,7 @@ namespace Discord | |||
| var data = e.Payload.ToObject<MessageCreateEvent>(_serializer); | |||
| Message msg = null; | |||
| bool isAuthor = data.Author.Id == CurrentUserId; | |||
| bool isAuthor = data.Author.Id == _userId; | |||
| bool hasFinishedSending = false; | |||
| if (Config.UseMessageQueue && isAuthor && data.Nonce != null) | |||
| { | |||
| @@ -566,7 +566,7 @@ namespace Discord | |||
| var channel = _channels[data.ChannelId]; | |||
| if (channel != null) | |||
| { | |||
| var user = _members[data.UserId, channel.ServerId]; | |||
| var user = _members[data.UserId, channel.Server.Id]; | |||
| if (user != null) | |||
| { | |||
| @@ -577,7 +577,7 @@ namespace Discord | |||
| { | |||
| if (!channel.IsPrivate) | |||
| { | |||
| var member = _members[data.UserId, channel.ServerId]; | |||
| var member = _members[data.UserId, channel.Server.Id]; | |||
| if (member != null) | |||
| member.UpdateActivity(); | |||
| } | |||
| @@ -612,7 +612,7 @@ namespace Discord | |||
| if (user != null) | |||
| { | |||
| user.Update(data); | |||
| RaiseUserUpdated(user); | |||
| RaiseProfileUpdated(user); | |||
| } | |||
| } | |||
| break; | |||
| @@ -17,7 +17,7 @@ namespace Discord | |||
| await _voiceSocket.SetChannel(_voiceServerId, channelId).ConfigureAwait(false); | |||
| _dataSocket.SendJoinVoice(_voiceServerId, channelId); | |||
| await _voiceSocket.WaitForConnection(_config.ConnectionTimeout); | |||
| await _voiceSocket.WaitForConnection(_config.ConnectionTimeout).ConfigureAwait(false); | |||
| } | |||
| /// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary> | |||
| @@ -25,12 +25,12 @@ namespace Discord | |||
| /// <param name="count">Number of bytes in this frame. </param> | |||
| void IDiscordVoiceClient.SendVoicePCM(byte[] data, int count) | |||
| { | |||
| CheckReady(checkVoice: true); | |||
| if (data == null) throw new ArgumentException(nameof(data)); | |||
| if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); | |||
| if (count == 0) return; | |||
| _voiceSocket.SendPCMFrames(data, count); | |||
| CheckReady(checkVoice: true); | |||
| if (count != 0) | |||
| _voiceSocket.SendPCMFrames(data, count); | |||
| } | |||
| /// <summary> Clears the PCM buffer. </summary> | |||
| void IDiscordVoiceClient.ClearVoicePCM() | |||
| @@ -20,27 +20,21 @@ namespace Discord | |||
| /// <summary> Provides a minimalistic websocket connection to the Discord service. </summary> | |||
| public partial class DiscordWSClient | |||
| { | |||
| internal readonly DataWebSocket _dataSocket; | |||
| internal readonly VoiceWebSocket _voiceSocket; | |||
| protected readonly DiscordWSClientConfig _config; | |||
| protected readonly ManualResetEvent _disconnectedEvent; | |||
| protected readonly ManualResetEventSlim _connectedEvent; | |||
| protected readonly bool _enableVoice; | |||
| internal readonly DataWebSocket _dataSocket; | |||
| internal readonly VoiceWebSocket _voiceSocket; | |||
| protected ExceptionDispatchInfo _disconnectReason; | |||
| protected string _gateway, _token; | |||
| protected string _voiceServerId; | |||
| protected string _userId, _voiceServerId; | |||
| private Task _runTask; | |||
| protected ExceptionDispatchInfo _disconnectReason; | |||
| private bool _wasDisconnectUnexpected; | |||
| /// <summary> Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. </summary> | |||
| public DiscordWSClientConfig Config => _config; | |||
| protected readonly DiscordWSClientConfig _config; | |||
| public string CurrentUserId => _userId; | |||
| /// <summary> Returns the id of the current logged-in user. </summary> | |||
| public string CurrentUserId => _currentUserId; | |||
| private string _currentUserId; | |||
| /*/// <summary> Returns the server this user is currently connected to for voice. </summary> | |||
| public string CurrentVoiceServerId => _voiceSocket.CurrentServerId;*/ | |||
| /// <summary> Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. </summary> | |||
| public DiscordWSClientConfig Config => _config; | |||
| /// <summary> Returns the current connection state of this client. </summary> | |||
| public DiscordClientState State => (DiscordClientState)_state; | |||
| @@ -56,15 +50,13 @@ namespace Discord | |||
| _config = config ?? new DiscordWSClientConfig(); | |||
| _config.Lock(); | |||
| _enableVoice = _config.EnableVoice; | |||
| _state = (int)DiscordClientState.Disconnected; | |||
| _cancelToken = new CancellationToken(true); | |||
| _disconnectedEvent = new ManualResetEvent(true); | |||
| _connectedEvent = new ManualResetEventSlim(false); | |||
| _dataSocket = CreateDataSocket(); | |||
| if (_enableVoice) | |||
| if (_config.EnableVoice) | |||
| _voiceSocket = CreateVoiceSocket(); | |||
| } | |||
| internal DiscordWSClient(DiscordWSClientConfig config = null, string voiceServerId = null) | |||
| @@ -247,7 +239,7 @@ namespace Discord | |||
| protected virtual async Task Cleanup() | |||
| { | |||
| if (_enableVoice) | |||
| if (_config.EnableVoice) | |||
| { | |||
| string voiceServerId = _voiceSocket.CurrentServerId; | |||
| if (voiceServerId != null) | |||
| @@ -256,7 +248,7 @@ namespace Discord | |||
| } | |||
| await _dataSocket.Disconnect().ConfigureAwait(false); | |||
| _currentUserId = null; | |||
| _userId = null; | |||
| _gateway = null; | |||
| _token = null; | |||
| } | |||
| @@ -286,7 +278,7 @@ namespace Discord | |||
| throw new InvalidOperationException("The client is connecting."); | |||
| } | |||
| if (checkVoice && !_enableVoice) | |||
| if (checkVoice && !_config.EnableVoice) | |||
| throw new InvalidOperationException("Voice is not enabled for this client."); | |||
| } | |||
| protected void RaiseEvent(string name, Action action) | |||
| @@ -307,17 +299,17 @@ namespace Discord | |||
| switch (e.Type) | |||
| { | |||
| case "READY": | |||
| _currentUserId = e.Payload["user"].Value<string>("id"); | |||
| _userId = e.Payload["user"].Value<string>("id"); | |||
| break; | |||
| case "VOICE_SERVER_UPDATE": | |||
| { | |||
| string guildId = e.Payload.Value<string>("guild_id"); | |||
| if (_enableVoice && guildId == _voiceSocket.CurrentServerId) | |||
| if (_config.EnableVoice && guildId == _voiceSocket.CurrentServerId) | |||
| { | |||
| string token = e.Payload.Value<string>("token"); | |||
| _voiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; | |||
| return _voiceSocket.Login(_currentUserId, _dataSocket.SessionId, token, CancelToken); | |||
| return _voiceSocket.Login(_userId, _dataSocket.SessionId, token, CancelToken); | |||
| } | |||
| } | |||
| break; | |||
| @@ -7,7 +7,7 @@ using System.Linq; | |||
| namespace Discord | |||
| { | |||
| internal abstract class AsyncCollection<TValue> : IEnumerable<TValue> | |||
| where TValue : class | |||
| where TValue : CachedObject | |||
| { | |||
| private readonly object _writerLock; | |||
| @@ -1,26 +1,53 @@ | |||
| namespace Discord | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text.RegularExpressions; | |||
| namespace Discord | |||
| { | |||
| public static class Mention | |||
| { | |||
| /// <summary> Returns the string used to create a user mention. </summary> | |||
| public static string User(User user) | |||
| => $"<@{user.Id}>"; | |||
| private static readonly Regex _userRegex = new Regex(@"<@(\d+?)>", RegexOptions.Compiled); | |||
| private static readonly Regex _channelRegex = new Regex(@"<#(\d+?)>", RegexOptions.Compiled); | |||
| /// <summary> Returns the string used to create a user mention. </summary> | |||
| public static string User(Member member) | |||
| => $"<@{member.UserId}>"; | |||
| /// <summary> Returns the string used to create a user mention. </summary> | |||
| public static string User(string userId) | |||
| => $"<@{userId}>"; | |||
| => $"<@{member.Id}>"; | |||
| /// <summary> Returns the string used to create a channel mention. </summary> | |||
| public static string Channel(Channel channel) | |||
| => $"<#{channel.Id}>"; | |||
| /// <summary> Returns the string used to create a channel mention. </summary> | |||
| public static string Channel(string channelId) | |||
| => $"<#{channelId}>"; | |||
| /// <summary> Returns the string used to create a channel mention. </summary> | |||
| public static string Everyone() | |||
| => $"@everyone"; | |||
| internal static string ConvertToNames(DiscordClient client, Server server, string text) | |||
| { | |||
| text = _userRegex.Replace(text, new MatchEvaluator(e => | |||
| { | |||
| string id = e.Value.Substring(2, e.Value.Length - 3); | |||
| var user = client.Members[id, server.Id]; | |||
| if (user != null) | |||
| return '@' + user.Name; | |||
| else //User not found | |||
| return e.Value; | |||
| })); | |||
| text = _channelRegex.Replace(text, new MatchEvaluator(e => | |||
| { | |||
| string id = e.Value.Substring(2, e.Value.Length - 3); | |||
| var channel = client.Channels[id]; | |||
| if (channel != null && channel.Server.Id == server.Id) | |||
| return channel.Name; | |||
| else //Channel not found | |||
| return e.Value; | |||
| })); | |||
| return text; | |||
| } | |||
| internal static IEnumerable<string> GetUserIds(string text) | |||
| { | |||
| return _userRegex.Matches(text) | |||
| .OfType<Match>() | |||
| .Select(x => x.Groups[1].Value) | |||
| .Where(x => x != null); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,48 +0,0 @@ | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text.RegularExpressions; | |||
| namespace Discord | |||
| { | |||
| internal static class MentionHelper | |||
| { | |||
| private static readonly Regex _userRegex, _channelRegex; | |||
| static MentionHelper() | |||
| { | |||
| _userRegex = new Regex(@"<@(\d+?)>", RegexOptions.Compiled); | |||
| _channelRegex = new Regex(@"<#(\d+?)>", RegexOptions.Compiled); | |||
| } | |||
| public static string ConvertToNames(DiscordClient client, string text) | |||
| { | |||
| text = _userRegex.Replace(text, new MatchEvaluator(e => | |||
| { | |||
| string id = e.Value.Substring(2, e.Value.Length - 3); | |||
| var user = client.Users[id]; | |||
| if (user != null) | |||
| return '@' + user.Name; | |||
| else //User not found | |||
| return e.Value; | |||
| })); | |||
| text = _channelRegex.Replace(text, new MatchEvaluator(e => | |||
| { | |||
| string id = e.Value.Substring(2, e.Value.Length - 3); | |||
| var channel = client.Channels[id]; | |||
| if (channel != null) | |||
| return channel.Name; | |||
| else //Channel not found | |||
| return e.Value; | |||
| })); | |||
| return text; | |||
| } | |||
| public static IEnumerable<string> GetUserIds(string text) | |||
| { | |||
| return _userRegex.Matches(text) | |||
| .OfType<Match>() | |||
| .Select(x => x.Groups[1].Value) | |||
| .Where(x => x != null); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,55 +0,0 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| namespace Discord | |||
| { | |||
| internal static class CollectionHelper | |||
| { | |||
| public static IEnumerable<string> FlattenChannels(IEnumerable<object> channels) | |||
| { | |||
| if (channels == null) | |||
| return new string[0]; | |||
| return channels.Select(x => | |||
| { | |||
| if (x is string) | |||
| return x as string; | |||
| else if (x is Channel) | |||
| return (x as Channel).Id; | |||
| else | |||
| throw new ArgumentException("Collection may only contain string or Channel.", nameof(channels)); | |||
| }); | |||
| } | |||
| public static IEnumerable<string> FlattenUsers(IEnumerable<object> users) | |||
| { | |||
| if (users == null) | |||
| return new string[0]; | |||
| return users.Select(x => | |||
| { | |||
| if (x is string) | |||
| return x as string; | |||
| else if (x is User) | |||
| return (x as User).Id; | |||
| else | |||
| throw new ArgumentException("Collection may only contain string or User.", nameof(users)); | |||
| }); | |||
| } | |||
| public static IEnumerable<string> FlattenRoles(IEnumerable<object> roles) | |||
| { | |||
| if (roles == null) | |||
| return new string[0]; | |||
| return roles.Select(x => | |||
| { | |||
| if (x is string) | |||
| return x as string; | |||
| else if (x is Role) | |||
| return (x as Role).Id; | |||
| else | |||
| throw new ArgumentException("Collection may only contain string or Role.", nameof(roles)); | |||
| }); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,35 @@ | |||
| namespace Discord | |||
| { | |||
| public abstract class CachedObject | |||
| { | |||
| protected readonly DiscordClient _client; | |||
| private bool _isCached; | |||
| internal CachedObject(DiscordClient client, string id) | |||
| { | |||
| _client = client; | |||
| Id = id; | |||
| } | |||
| /// <summary> Returns the unique identifier for this object. </summary> | |||
| public string Id { get; internal set; } | |||
| public override string ToString() => $"{this.GetType().Name} {Id}"; | |||
| internal void Cache() | |||
| { | |||
| OnCached(); | |||
| _isCached = true; | |||
| } | |||
| internal void Uncache() | |||
| { | |||
| if (_isCached) | |||
| { | |||
| OnUncached(); | |||
| _isCached = false; | |||
| } | |||
| } | |||
| internal abstract void OnCached(); | |||
| internal abstract void OnUncached(); | |||
| } | |||
| } | |||
| @@ -7,7 +7,7 @@ using System.Linq; | |||
| namespace Discord | |||
| { | |||
| public sealed class Channel | |||
| public sealed class Channel : CachedObject | |||
| { | |||
| public sealed class PermissionOverwrite | |||
| { | |||
| @@ -21,44 +21,37 @@ namespace Discord | |||
| TargetType = targetType; | |||
| TargetId = targetId; | |||
| Allow = new ChannelPermissions(allow); | |||
| Deny = new ChannelPermissions(deny); | |||
| Allow.Lock(); | |||
| Deny = new ChannelPermissions(deny); | |||
| Deny.Lock(); | |||
| } | |||
| } | |||
| private readonly DiscordClient _client; | |||
| private readonly ConcurrentDictionary<string, bool> _messages; | |||
| private bool _areMembersStale; | |||
| private bool _hasRef; | |||
| /// <summary> Returns the unique identifier for this channel. </summary> | |||
| public string Id { get; } | |||
| private string _name; | |||
| private readonly string _serverId, _recipientId; | |||
| private Server _server; | |||
| private Member _recipient; | |||
| /// <summary> Returns the name of this channel. </summary> | |||
| public string Name { get { return !IsPrivate ? $"{_name}" : $"@{Recipient.Name}"; } internal set { _name = value; } } | |||
| public string Name { get; private set; } | |||
| /// <summary> Returns the topic associated with this channel. </summary> | |||
| public string Topic { get; private set; } | |||
| /// <summary> Returns the position of this channel in the channel list for this server. </summary> | |||
| public int Position { get; private set; } | |||
| /// <summary> Returns false is this is a public chat and true if this is a private chat with another user (see Recipient). </summary> | |||
| public bool IsPrivate => RecipientId != null; | |||
| public bool IsPrivate => _recipientId != null; | |||
| /// <summary> Returns the type of this channel (see ChannelTypes). </summary> | |||
| public string Type { get; private set; } | |||
| /// <summary> Returns the id of the server containing this channel. </summary> | |||
| public string ServerId { get; } | |||
| /// <summary> Returns the server containing this channel. </summary> | |||
| [JsonIgnore] | |||
| public Server Server => _client.Servers[ServerId]; | |||
| /// For private chats, returns the Id of the target user, otherwise null. | |||
| public string RecipientId { get; set; } | |||
| public Server Server => _client.Servers[_serverId]; | |||
| /// For private chats, returns the target user, otherwise null. | |||
| [JsonIgnore] | |||
| public User Recipient => _client.Users[RecipientId]; | |||
| public Member Recipient => _client.Members[_recipientId, _serverId]; | |||
| /// <summary> Returns a collection of the IDs of all users with read access to this channel. </summary> | |||
| public IEnumerable<string> UserIds | |||
| @@ -68,7 +61,7 @@ namespace Discord | |||
| if (!_areMembersStale) | |||
| return _userIds; | |||
| _userIds = Server.Members.Where(x => x.GetPermissions(Id)?.ReadMessages ?? false).Select(x => x.UserId).ToArray(); | |||
| _userIds = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).Select(x => x.Id).ToArray(); | |||
| _areMembersStale = false; | |||
| return _userIds; | |||
| } | |||
| @@ -76,10 +69,7 @@ namespace Discord | |||
| private string[] _userIds; | |||
| /// <summary> Returns a collection of all users with read access to this channel. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Member> Members => UserIds.Select(x => _client.Members[x, ServerId]); | |||
| /// <summary> Returns a collection of all users with read access to this channel. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<User> Users => UserIds.Select(x => _client.Users[x]); | |||
| public IEnumerable<Member> Members => UserIds.Select(x => _client.Members[x, _serverId]); | |||
| /// <summary> Returns a collection of the ids of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. </summary> | |||
| [JsonIgnore] | |||
| @@ -94,64 +84,39 @@ namespace Discord | |||
| public IEnumerable<PermissionOverwrite> PermissionOverwrites => _permissionOverwrites; | |||
| internal Channel(DiscordClient client, string id, string serverId, string recipientId) | |||
| : base(client, id) | |||
| { | |||
| _client = client; | |||
| Id = id; | |||
| ServerId = serverId ?? _client.Servers.PMServer.Id; | |||
| RecipientId = recipientId; | |||
| _messages = new ConcurrentDictionary<string, bool>(); | |||
| _serverId = serverId ?? _client.Servers.PMServer.Id; | |||
| _recipientId = recipientId; | |||
| _permissionOverwrites = _initialPermissionsOverwrites; | |||
| _areMembersStale = true; | |||
| //Local Cache | |||
| _messages = new ConcurrentDictionary<string, bool>(); | |||
| } | |||
| internal void OnCached() | |||
| internal override void OnCached() | |||
| { | |||
| var server = Server; | |||
| if (server != null) | |||
| server.AddChannel(Id); | |||
| if (IsPrivate) | |||
| { | |||
| var user = Recipient; | |||
| if (user != null) | |||
| { | |||
| Name = "@" + user.Name; | |||
| user.PrivateChannelId = Id; | |||
| user.AddRef(); | |||
| _hasRef = true; | |||
| } | |||
| else | |||
| Name = "@" + RecipientId; | |||
| var member = _client.Members.GetOrAdd(RecipientId, ServerId); | |||
| member.Update(new ExtendedMemberInfo | |||
| { | |||
| GuildId = ServerId, | |||
| UserId = RecipientId, | |||
| JoinedAt = DateTime.UtcNow, | |||
| Roles = new string[0] | |||
| }); | |||
| _permissionOverwrites = new PermissionOverwrite[] | |||
| { | |||
| new PermissionOverwrite(PermissionTarget.Member, _client.CurrentUserId, ChannelPermissions.PrivateOnly.RawValue, 0), | |||
| new PermissionOverwrite(PermissionTarget.Member, RecipientId, ChannelPermissions.PrivateOnly.RawValue, 0) | |||
| }; | |||
| _recipient = _client.Members[_recipientId, _serverId]; | |||
| Name = "@" + _recipient.Name; | |||
| } | |||
| else | |||
| { | |||
| _server = _client.Servers[_serverId]; | |||
| _server.AddChannel(this); | |||
| } | |||
| } | |||
| internal void OnUncached() | |||
| internal override void OnUncached() | |||
| { | |||
| var server = Server; | |||
| if (server != null) | |||
| server.RemoveChannel(Id); | |||
| if (IsPrivate) | |||
| { | |||
| var user = Recipient; | |||
| if (user != null) | |||
| { | |||
| user.PrivateChannelId = null; | |||
| if (_hasRef) | |||
| user.RemoveRef(); | |||
| } | |||
| _client.Members.TryRemove(RecipientId, ServerId); | |||
| } | |||
| _hasRef = false; | |||
| if (_server != null) | |||
| _server.RemoveChannel(this); | |||
| _server = null; | |||
| if (_recipient != null) | |||
| _recipient.GlobalUser.PrivateChannel = null; | |||
| _recipient = null; | |||
| } | |||
| internal void Update(ChannelReference model) | |||
| @@ -199,14 +164,14 @@ namespace Discord | |||
| { | |||
| _areMembersStale = true; | |||
| foreach (var member in Members) | |||
| member.UpdatePermissions(Id); | |||
| member.UpdatePermissions(this); | |||
| } | |||
| internal void InvalidatePermissionsCache(string userId) | |||
| { | |||
| _areMembersStale = true; | |||
| var member = _client.Members[userId, ServerId]; | |||
| var member = _client.Members[userId, _serverId]; | |||
| if (member != null) | |||
| member.UpdatePermissions(Id); | |||
| member.UpdatePermissions(this); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,73 @@ | |||
| using Discord.API; | |||
| using Newtonsoft.Json; | |||
| using System.Collections.Concurrent; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Threading; | |||
| namespace Discord | |||
| { | |||
| internal sealed class GlobalUser : CachedObject | |||
| { | |||
| private readonly ConcurrentDictionary<string, bool> _servers; | |||
| private int _refCount; | |||
| /// <summary> Returns the email for this user. </summary> | |||
| /// <remarks> This field is only ever populated for the current logged in user. </remarks> | |||
| [JsonIgnore] | |||
| public string Email { get; private set; } | |||
| /// <summary> Returns if the email for this user has been verified. </summary> | |||
| /// <remarks> This field is only ever populated for the current logged in user. </remarks> | |||
| [JsonIgnore] | |||
| public bool? IsVerified { get; private set; } | |||
| /// <summary> Returns the private messaging channel with this user, if one exists. </summary> | |||
| [JsonIgnore] | |||
| public Channel PrivateChannel { get; internal set; } | |||
| /// <summary> Returns a collection of all server-specific data for every server this user is a member of. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Member> Memberships => _servers.Select(x => _client.Members[Id, x.Key]); | |||
| /// <summary> Returns a collection of all servers this user is a member of. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Server> Servers => _servers.Select(x => _client.Servers[x.Key]); | |||
| internal GlobalUser(DiscordClient client, string id) | |||
| : base(client, id) | |||
| { | |||
| _servers = new ConcurrentDictionary<string, bool>(); | |||
| } | |||
| internal override void OnCached() { } | |||
| internal override void OnUncached() { } | |||
| internal void Update(UserInfo model) | |||
| { | |||
| if (model.Email != null) | |||
| Email = model.Email; | |||
| if (model.IsVerified != null) | |||
| IsVerified = model.IsVerified; | |||
| } | |||
| internal void AddServer(string serverId) | |||
| { | |||
| _servers.TryAdd(serverId, true); | |||
| } | |||
| internal bool RemoveServer(string serverId) | |||
| { | |||
| bool ignored; | |||
| return _servers.TryRemove(serverId, out ignored); | |||
| } | |||
| internal void AddRef() | |||
| { | |||
| Interlocked.Increment(ref _refCount); | |||
| } | |||
| internal void RemoveRef() | |||
| { | |||
| if (Interlocked.Decrement(ref _refCount) == 0) | |||
| _client.Users.TryRemove(Id); | |||
| } | |||
| public override string ToString() => Id; | |||
| } | |||
| } | |||
| @@ -1,15 +1,14 @@ | |||
| using Discord.API; | |||
| using System; | |||
| using Discord.API; | |||
| using Newtonsoft.Json; | |||
| namespace Discord | |||
| { | |||
| public sealed class Invite | |||
| public sealed class Invite : CachedObject | |||
| { | |||
| private readonly DiscordClient _client; | |||
| /// <summary> Returns the unique identifier for this invite. </summary> | |||
| public string Id { get; } | |||
| private readonly string _serverId; | |||
| private string _inviterId, _channelId; | |||
| /// <summary> Time (in seconds) until the invite expires. Set to 0 to never expire. </summary> | |||
| public int MaxAge { get; private set; } | |||
| /// <summary> The amount of times this invite has been used. </summary> | |||
| @@ -25,41 +24,37 @@ namespace Discord | |||
| /// <summary> Returns a URL for this invite using XkcdPass if available or Id if not. </summary> | |||
| public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Id); | |||
| /// <summary> Returns the id of the user that created this invite. </summary> | |||
| public string InviterId { get; private set; } | |||
| /// <summary> Returns the user that created this invite. </summary> | |||
| [JsonIgnore] | |||
| public User Inviter => _client.Users[InviterId]; | |||
| /// <summary> Returns the id of the server this invite is to. </summary> | |||
| public string ServerId { get; } | |||
| public Member Inviter => _client.Members[_inviterId, _serverId]; | |||
| /// <summary> Returns the server this invite is to. </summary> | |||
| [JsonIgnore] | |||
| public Server Server => _client.Servers[ServerId]; | |||
| /// <summary> Returns the id of the channel this invite is to. </summary> | |||
| public string ChannelId { get; private set; } | |||
| public Server Server => _client.Servers[_serverId]; | |||
| /// <summary> Returns the channel this invite is to. </summary> | |||
| [JsonIgnore] | |||
| public Channel Channel => _client.Channels[ChannelId]; | |||
| public Channel Channel => _client.Channels[_channelId]; | |||
| internal Invite(DiscordClient client, string code, string xkcdPass, string serverId) | |||
| : base(client, code) | |||
| { | |||
| _client = client; | |||
| Id = code; | |||
| XkcdPass = xkcdPass; | |||
| ServerId = serverId; | |||
| _serverId = serverId; | |||
| } | |||
| internal override void OnCached() { } | |||
| internal override void OnUncached() { } | |||
| public override string ToString() => XkcdPass ?? Id; | |||
| internal void Update(InviteReference model) | |||
| { | |||
| if (model.Channel != null) | |||
| ChannelId = model.Channel.Id; | |||
| _channelId = model.Channel.Id; | |||
| if (model.Inviter != null) | |||
| InviterId = model.Inviter.Id; | |||
| _inviterId = model.Inviter.Id; | |||
| } | |||
| internal void Update(InviteInfo model) | |||
| @@ -1,279 +0,0 @@ | |||
| using Discord.API; | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Concurrent; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| namespace Discord | |||
| { | |||
| public class Member | |||
| { | |||
| private readonly DiscordClient _client; | |||
| private ConcurrentDictionary<string, ChannelPermissions> _permissions; | |||
| private bool _hasRef; | |||
| /// <summary> Returns the name of this user on this server. </summary> | |||
| public string Name { get; private set; } | |||
| /// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary> | |||
| public string Discriminator { get; private set; } | |||
| /// <summary> Returns the unique identifier for this user's current avatar. </summary> | |||
| public string AvatarId { get; private set; } | |||
| /// <summary> Returns the URL to this user's current avatar. </summary> | |||
| public string AvatarUrl => API.Endpoints.UserAvatar(UserId, AvatarId); | |||
| /// <summary> Returns the datetime that this user joined this server. </summary> | |||
| public DateTime JoinedAt { get; private set; } | |||
| public bool IsSelfMuted { get; private set; } | |||
| public bool IsSelfDeafened { get; private set; } | |||
| public bool IsServerMuted { get; private set; } | |||
| public bool IsServerDeafened { get; private set; } | |||
| public bool IsServerSuppressed { get; private set; } | |||
| public bool IsSpeaking { get; internal set; } | |||
| public string SessionId { get; private set; } | |||
| public string Token { get; private set; } | |||
| /// <summary> Returns the id for the game this user is currently playing. </summary> | |||
| public string GameId { get; private set; } | |||
| /// <summary> Returns the current status for this user. </summary> | |||
| public string Status { get; private set; } | |||
| /// <summary> Returns the time this user last sent/edited a message, started typing or sent voice data in this server. </summary> | |||
| public DateTime? LastActivityAt { get; private set; } | |||
| /// <summary> Returns the time this user was last seen online in this server. </summary> | |||
| public DateTime LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; | |||
| private DateTime _lastOnline; | |||
| public string UserId { get; } | |||
| [JsonIgnore] | |||
| public User User => _client.Users[UserId]; | |||
| public string ServerId { get; } | |||
| [JsonIgnore] | |||
| public Server Server => _client.Servers[ServerId]; | |||
| public string VoiceChannelId { get; private set; } | |||
| [JsonIgnore] | |||
| public Channel VoiceChannel => _client.Channels[VoiceChannelId]; | |||
| private static readonly string[] _initialRoleIds = new string[0]; | |||
| public string[] RoleIds { get; private set; } | |||
| [JsonIgnore] | |||
| public IEnumerable<Role> Roles => RoleIds.Select(x => _client.Roles[x]); | |||
| /// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Message> Messages => _client.Messages.Where(x => x.UserId == UserId && x.ServerId == ServerId); | |||
| /// <summary> Returns a collection of all channels this user is a member of. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Channel> Channels => _client.Channels.Where(x => x.ServerId == ServerId && x.UserIds.Contains(UserId)); | |||
| internal Member(DiscordClient client, string userId, string serverId) | |||
| { | |||
| _client = client; | |||
| UserId = userId; | |||
| ServerId = serverId ?? _client.Servers.PMServer.Id; | |||
| Status = UserStatus.Offline; | |||
| RoleIds = _initialRoleIds; | |||
| _permissions = new ConcurrentDictionary<string, ChannelPermissions>(); | |||
| } | |||
| internal void OnCached() | |||
| { | |||
| var server = Server; | |||
| if (server != null) | |||
| { | |||
| server.AddMember(this); | |||
| if (UserId == _client.CurrentUserId) | |||
| server.CurrentMember = this; | |||
| } | |||
| var user = User; | |||
| if (user != null) | |||
| { | |||
| if (server == null || !server.IsVirtual) | |||
| user.AddServer(ServerId); | |||
| user.AddRef(); | |||
| _hasRef = true; | |||
| } | |||
| } | |||
| internal void OnUncached() | |||
| { | |||
| var server = Server; | |||
| if (server != null) | |||
| { | |||
| server.RemoveMember(this); | |||
| if (UserId == _client.CurrentUserId) | |||
| server.CurrentMember = null; | |||
| } | |||
| var user = User; | |||
| if (user != null) | |||
| { | |||
| user.RemoveServer(ServerId); | |||
| if (_hasRef) | |||
| user.RemoveRef(); | |||
| } | |||
| _hasRef = false; | |||
| } | |||
| public override string ToString() => UserId; | |||
| internal void Update(UserReference model) | |||
| { | |||
| if (model.Avatar != null) | |||
| AvatarId = model.Avatar; | |||
| if (model.Discriminator != null) | |||
| Discriminator = model.Discriminator; | |||
| if (model.Username != null) | |||
| Name = model.Username; | |||
| } | |||
| internal void Update(MemberInfo model) | |||
| { | |||
| if (model.User != null) | |||
| Update(model.User); | |||
| if (model.JoinedAt.HasValue) | |||
| JoinedAt = model.JoinedAt.Value; | |||
| if (model.Roles != null) | |||
| UpdateRoles(model.Roles); | |||
| UpdatePermissions(); | |||
| } | |||
| internal void Update(ExtendedMemberInfo model) | |||
| { | |||
| Update(model as API.MemberInfo); | |||
| if (model.IsServerDeafened != null) | |||
| IsServerDeafened = model.IsServerDeafened.Value; | |||
| if (model.IsServerMuted != null) | |||
| IsServerMuted = model.IsServerMuted.Value; | |||
| } | |||
| internal void Update(PresenceInfo model) | |||
| { | |||
| if (model.User != null) | |||
| Update(model.User as UserReference); | |||
| if (model.Roles != null) | |||
| UpdateRoles(model.Roles); | |||
| if (model.Status != null && Status != model.Status) | |||
| { | |||
| Status = model.Status; | |||
| if (Status == UserStatus.Offline) | |||
| _lastOnline = DateTime.UtcNow; | |||
| } | |||
| //Allows null | |||
| GameId = model.GameId; | |||
| } | |||
| internal void Update(VoiceMemberInfo model) | |||
| { | |||
| if (model.IsServerDeafened != null) | |||
| IsServerDeafened = model.IsServerDeafened.Value; | |||
| if (model.IsServerMuted != null) | |||
| IsServerMuted = model.IsServerMuted.Value; | |||
| if (model.SessionId != null) | |||
| SessionId = model.SessionId; | |||
| if (model.Token != null) | |||
| Token = model.Token; | |||
| if (model.ChannelId != null) | |||
| VoiceChannelId = model.ChannelId; | |||
| if (model.IsSelfDeafened != null) | |||
| IsSelfDeafened = model.IsSelfDeafened.Value; | |||
| if (model.IsSelfMuted != null) | |||
| IsSelfMuted = model.IsSelfMuted.Value; | |||
| if (model.IsServerSuppressed != null) | |||
| IsServerSuppressed = model.IsServerSuppressed.Value; | |||
| } | |||
| private void UpdateRoles(string[] roleIds) | |||
| { | |||
| //Set roles, with the everyone role added too | |||
| string[] newRoles = new string[roleIds.Length + 1]; | |||
| newRoles[0] = ServerId; //Everyone | |||
| for (int i = 0; i < roleIds.Length; i++) | |||
| newRoles[i + 1] = roleIds[i]; | |||
| RoleIds = newRoles; | |||
| } | |||
| internal void UpdateActivity(DateTime? activity = null) | |||
| { | |||
| if (LastActivityAt == null || activity > LastActivityAt.Value) | |||
| LastActivityAt = activity ?? DateTime.UtcNow; | |||
| } | |||
| internal void UpdatePermissions() | |||
| { | |||
| foreach (var channel in _permissions) | |||
| UpdatePermissions(channel.Key); | |||
| } | |||
| internal void UpdatePermissions(string channelId) | |||
| { | |||
| if (RoleIds == null) return; // We don't have all our data processed yet, this will be called again soon | |||
| var server = Server; | |||
| if (server == null) return; | |||
| var channel = _client.Channels[channelId]; | |||
| ChannelPermissions permissions; | |||
| if (!_permissions.TryGetValue(channelId, out permissions)) return; | |||
| uint newPermissions = 0x0; | |||
| uint oldPermissions = permissions.RawValue; | |||
| if (UserId == server.OwnerId) | |||
| newPermissions = ChannelPermissions.All(channel).RawValue; | |||
| else | |||
| { | |||
| if (channel == null) return; | |||
| var channelOverwrites = channel.PermissionOverwrites; | |||
| //var roles = Roles.OrderBy(x => x.Id); | |||
| var roles = Roles; | |||
| foreach (var serverRole in roles) | |||
| newPermissions |= serverRole.Permissions.RawValue; | |||
| foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Deny.RawValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||
| newPermissions &= ~denyRole.Deny.RawValue; | |||
| foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Allow.RawValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||
| newPermissions |= allowRole.Allow.RawValue; | |||
| foreach (var denyMembers in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Member && x.TargetId == UserId && x.Deny.RawValue != 0)) | |||
| newPermissions &= ~denyMembers.Deny.RawValue; | |||
| foreach (var allowMembers in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Member && x.TargetId == UserId && x.Allow.RawValue != 0)) | |||
| newPermissions |= allowMembers.Allow.RawValue; | |||
| } | |||
| permissions.SetRawValueInternal(newPermissions); | |||
| if (permissions.ManagePermissions) | |||
| permissions.SetRawValueInternal(ChannelPermissions.All(channel).RawValue); | |||
| /*else if (server.DefaultChannelId == channelId) | |||
| permissions.SetBitInternal(PackedPermissions.Text_ReadMessagesBit, true);*/ | |||
| if (permissions.RawValue != oldPermissions) | |||
| channel.InvalidMembersCache(); | |||
| } | |||
| //TODO: Add GetServerPermissions | |||
| public ChannelPermissions GetPermissions(Channel channel) | |||
| => GetPermissions(channel?.Id); | |||
| public ChannelPermissions GetPermissions(string channelId) | |||
| { | |||
| if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
| ChannelPermissions perms; | |||
| if (_permissions.TryGetValue(channelId, out perms)) | |||
| return perms; | |||
| return null; | |||
| } | |||
| internal void AddChannel(string channelId) | |||
| { | |||
| var perms = new ChannelPermissions(); | |||
| perms.Lock(); | |||
| _permissions.TryAdd(channelId, perms); | |||
| UpdatePermissions(channelId); | |||
| } | |||
| internal bool RemoveChannel(string channelId) | |||
| { | |||
| ChannelPermissions ignored; | |||
| return _permissions.TryRemove(channelId, out ignored); | |||
| } | |||
| public bool HasRole(Role role) => RoleIds.Contains(role?.Id); | |||
| public bool HasRole(string roleId) => RoleIds.Contains(roleId); | |||
| } | |||
| } | |||
| @@ -6,7 +6,7 @@ using System.Linq; | |||
| namespace Discord | |||
| { | |||
| public sealed class Message | |||
| public sealed class Message : CachedObject | |||
| { | |||
| public sealed class Attachment : File | |||
| { | |||
| @@ -91,12 +91,9 @@ namespace Discord | |||
| } | |||
| } | |||
| private readonly DiscordClient _client; | |||
| private string _cleanText; | |||
| private bool _gotRef; | |||
| /// <summary> Returns the global unique identifier for this message. </summary> | |||
| public string Id { get; internal set; } | |||
| private string _channelId, _userId; | |||
| /// <summary> Returns the local unique identifier for this message. </summary> | |||
| public string Nonce { get; internal set; } | |||
| @@ -115,7 +112,7 @@ namespace Discord | |||
| public string RawText { get; private set; } | |||
| /// <summary> Returns the content of this message with any special references such as mentions converted. </summary> | |||
| /// <remarks> This value is lazy loaded and only processed on first request. Each subsequent request will pull from cache. </remarks> | |||
| public string Text => _cleanText != null ? _cleanText : (_cleanText = MentionHelper.ConvertToNames(_client, RawText)); | |||
| public string Text => _cleanText != null ? _cleanText : (_cleanText = Mention.ConvertToNames(_client, Server, RawText)); | |||
| /// <summary> Returns the timestamp for when this message was sent. </summary> | |||
| public DateTime Timestamp { get; private set; } | |||
| /// <summary> Returns the timestamp for when this message was last edited. </summary> | |||
| @@ -132,19 +129,14 @@ namespace Discord | |||
| public string[] MentionIds { get; private set; } | |||
| /// <summary> Returns a collection of all users mentioned in this message. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<User> Mentions => MentionIds.Select(x => _client.Users[x]).Where(x => x != null); | |||
| /// <summary> Returns the id of the server containing the channel this message was sent to. </summary> | |||
| public string ServerId => Channel.ServerId; | |||
| public IEnumerable<Member> Mentions { get; internal set; } | |||
| /// <summary> Returns the server containing the channel this message was sent to. </summary> | |||
| [JsonIgnore] | |||
| public Server Server => _client.Servers[Channel.ServerId]; | |||
| /// <summary> Returns the id of the channel this message was sent to. </summary> | |||
| public string ChannelId { get; } | |||
| public Server Server => Channel.Server; | |||
| /// <summary> Returns the channel this message was sent to. </summary> | |||
| [JsonIgnore] | |||
| public Channel Channel => _client.Channels[ChannelId]; | |||
| public Channel Channel { get; private set; } | |||
| /// <summary> Returns true if the current user created this message. </summary> | |||
| public bool IsAuthor => _client.CurrentUserId == UserId; | |||
| @@ -152,42 +144,28 @@ namespace Discord | |||
| public string UserId { get; } | |||
| /// <summary> Returns the author of this message. </summary> | |||
| [JsonIgnore] | |||
| public User User => _client.Users[UserId]; | |||
| /// <summary> Returns the author of this message. </summary> | |||
| [JsonIgnore] | |||
| public Member Member => _client.Members[UserId, ServerId]; | |||
| public Member Member => _client.Members[_userId, Channel.Server.Id]; | |||
| internal Message(DiscordClient client, string id, string channelId, string userId) | |||
| : base(client, id) | |||
| { | |||
| _client = client; | |||
| Id = id; | |||
| ChannelId = channelId; | |||
| UserId = userId; | |||
| _channelId = channelId; | |||
| _userId = userId; | |||
| Attachments = _initialAttachments; | |||
| Embeds = _initialEmbeds; | |||
| MentionIds = _initialMentions; | |||
| } | |||
| internal void OnCached() | |||
| internal override void OnCached() | |||
| { | |||
| var channel = Channel; | |||
| if (channel != null) | |||
| channel.AddMessage(Id); | |||
| var user = User; | |||
| if (user != null) | |||
| { | |||
| user.AddRef(); | |||
| _gotRef = true; | |||
| } | |||
| var channel = _client.Channels[_channelId]; | |||
| channel.AddMessage(Id); | |||
| Channel = channel; | |||
| } | |||
| internal void OnUncached() | |||
| internal override void OnUncached() | |||
| { | |||
| var channel = Channel; | |||
| if (channel != null) | |||
| channel.RemoveMessage(Id); | |||
| var user = User; | |||
| if (user != null && _gotRef) | |||
| user.RemoveRef(); | |||
| _gotRef = false; | |||
| } | |||
| internal void Update(MessageInfo model) | |||
| @@ -236,6 +214,6 @@ namespace Discord | |||
| } | |||
| } | |||
| public override string ToString() => User.ToString() + ": " + RawText; | |||
| public override string ToString() => Member.Name + ": " + RawText; | |||
| } | |||
| } | |||
| @@ -5,12 +5,11 @@ using System.Linq; | |||
| namespace Discord | |||
| { | |||
| public sealed class Role | |||
| public sealed class Role : CachedObject | |||
| { | |||
| private readonly DiscordClient _client; | |||
| /// <summary> Returns the unique identifier for this role. </summary> | |||
| public string Id { get; } | |||
| private readonly string _serverId; | |||
| private Server _server; | |||
| /// <summary> Returns the name of this role. </summary> | |||
| public string Name { get; private set; } | |||
| /// <summary> If true, this role is displayed isolated from other users. </summary> | |||
| @@ -24,27 +23,21 @@ namespace Discord | |||
| /// <summary> Returns the the permissions contained by this role. </summary> | |||
| public ServerPermissions Permissions { get; } | |||
| /// <summary> Returns the id of the server this role is a member of. </summary> | |||
| public string ServerId { get; } | |||
| /// <summary> Returns the server this role is a member of. </summary> | |||
| [JsonIgnore] | |||
| public Server Server => _client.Servers[ServerId]; | |||
| public Server Server => _server; | |||
| /// <summary> Returns true if this is the role representing all users in a server. </summary> | |||
| public bool IsEveryone => Id == ServerId; | |||
| /// <summary> Returns a list of the ids of all members in this role. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<string> MemberIds => IsEveryone ? Server.UserIds : Server.Members.Where(x => x.RoleIds.Contains(Id)).Select(x => x.UserId); | |||
| public bool IsEveryone => Id == _serverId; | |||
| /// <summary> Returns a list of all members in this role. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Member> Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.RoleIds.Contains(Id)); | |||
| public IEnumerable<Member> Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this)); | |||
| internal Role(DiscordClient client, string id, string serverId) | |||
| : base(client, id) | |||
| { | |||
| _client = client; | |||
| Id = id; | |||
| ServerId = serverId; | |||
| _serverId = serverId; | |||
| Permissions = new ServerPermissions(0); | |||
| Permissions.Lock(); | |||
| Color = new Color(0); | |||
| @@ -53,18 +46,17 @@ namespace Discord | |||
| if (IsEveryone) | |||
| Position = int.MinValue; | |||
| } | |||
| internal void OnCached() | |||
| internal override void OnCached() | |||
| { | |||
| var server = Server; | |||
| if (server != null) | |||
| server.AddRole(Id); | |||
| _server = _client.Servers[_serverId]; | |||
| _server.AddRole(this); | |||
| } | |||
| internal void OnUncached() | |||
| internal override void OnUncached() | |||
| { | |||
| var server = Server; | |||
| if (server != null) | |||
| server.RemoveRole(Id); | |||
| } | |||
| if (_server != null) | |||
| _server.RemoveRole(this); | |||
| _server = null; | |||
| } | |||
| internal void Update(RoleInfo model) | |||
| { | |||
| @@ -7,13 +7,16 @@ using System.Linq; | |||
| namespace Discord | |||
| { | |||
| public sealed class Server | |||
| public sealed class Server : CachedObject | |||
| { | |||
| private readonly DiscordClient _client; | |||
| private readonly ConcurrentDictionary<string, bool> _bans, _channels, _invites, _members, _roles; | |||
| private readonly ConcurrentDictionary<string, bool> _bans; | |||
| private readonly ConcurrentDictionary<string, Channel> _channels; | |||
| private readonly ConcurrentDictionary<string, Member> _members; | |||
| private readonly ConcurrentDictionary<string, Role> _roles; | |||
| private readonly ConcurrentDictionary<string, Invite> _invites; | |||
| /// <summary> Returns the unique identifier for this server. </summary> | |||
| public string Id { get; } | |||
| private string _ownerId; | |||
| /// <summary> Returns the name of this channel. </summary> | |||
| public string Name { get; private set; } | |||
| /// <summary> Returns the current logged-in user's data for this server. </summary> | |||
| @@ -31,12 +34,10 @@ namespace Discord | |||
| internal string VoiceServer { get; set; }*/ | |||
| /// <summary> Returns true if the current user created this server. </summary> | |||
| public bool IsOwner => _client.CurrentUserId == OwnerId; | |||
| /// <summary> Returns the id of the user that first created this server. </summary> | |||
| public string OwnerId { get; private set; } | |||
| public bool IsOwner => _client.CurrentUserId == _ownerId; | |||
| /// <summary> Returns the user that first created this server. </summary> | |||
| [JsonIgnore] | |||
| public User Owner => _client.Users[OwnerId]; | |||
| public Member Owner => _client.Members[_ownerId, Id]; | |||
| /// <summary> Returns the id of the AFK voice channel for this server (see AFKTimeout). </summary> | |||
| public string AFKChannelId { get; private set; } | |||
| @@ -52,11 +53,8 @@ namespace Discord | |||
| /// <summary> Returns a collection of the ids of all users banned on this server. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<string> BanIds => _bans.Select(x => x.Key); | |||
| /// <summary> Returns a collection of the ids of all channels within this server. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<string> ChannelIds => _channels.Select(x => x.Key); | |||
| public IEnumerable<string> Bans => _bans.Select(x => x.Key); | |||
| /// <summary> Returns a collection of all channels within this server. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Channel> Channels => _channels.Select(x => _client.Channels[x.Key]); | |||
| @@ -67,62 +65,58 @@ namespace Discord | |||
| [JsonIgnore] | |||
| public IEnumerable<Channel> VoiceChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelTypes.Voice); | |||
| /// <summary> Returns a collection of all invite codes to this server. </summary> | |||
| /// <summary> Returns a collection of all invites to this server. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<string> InviteCodes => _invites.Select(x => x.Key); | |||
| /*/// <summary> Returns a collection of all invites to this server. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Invite> Invites => _invites.Select(x => _client.Invites[x.Key]);*/ | |||
| public IEnumerable<Invite> Invites => _invites.Values; | |||
| /// <summary> Returns a collection of all users within this server with their server-specific data. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Member> Members => _members.Select(x => _client.Members[x.Key, Id]); | |||
| /// <summary> Returns a collection of the ids of all users within this server. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<string> UserIds => _members.Select(x => x.Key); | |||
| /// <summary> Returns a collection of all users within this server. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<User> Users => _members.Select(x => _client.Users[x.Key]); | |||
| /// <summary> Return the id of the role representing all users in a server. </summary> | |||
| public string EveryoneRoleId => Id; | |||
| /// <summary> Return the the role representing all users in a server. </summary> | |||
| [JsonIgnore] | |||
| public Role EveryoneRole => _client.Roles[EveryoneRoleId]; | |||
| /// <summary> Returns a collection of the ids of all roles within this server. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<string> RoleIds => _roles.Select(x => x.Key); | |||
| /// <summary> Returns a collection of all roles within this server. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Role> Roles => _roles.Select(x => _client.Roles[x.Key]); | |||
| internal Server(DiscordClient client, string id) | |||
| : base(client, id) | |||
| { | |||
| _client = client; | |||
| Id = id; | |||
| //Global Cache | |||
| _channels = new ConcurrentDictionary<string, Channel>(); | |||
| _members = new ConcurrentDictionary<string, Member>(); | |||
| _roles = new ConcurrentDictionary<string, Role>(); | |||
| //Local Cache | |||
| _bans = new ConcurrentDictionary<string, bool>(); | |||
| _channels = new ConcurrentDictionary<string, bool>(); | |||
| _invites = new ConcurrentDictionary<string, bool>(); | |||
| _members = new ConcurrentDictionary<string, bool>(); | |||
| _roles = new ConcurrentDictionary<string, bool>(); | |||
| } | |||
| internal void OnCached() | |||
| { | |||
| _invites = new ConcurrentDictionary<string, Invite>(); | |||
| } | |||
| internal void OnUncached() | |||
| internal override void OnCached() { } | |||
| internal override void OnUncached() | |||
| { | |||
| //Global Cache | |||
| var channels = _client.Channels; | |||
| foreach (var channelId in ChannelIds) | |||
| channels.TryRemove(channelId); | |||
| foreach (var channel in _channels) | |||
| channels.TryRemove(channel.Key); | |||
| var members = _client.Members; | |||
| foreach (var userId in UserIds) | |||
| members.TryRemove(userId, Id); | |||
| foreach (var user in _members) | |||
| members.TryRemove(user.Key, Id); | |||
| var roles = _client.Roles; | |||
| foreach (var roleId in RoleIds) | |||
| roles.TryRemove(roleId); | |||
| } | |||
| foreach (var role in _roles) | |||
| roles.TryRemove(role.Key); | |||
| //Local Cache | |||
| foreach (var invite in _invites) | |||
| invite.Value.Uncache(); | |||
| _invites.Clear(); | |||
| _bans.Clear(); | |||
| } | |||
| internal void Update(GuildInfo model) | |||
| { | |||
| @@ -136,7 +130,7 @@ namespace Discord | |||
| if (model.Name != null) | |||
| Name = model.Name; | |||
| if (model.OwnerId != null) | |||
| OwnerId = model.OwnerId; | |||
| _ownerId = model.OwnerId; | |||
| if (model.Region != null) | |||
| Region = model.Region; | |||
| @@ -165,8 +159,6 @@ namespace Discord | |||
| var members = _client.Members; | |||
| foreach (var subModel in model.Members) | |||
| { | |||
| var user = users.GetOrAdd(subModel.User.Id); | |||
| user.Update(subModel.User); | |||
| var member = members.GetOrAdd(subModel.User.Id, Id); | |||
| member.Update(subModel); | |||
| } | |||
| @@ -196,62 +188,43 @@ namespace Discord | |||
| return _bans.TryRemove(banId, out ignored); | |||
| } | |||
| internal void AddChannel(string channelId) | |||
| internal void AddChannel(Channel channel) | |||
| { | |||
| _channels.TryAdd(channelId, true); | |||
| _channels.TryAdd(channel.Id, channel); | |||
| foreach (var member in Members) | |||
| member.AddChannel(channelId); | |||
| member.AddChannel(channel); | |||
| } | |||
| internal bool RemoveChannel(string channelId) | |||
| internal void RemoveChannel(Channel channel) | |||
| { | |||
| bool ignored; | |||
| foreach (var member in Members) | |||
| member.RemoveChannel(channelId); | |||
| return _channels.TryRemove(channelId, out ignored); | |||
| member.RemoveChannel(channel); | |||
| _channels.TryRemove(channel.Id, out channel); | |||
| } | |||
| internal void AddInvite(string inviteId) | |||
| { | |||
| _invites.TryAdd(inviteId, true); | |||
| } | |||
| internal bool RemoveInvite(string inviteId) | |||
| { | |||
| bool ignored; | |||
| return _invites.TryRemove(inviteId, out ignored); | |||
| } | |||
| internal void AddInvite(Invite invite) => _invites.TryAdd(invite.Id, invite); | |||
| internal void RemoveInvite(Invite invite) => _invites.TryRemove(invite.Id, out invite); | |||
| internal void AddMember(Member member) | |||
| { | |||
| _members.TryAdd(member.UserId, true); | |||
| _members.TryAdd(member.Id, member); | |||
| foreach (var channel in Channels) | |||
| { | |||
| member.AddChannel(channel.Id); | |||
| channel.InvalidatePermissionsCache(member.UserId); | |||
| member.AddChannel(channel); | |||
| channel.InvalidatePermissionsCache(member.Id); | |||
| } | |||
| } | |||
| internal bool RemoveMember(Member member) | |||
| internal void RemoveMember(Member member) | |||
| { | |||
| bool ignored; | |||
| foreach (var channel in Channels) | |||
| { | |||
| member.RemoveChannel(channel.Id); | |||
| channel.InvalidatePermissionsCache(member.UserId); | |||
| member.RemoveChannel(channel); | |||
| channel.InvalidatePermissionsCache(member.Id); | |||
| } | |||
| return _members.TryRemove(member.UserId, out ignored); | |||
| } | |||
| internal bool HasMember(string userId) | |||
| { | |||
| return _members.ContainsKey(userId); | |||
| _members.TryRemove(member.Id, out member); | |||
| } | |||
| internal void HasMember(Member user) => _members.ContainsKey(user.Id); | |||
| internal void AddRole(string roleId) | |||
| { | |||
| _roles.TryAdd(roleId, true); | |||
| } | |||
| internal bool RemoveRole(string roleId) | |||
| { | |||
| bool ignored; | |||
| return _roles.TryRemove(roleId, out ignored); | |||
| } | |||
| internal void AddRole(Role role) => _roles.TryAdd(role.Id, role); | |||
| internal void RemoveRole(Role role) => _roles.TryRemove(role.Id, out role); | |||
| } | |||
| } | |||
| @@ -1,20 +1,21 @@ | |||
| using Discord.API; | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Concurrent; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Threading; | |||
| namespace Discord | |||
| { | |||
| public sealed class User | |||
| public class Member : CachedObject | |||
| { | |||
| private readonly DiscordClient _client; | |||
| private readonly ConcurrentDictionary<string, bool> _servers; | |||
| private int _refCount; | |||
| private static readonly string[] _initialRoleIds = new string[0]; | |||
| private ConcurrentDictionary<string, Channel> _channels; | |||
| private ConcurrentDictionary<string, ChannelPermissions> _permissions; | |||
| private bool _hasRef; | |||
| private string[] _roleIds; | |||
| /// <summary> Returns the unique identifier for this user. </summary> | |||
| public string Id { get; } | |||
| /// <summary> Returns the name of this user on this server. </summary> | |||
| public string Name { get; private set; } | |||
| /// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary> | |||
| @@ -23,59 +24,97 @@ namespace Discord | |||
| public string AvatarId { get; private set; } | |||
| /// <summary> Returns the URL to this user's current avatar. </summary> | |||
| public string AvatarUrl => API.Endpoints.UserAvatar(Id, AvatarId); | |||
| /// <summary> Returns the datetime that this user joined this server. </summary> | |||
| public DateTime JoinedAt { get; private set; } | |||
| public bool IsSelfMuted { get; private set; } | |||
| public bool IsSelfDeafened { get; private set; } | |||
| public bool IsServerMuted { get; private set; } | |||
| public bool IsServerDeafened { get; private set; } | |||
| public bool IsServerSuppressed { get; private set; } | |||
| public bool IsSpeaking { get; internal set; } | |||
| public string SessionId { get; private set; } | |||
| public string Token { get; private set; } | |||
| /// <summary> Returns the id for the game this user is currently playing. </summary> | |||
| public string GameId { get; private set; } | |||
| /// <summary> Returns the current status for this user. </summary> | |||
| public string Status { get; private set; } | |||
| /// <summary> Returns the time this user last sent/edited a message, started typing or sent voice data in this server. </summary> | |||
| public DateTime? LastActivityAt { get; private set; } | |||
| /// <summary> Returns the time this user was last seen online in this server. </summary> | |||
| public DateTime LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; | |||
| private DateTime _lastOnline; | |||
| /// <summary> Returns the email for this user. </summary> | |||
| /// <remarks> This field is only ever populated for the current logged in user. </remarks> | |||
| [JsonIgnore] | |||
| public string Email { get; private set; } | |||
| /// <summary> Returns if the email for this user has been verified. </summary> | |||
| /// <remarks> This field is only ever populated for the current logged in user. </remarks> | |||
| [JsonIgnore] | |||
| public bool? IsVerified { get; private set; } | |||
| internal GlobalUser GlobalUser => _client.Users[Id]; | |||
| /// <summary> Returns the Id of the private messaging channel with this user, if one exists. </summary> | |||
| public string PrivateChannelId { get; internal set; } | |||
| /// <summary> Returns the private messaging channel with this user, if one exists. </summary> | |||
| public string ServerId { get; } | |||
| [JsonIgnore] | |||
| public Channel PrivateChannel => _client.Channels[PrivateChannelId]; | |||
| public Server Server => _client.Servers[ServerId]; | |||
| /// <summary> Returns a collection of all server-specific data for every server this user is a member of. </summary> | |||
| public string VoiceChannelId { get; private set; } | |||
| [JsonIgnore] | |||
| public IEnumerable<Member> Memberships => _servers.Select(x => _client.GetMember(x.Key, Id)); | |||
| /// <summary> Returns a collection of all servers this user is a member of. </summary> | |||
| public Channel VoiceChannel => _client.Channels[VoiceChannelId]; | |||
| [JsonIgnore] | |||
| public IEnumerable<Server> Servers => _servers.Select(x => _client.GetServer(x.Key)); | |||
| /// <summary> Returns a collection of the ids of all servers this user is a member of. </summary> | |||
| public IEnumerable<Role> Roles => _roleIds.Select(x => _client.Roles[x]); | |||
| /// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<string> ServersIds => _servers.Select(x => x.Key); | |||
| /// <summary> Returns a collection of all messages this user has sent that are still in cache. </summary> | |||
| public IEnumerable<Message> Messages => _client.Messages.Where(x => x.UserId == Id && x.Server.Id == ServerId); | |||
| /// <summary> Returns a collection of all channels this user is a member of. </summary> | |||
| [JsonIgnore] | |||
| public IEnumerable<Message> Messages => _client.Messages.Where(x => x.UserId == Id); | |||
| public IEnumerable<Channel> Channels => _client.Channels.Where(x => x.Server.Id == ServerId && x.UserIds.Contains(Id)); | |||
| /// <summary> Returns the id for the game this user is currently playing. </summary> | |||
| /*public string GameId => Memberships.Where(x => x.GameId != null).Select(x => x.GameId).FirstOrDefault(); | |||
| /// <summary> Returns the current status for this user. </summary> | |||
| public string Status => Memberships.OrderByDescending(x => x.StatusSince).Select(x => x.Status).FirstOrDefault(); | |||
| /// <summary> Returns the time this user's status was last changed. </summary> | |||
| public DateTime StatusSince => Memberships.OrderByDescending(x => x.StatusSince).Select(x => x.StatusSince).First(); | |||
| /// <summary> Returns the time this user last sent/edited a message, started typing or sent voice data. </summary> | |||
| public DateTime? LastActivity => Memberships.OrderByDescending(x => x.LastActivity).Select(x => x.LastActivity).FirstOrDefault(); | |||
| /// <summary> Returns the time this user was last seen online. </summary> | |||
| public DateTime? LastOnline => Memberships.OrderByDescending(x => x.LastOnline).Select(x => x.LastOnline).FirstOrDefault();*/ | |||
| internal User(DiscordClient client, string id) | |||
| { | |||
| _client = client; | |||
| Id = id; | |||
| _servers = new ConcurrentDictionary<string, bool>(); | |||
| internal Member(DiscordClient client, string id, string serverId) | |||
| : base(client, id) | |||
| { | |||
| ServerId = serverId ?? _client.Servers.PMServer.Id; | |||
| Status = UserStatus.Offline; | |||
| _roleIds = _initialRoleIds; | |||
| _channels = new ConcurrentDictionary<string, Channel>(); | |||
| _permissions = new ConcurrentDictionary<string, ChannelPermissions>(); | |||
| } | |||
| internal void OnCached() | |||
| internal override void OnCached() | |||
| { | |||
| var server = Server; | |||
| if (server != null) | |||
| { | |||
| server.AddMember(this); | |||
| if (Id == _client.CurrentUserId) | |||
| server.CurrentMember = this; | |||
| } | |||
| var user = GlobalUser; | |||
| if (user != null) | |||
| { | |||
| if (server == null || !server.IsVirtual) | |||
| user.AddServer(ServerId); | |||
| user.AddRef(); | |||
| _hasRef = true; | |||
| } | |||
| } | |||
| internal void OnUncached() | |||
| internal override void OnUncached() | |||
| { | |||
| var server = Server; | |||
| if (server != null) | |||
| { | |||
| server.RemoveMember(this); | |||
| if (Id == _client.CurrentUserId) | |||
| server.CurrentMember = null; | |||
| } | |||
| var user = GlobalUser; | |||
| if (user != null) | |||
| { | |||
| user.RemoveServer(ServerId); | |||
| if (_hasRef) | |||
| user.RemoveRef(); | |||
| } | |||
| _hasRef = false; | |||
| } | |||
| public override string ToString() => Id; | |||
| internal void Update(UserReference model) | |||
| { | |||
| if (model.Avatar != null) | |||
| @@ -85,36 +124,157 @@ namespace Discord | |||
| if (model.Username != null) | |||
| Name = model.Username; | |||
| } | |||
| internal void Update(UserInfo model) | |||
| internal void Update(MemberInfo model) | |||
| { | |||
| if (model.User != null) | |||
| Update(model.User); | |||
| if (model.JoinedAt.HasValue) | |||
| JoinedAt = model.JoinedAt.Value; | |||
| if (model.Roles != null) | |||
| UpdateRoles(model.Roles); | |||
| UpdatePermissions(); | |||
| } | |||
| internal void Update(ExtendedMemberInfo model) | |||
| { | |||
| Update(model as API.MemberInfo); | |||
| if (model.IsServerDeafened != null) | |||
| IsServerDeafened = model.IsServerDeafened.Value; | |||
| if (model.IsServerMuted != null) | |||
| IsServerMuted = model.IsServerMuted.Value; | |||
| } | |||
| internal void Update(PresenceInfo model) | |||
| { | |||
| if (model.User != null) | |||
| Update(model.User as UserReference); | |||
| if (model.Roles != null) | |||
| UpdateRoles(model.Roles); | |||
| if (model.Status != null && Status != model.Status) | |||
| { | |||
| Status = model.Status; | |||
| if (Status == UserStatus.Offline) | |||
| _lastOnline = DateTime.UtcNow; | |||
| } | |||
| //Allows null | |||
| GameId = model.GameId; | |||
| } | |||
| internal void Update(VoiceMemberInfo model) | |||
| { | |||
| Update(model as UserReference); | |||
| if (model.IsServerDeafened != null) | |||
| IsServerDeafened = model.IsServerDeafened.Value; | |||
| if (model.IsServerMuted != null) | |||
| IsServerMuted = model.IsServerMuted.Value; | |||
| if (model.SessionId != null) | |||
| SessionId = model.SessionId; | |||
| if (model.Token != null) | |||
| Token = model.Token; | |||
| if (model.Email != null) | |||
| Email = model.Email; | |||
| if (model.IsVerified != null) | |||
| IsVerified = model.IsVerified; | |||
| if (model.ChannelId != null) | |||
| VoiceChannelId = model.ChannelId; | |||
| if (model.IsSelfDeafened != null) | |||
| IsSelfDeafened = model.IsSelfDeafened.Value; | |||
| if (model.IsSelfMuted != null) | |||
| IsSelfMuted = model.IsSelfMuted.Value; | |||
| if (model.IsServerSuppressed != null) | |||
| IsServerSuppressed = model.IsServerSuppressed.Value; | |||
| } | |||
| private void UpdateRoles(string[] roleIds) | |||
| { | |||
| //Set roles, with the everyone role added too | |||
| string[] newRoles = new string[roleIds.Length + 1]; | |||
| newRoles[0] = ServerId; //Everyone | |||
| for (int i = 0; i < roleIds.Length; i++) | |||
| newRoles[i + 1] = roleIds[i]; | |||
| _roleIds = newRoles; | |||
| } | |||
| public override string ToString() => Name; | |||
| internal void UpdateActivity(DateTime? activity = null) | |||
| { | |||
| if (LastActivityAt == null || activity > LastActivityAt.Value) | |||
| LastActivityAt = activity ?? DateTime.UtcNow; | |||
| } | |||
| internal void AddServer(string serverId) | |||
| internal void UpdatePermissions() | |||
| { | |||
| foreach (var channel in _channels) | |||
| UpdatePermissions(channel.Value); | |||
| } | |||
| internal void UpdatePermissions(Channel channel) | |||
| { | |||
| _servers.TryAdd(serverId, true); | |||
| if (_roleIds == null) return; // We don't have all our data processed yet, this will be called again soon | |||
| var server = Server; | |||
| if (server == null || channel.Server != server) return; | |||
| ChannelPermissions permissions; | |||
| if (!_permissions.TryGetValue(channel.Id, out permissions)) return; | |||
| uint newPermissions = 0x0; | |||
| uint oldPermissions = permissions.RawValue; | |||
| if (Id == server.Owner.Id) | |||
| newPermissions = ChannelPermissions.All(channel).RawValue; | |||
| else | |||
| { | |||
| if (channel == null) return; | |||
| var channelOverwrites = channel.PermissionOverwrites; | |||
| //var roles = Roles.OrderBy(x => x.Id); | |||
| var roles = Roles; | |||
| foreach (var serverRole in roles) | |||
| newPermissions |= serverRole.Permissions.RawValue; | |||
| foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Deny.RawValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||
| newPermissions &= ~denyRole.Deny.RawValue; | |||
| foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Allow.RawValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||
| newPermissions |= allowRole.Allow.RawValue; | |||
| foreach (var denyMembers in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Member && x.TargetId == Id && x.Deny.RawValue != 0)) | |||
| newPermissions &= ~denyMembers.Deny.RawValue; | |||
| foreach (var allowMembers in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Member && x.TargetId == Id && x.Allow.RawValue != 0)) | |||
| newPermissions |= allowMembers.Allow.RawValue; | |||
| } | |||
| permissions.SetRawValueInternal(newPermissions); | |||
| if (permissions.ManagePermissions) | |||
| permissions.SetRawValueInternal(ChannelPermissions.All(channel).RawValue); | |||
| /*else if (server.DefaultChannelId == channelId) | |||
| permissions.SetBitInternal(PackedPermissions.Text_ReadMessagesBit, true);*/ | |||
| if (permissions.RawValue != oldPermissions) | |||
| channel.InvalidMembersCache(); | |||
| } | |||
| internal bool RemoveServer(string serverId) | |||
| //TODO: Add GetServerPermissions | |||
| public ChannelPermissions GetPermissions(Channel channel) | |||
| { | |||
| bool ignored; | |||
| return _servers.TryRemove(serverId, out ignored); | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| ChannelPermissions perms; | |||
| if (_permissions.TryGetValue(channel.Id, out perms)) | |||
| return perms; | |||
| return null; | |||
| } | |||
| internal void AddRef() | |||
| internal void AddChannel(Channel channel) | |||
| { | |||
| Interlocked.Increment(ref _refCount); | |||
| var perms = new ChannelPermissions(); | |||
| perms.Lock(); | |||
| _channels.TryAdd(channel.Id, channel); | |||
| _permissions.TryAdd(channel.Id, perms); | |||
| UpdatePermissions(channel); | |||
| } | |||
| internal void RemoveRef() | |||
| internal void RemoveChannel(Channel channel) | |||
| { | |||
| if (Interlocked.Decrement(ref _refCount) == 0) | |||
| _client.Users.TryRemove(Id); | |||
| ChannelPermissions ignored; | |||
| _channels.TryRemove(channel.Id, out channel); | |||
| _permissions.TryRemove(channel.Id, out ignored); | |||
| } | |||
| public bool HasRole(Role role) | |||
| { | |||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||
| return _roleIds.Contains(role.Id); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -59,14 +59,14 @@ namespace Discord.Tests | |||
| string name = $"#test_{_random.Next()}"; | |||
| AssertEvent<ChannelEventArgs>( | |||
| "ChannelCreated event never received", | |||
| () => channel = _hostClient.CreateChannel(_testServer, name.Substring(1), type).Result, | |||
| async () => channel = await _hostClient.CreateChannel(_testServer, name.Substring(1), type), | |||
| x => _targetBot.ChannelCreated += x, | |||
| x => _targetBot.ChannelCreated -= x, | |||
| (s, e) => e.Channel.Name == name); | |||
| AssertEvent<ChannelEventArgs>( | |||
| "ChannelDestroyed event never received", | |||
| () => _hostClient.DestroyChannel(channel), | |||
| async () => await _hostClient.DestroyChannel(channel), | |||
| x => _targetBot.ChannelDestroyed += x, | |||
| x => _targetBot.ChannelDestroyed -= x, | |||
| (s, e) => e.Channel.Name == name); | |||
| @@ -120,15 +120,15 @@ namespace Discord.Tests | |||
| _observerBot.Disconnect()); | |||
| } | |||
| private static void AssertEvent<TArgs>(string msg, Action action, Action<EventHandler<TArgs>> addEvent, Action<EventHandler<TArgs>> removeEvent, Func<object, TArgs, bool> test = null) | |||
| private static void AssertEvent<TArgs>(string msg, Func<Task> action, Action<EventHandler<TArgs>> addEvent, Action<EventHandler<TArgs>> removeEvent, Func<object, TArgs, bool> test = null) | |||
| { | |||
| AssertEvent(msg, action, addEvent, removeEvent, test, true); | |||
| } | |||
| private static void AssertNoEvent<TArgs>(string msg, Action action, Action<EventHandler<TArgs>> addEvent, Action<EventHandler<TArgs>> removeEvent, Func<object, TArgs, bool> test = null) | |||
| private static void AssertNoEvent<TArgs>(string msg, Func<Task> action, Action<EventHandler<TArgs>> addEvent, Action<EventHandler<TArgs>> removeEvent, Func<object, TArgs, bool> test = null) | |||
| { | |||
| AssertEvent(msg, action, addEvent, removeEvent, test, false); | |||
| } | |||
| private static void AssertEvent<TArgs>(string msg, Action action, Action<EventHandler<TArgs>> addEvent, Action<EventHandler<TArgs>> removeEvent, Func<object, TArgs, bool> test, bool assertTrue) | |||
| private static void AssertEvent<TArgs>(string msg, Func<Task> action, Action<EventHandler<TArgs>> addEvent, Action<EventHandler<TArgs>> removeEvent, Func<object, TArgs, bool> test, bool assertTrue) | |||
| { | |||
| ManualResetEventSlim trigger = new ManualResetEventSlim(false); | |||
| bool result = false; | |||
| @@ -145,8 +145,9 @@ namespace Discord.Tests | |||
| }; | |||
| addEvent(handler); | |||
| action(); | |||
| var task = action(); | |||
| trigger.Wait(EventTimeout); | |||
| task.Wait(); | |||
| removeEvent(handler); | |||
| Assert.AreEqual(assertTrue, result, msg); | |||