@@ -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); | |||