@@ -11,14 +11,10 @@ namespace Discord.Commands | |||||
public string ArgText { get; } | public string ArgText { get; } | ||||
public int? Permissions { get; } | public int? Permissions { get; } | ||||
public string[] Args { get; } | public string[] Args { get; } | ||||
public User User => Message.User; | |||||
public string UserId => Message.UserId; | |||||
public Member Member => Message.Member; | public Member Member => Message.Member; | ||||
public Channel Channel => Message.Channel; | public Channel Channel => Message.Channel; | ||||
public string ChannelId => Message.ChannelId; | |||||
public Server Server => Message.Channel.Server; | 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) | 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 readonly DiscordClient _client; | ||||
private List<Command> _commands; | private List<Command> _commands; | ||||
private Func<User, Server, int> _getPermissions; | |||||
private Func<Member, int> _getPermissions; | |||||
public IEnumerable<Command> Commands => _commands; | public IEnumerable<Command> Commands => _commands; | ||||
@@ -17,7 +17,7 @@ namespace Discord.Commands | |||||
public bool RequireCommandCharInPublic { get; set; } | public bool RequireCommandCharInPublic { get; set; } | ||||
public bool RequireCommandCharInPrivate { 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; | _client = client; | ||||
_getPermissions = getPermissions; | _getPermissions = getPermissions; | ||||
@@ -96,7 +96,7 @@ namespace Discord.Commands | |||||
argText = msg.Substring(args[cmd.Parts.Length].Index); | argText = msg.Substring(args[cmd.Parts.Length].Index); | ||||
//Check Permissions | //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); | var eventArgs = new CommandEventArgs(e.Message, cmd, msg, argText, permissions, newArgs); | ||||
if (permissions < cmd.MinPerms) | if (permissions < cmd.MinPerms) | ||||
{ | { | ||||
@@ -202,6 +202,9 @@ | |||||
<Compile Include="..\Discord.Net\DiscordWSClientConfig.cs"> | <Compile Include="..\Discord.Net\DiscordWSClientConfig.cs"> | ||||
<Link>DiscordWSClientConfig.cs</Link> | <Link>DiscordWSClientConfig.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Helpers\AsyncCollection.cs"> | |||||
<Link>Helpers\AsyncCollection.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Helpers\EpochTime.cs"> | <Compile Include="..\Discord.Net\Helpers\EpochTime.cs"> | ||||
<Link>Helpers\EpochTime.cs</Link> | <Link>Helpers\EpochTime.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -214,14 +217,8 @@ | |||||
<Compile Include="..\Discord.Net\Helpers\Mention.cs"> | <Compile Include="..\Discord.Net\Helpers\Mention.cs"> | ||||
<Link>Helpers\Mention.cs</Link> | <Link>Helpers\Mention.cs</Link> | ||||
</Compile> | </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"> | <Compile Include="..\Discord.Net\Helpers\Shared\TaskHelper.cs"> | ||||
<Link>Helpers\Shared\TaskHelper.cs</Link> | |||||
<Link>Helpers\TaskHelper.cs</Link> | |||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Helpers\TimeoutException.cs"> | <Compile Include="..\Discord.Net\Helpers\TimeoutException.cs"> | ||||
<Link>Helpers\TimeoutException.cs</Link> | <Link>Helpers\TimeoutException.cs</Link> | ||||
@@ -229,8 +226,8 @@ | |||||
<Compile Include="..\Discord.Net\HttpException.cs"> | <Compile Include="..\Discord.Net\HttpException.cs"> | ||||
<Link>HttpException.cs</Link> | <Link>HttpException.cs</Link> | ||||
</Compile> | </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> | ||||
<Compile Include="..\Discord.Net\Models\Channel.cs"> | <Compile Include="..\Discord.Net\Models\Channel.cs"> | ||||
<Link>Models\Channel.cs</Link> | <Link>Models\Channel.cs</Link> | ||||
@@ -238,12 +235,12 @@ | |||||
<Compile Include="..\Discord.Net\Models\Color.cs"> | <Compile Include="..\Discord.Net\Models\Color.cs"> | ||||
<Link>Models\Color.cs</Link> | <Link>Models\Color.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Models\GlobalUser.cs"> | |||||
<Link>Models\GlobalUser.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Models\Invite.cs"> | <Compile Include="..\Discord.Net\Models\Invite.cs"> | ||||
<Link>Models\Invite.cs</Link> | <Link>Models\Invite.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Models\Member.cs"> | |||||
<Link>Models\Member.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Models\Message.cs"> | <Compile Include="..\Discord.Net\Models\Message.cs"> | ||||
<Link>Models\Message.cs</Link> | <Link>Models\Message.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -110,11 +110,11 @@ namespace Discord | |||||
} | } | ||||
//Invites | //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)); | 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); | return _rest.Post<CreateInviteResponse>(Endpoints.ChannelInvites(channelId), request); | ||||
} | } | ||||
public Task<GetInviteResponse> GetInvite(string inviteIdOrXkcd) | public Task<GetInviteResponse> GetInvite(string inviteIdOrXkcd) | ||||
@@ -7,14 +7,11 @@ namespace Discord | |||||
{ | { | ||||
public class BanEventArgs : EventArgs | public class BanEventArgs : EventArgs | ||||
{ | { | ||||
public User User { get; } | |||||
public string UserId { get; } | public string UserId { get; } | ||||
public Server Server { 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; | UserId = userId; | ||||
Server = server; | Server = server; | ||||
} | } | ||||
@@ -26,57 +23,31 @@ namespace Discord | |||||
private void RaiseBanAdded(string userId, Server server) | private void RaiseBanAdded(string userId, Server server) | ||||
{ | { | ||||
if (BanAdded != null) | 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; | public event EventHandler<BanEventArgs> BanRemoved; | ||||
private void RaiseBanRemoved(string userId, Server server) | private void RaiseBanRemoved(string userId, Server server) | ||||
{ | { | ||||
if (BanRemoved != null) | 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> | /// <summary> Bans a user from the provided server. </summary> | ||||
public Task Ban(Member member) | 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(); | 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> | /// <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(); | 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) { } | catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | ||||
} | } | ||||
} | } |
@@ -18,9 +18,7 @@ namespace Discord | |||||
public class ChannelEventArgs : EventArgs | public class ChannelEventArgs : EventArgs | ||||
{ | { | ||||
public Channel Channel { get; } | public Channel Channel { get; } | ||||
public string ChannelId => Channel.Id; | |||||
public Server Server => Channel.Server; | public Server Server => Channel.Server; | ||||
public string ServerId => Channel.ServerId; | |||||
internal ChannelEventArgs(Channel channel) { Channel = channel; } | internal ChannelEventArgs(Channel channel) { Channel = channel; } | ||||
} | } | ||||
@@ -49,29 +47,30 @@ namespace Discord | |||||
RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); | RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); | ||||
} | } | ||||
/// <summary> Returns the channel with the specified id, or null if none was found. </summary> | /// <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> | /// <summary> Returns all channels with the specified server and name. </summary> | ||||
/// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | /// <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; | IEnumerable<Channel> result; | ||||
if (name.StartsWith("#")) | if (name.StartsWith("#")) | ||||
{ | { | ||||
string name2 = name.Substring(1); | 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, name, StringComparison.OrdinalIgnoreCase) || | ||||
string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
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, name, StringComparison.OrdinalIgnoreCase)); | ||||
} | } | ||||
@@ -82,55 +81,44 @@ namespace Discord | |||||
} | } | ||||
/// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | /// <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 (name == null) throw new ArgumentNullException(nameof(name)); | ||||
if (type == null) throw new ArgumentNullException(nameof(type)); | 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); | var channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); | ||||
channel.Update(response); | channel.Update(response); | ||||
return channel; | 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> | /// <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(); | CheckReady(); | ||||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||||
Channel channel = null; | Channel channel = null; | ||||
if (user != null) | |||||
channel = user.PrivateChannel; | |||||
if (member != null) | |||||
channel = member.GlobalUser.PrivateChannel; | |||||
if (channel == null) | 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 = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); | ||||
channel.Update(response); | channel.Update(response); | ||||
} | } | ||||
return channel; | 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> | /// <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) | public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null) | ||||
{ | { | ||||
CheckReady(); | |||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
CheckReady(); | |||||
await _api.EditChannel(channel.Id, name: name, topic: topic); | await _api.EditChannel(channel.Id, name: name, topic: topic); | ||||
@@ -155,34 +143,29 @@ namespace Discord | |||||
channels[i] = channels[i - 1]; | channels[i] = channels[i - 1]; | ||||
channels[newPos] = channel; | 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 (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> | /// <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(); | 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) { } | 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; | ||||
using System.Net; | using System.Net; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -14,6 +13,14 @@ namespace Discord | |||||
CheckReady(); | CheckReady(); | ||||
if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); | 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 response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); | ||||
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); | var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); | ||||
invite.Update(response); | 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="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> | /// <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) | 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> | /// <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="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="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="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> | /// <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 (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); | ||||
if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); | 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); | var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); | ||||
invite.Update(response); | invite.Update(response); | ||||
return invite; | return invite; | ||||
} | } | ||||
/// <summary> Deletes the provided invite. </summary> | /// <summary> Deletes the provided invite. </summary> | ||||
public async Task DestroyInvite(string inviteId) | |||||
public async Task DestroyInvite(Invite invite) | |||||
{ | { | ||||
CheckReady(); | 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) { } | catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | ||||
} | } | ||||
@@ -75,22 +74,5 @@ namespace Discord | |||||
return _api.AcceptInvite(invite.Id); | 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 class MemberEventArgs : EventArgs | ||||
{ | { | ||||
public Member Member { get; } | 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 Server Server => Member.Server; | ||||
public string ServerId => Member.ServerId; | public string ServerId => Member.ServerId; | ||||
@@ -66,80 +65,65 @@ namespace Discord | |||||
if (UserIsSpeaking != null) | if (UserIsSpeaking != null) | ||||
RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new MemberIsSpeakingEventArgs(member, channel, isSpeaking))); | RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new MemberIsSpeakingEventArgs(member, channel, isSpeaking))); | ||||
} | } | ||||
private Member _currentUser; | |||||
internal Members Members => _members; | internal Members Members => _members; | ||||
private readonly 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> | /// <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> | /// <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> | /// <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> | /// <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> | /// <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 (server == null) throw new ArgumentNullException(nameof(server)); | ||||
if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
CheckReady(); | |||||
if (name.StartsWith("@")) | |||||
IEnumerable<Member> query; | |||||
if (!exactMatch && name.StartsWith("@")) | |||||
{ | { | ||||
string name2 = name.Substring(1); | 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 | 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(); | 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 class MessageEventArgs : EventArgs | ||||
{ | { | ||||
public Message Message { get; } | public Message Message { get; } | ||||
public string MessageId => Message.Id; | |||||
public Member Member => Message.Member; | public Member Member => Message.Member; | ||||
public Channel Channel => Message.Channel; | public Channel Channel => Message.Channel; | ||||
public string ChannelId => Message.ChannelId; | |||||
public Server Server => Message.Server; | 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; } | internal MessageEventArgs(Message msg) { Message = msg; } | ||||
} | } | ||||
@@ -74,165 +69,127 @@ namespace Discord | |||||
public Message GetMessage(string id) => _messages[id]; | 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> | /// <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(); | 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 (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
if (text == null) throw new ArgumentNullException(nameof(text)); | 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(); | 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> | /// <summary> Sends a file to the provided channel. </summary> | ||||
public Task SendFile(Channel channel, string filePath) | 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)); | 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> | /// <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> | /// <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(); | 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) | if (text != null && text.Length > MaxMessageSize) | ||||
text = text.Substring(0, 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> | /// <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(); | CheckReady(); | ||||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||||
if (msgId == null) throw new ArgumentNullException(nameof(msgId)); | |||||
try | 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) { } | 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(); | CheckReady(); | ||||
if (msgIds == null) throw new ArgumentNullException(nameof(msgIds)); | |||||
foreach (var msgId in msgIds) | |||||
foreach (var message in messages) | |||||
{ | { | ||||
try | 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) { } | catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | ||||
} | } | ||||
} | } | ||||
/// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | /// <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) 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) | if (channel != null && channel.Type == ChannelTypes.Text) | ||||
{ | { | ||||
try | try | ||||
@@ -283,7 +240,7 @@ namespace Discord | |||||
SendMessageResponse response = null; | SendMessageResponse response = null; | ||||
try | 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 (WebException) { break; } | ||||
catch (HttpException) { hasFailed = true; } | catch (HttpException) { hasFailed = true; } | ||||
@@ -8,47 +8,13 @@ namespace Discord | |||||
public partial class DiscordClient | public partial class DiscordClient | ||||
{ | { | ||||
public Task SetChannelUserPermissions(Channel channel, Member member, ChannelPermissions allow = null, ChannelPermissions deny = null) | 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) | 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) | public Task SetChannelRolePermissions(Channel channel, Role role, ChannelPermissions allow = null, ChannelPermissions deny = null) | ||||
=> SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, allow, deny); | => 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) | public Task SetChannelRolePermissions(Channel channel, Role role, DualChannelPermissions permissions = null) | ||||
=> SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, permissions?.Allow, permissions?.Deny); | => 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) | private async Task SetChannelPermissions(Channel channel, string targetId, string targetType, ChannelPermissions allow = null, ChannelPermissions deny = null) | ||||
{ | { | ||||
CheckReady(); | CheckReady(); | ||||
@@ -103,34 +69,23 @@ namespace Discord | |||||
} | } | ||||
public Task RemoveChannelUserPermissions(Channel channel, Member member) | 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) | 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 (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 | try | ||||
{ | { | ||||
var perms = channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).FirstOrDefault(); | var perms = channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).FirstOrDefault(); | ||||
@@ -33,9 +33,7 @@ namespace Discord | |||||
public class RoleEventArgs : EventArgs | public class RoleEventArgs : EventArgs | ||||
{ | { | ||||
public Role Role { get; } | public Role Role { get; } | ||||
public string RoleId => Role.Id; | |||||
public Server Server => Role.Server; | public Server Server => Role.Server; | ||||
public string ServerId => Role.ServerId; | |||||
internal RoleEventArgs(Role role) { Role = role; } | internal RoleEventArgs(Role role) { Role = role; } | ||||
} | } | ||||
@@ -68,23 +66,20 @@ namespace Discord | |||||
public Role GetRole(string id) => _roles[id]; | public Role GetRole(string id) => _roles[id]; | ||||
/// <summary> Returns all roles with the specified server and name. </summary> | /// <summary> Returns all roles with the specified server and name. </summary> | ||||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | /// <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 == null) throw new ArgumentNullException(nameof(name)); | ||||
if (name.StartsWith("@")) | if (name.StartsWith("@")) | ||||
{ | { | ||||
string name2 = name.Substring(1); | 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)); | string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
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, name, StringComparison.OrdinalIgnoreCase)); | ||||
} | } | ||||
} | } | ||||
@@ -106,16 +101,14 @@ namespace Discord | |||||
return role; | 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) | 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)); | if (role == null) throw new ArgumentNullException(nameof(role)); | ||||
CheckReady(); | |||||
//TODO: check this null workaround later, should be fixed on Discord's end soon | //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, | name: name ?? role.Name, | ||||
permissions: permissions?.RawValue ?? role.Permissions.RawValue, | permissions: permissions?.RawValue ?? role.Permissions.RawValue, | ||||
color: color?.RawValue, | color: color?.RawValue, | ||||
@@ -142,40 +135,26 @@ namespace Discord | |||||
roles[i] = roles[i - 1]; | roles[i] = roles[i - 1]; | ||||
roles[newPos] = role; | 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) | 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(); | 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 (roles == null) throw new ArgumentNullException(nameof(roles)); | ||||
if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); | 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 | namespace Discord | ||||
{ | { | ||||
internal sealed class Users : AsyncCollection<User> | |||||
internal sealed class Users : AsyncCollection<GlobalUser> | |||||
{ | { | ||||
public Users(DiscordClient client, object writerLock) | public Users(DiscordClient client, object writerLock) | ||||
: base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } | : 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 | public partial class DiscordClient | ||||
@@ -36,11 +28,11 @@ namespace Discord | |||||
if (UserRemoved != null) | if (UserRemoved != null) | ||||
RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new MemberEventArgs(member))); | 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; | public event EventHandler<MemberEventArgs> MemberUpdated; | ||||
private void RaiseMemberUpdated(Member member) | private void RaiseMemberUpdated(Member member) | ||||
@@ -65,55 +57,14 @@ namespace Discord | |||||
internal Users Users => _users; | internal Users Users => _users; | ||||
private readonly 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 = "", | public Task<EditUserResponse> EditProfile(string currentPassword = "", | ||||
string username = null, string email = null, string password = null, | string username = null, string email = null, string password = null, | ||||
ImageType avatarType = ImageType.Png, byte[] avatar = null) | ImageType avatarType = ImageType.Png, byte[] avatar = null) | ||||
{ | { | ||||
if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); | 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); | avatarType: avatarType, avatar: avatar); | ||||
} | } | ||||
@@ -47,18 +47,13 @@ namespace Discord | |||||
return client; | 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 | 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; | return client; | ||||
} | } | ||||
@@ -10,9 +10,9 @@ using System.Threading.Tasks; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> Provides a connection to the DiscordApp service. </summary> | /// <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 Random _rand; | ||||
private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
private readonly ConcurrentQueue<Message> _pendingMessages; | private readonly ConcurrentQueue<Message> _pendingMessages; | ||||
@@ -24,6 +24,9 @@ namespace Discord | |||||
public new DiscordClientConfig Config => _config as DiscordClientConfig; | 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> | /// <summary> Initializes a new instance of the DiscordClient class. </summary> | ||||
public DiscordClient(DiscordClientConfig config = null) | public DiscordClient(DiscordClientConfig config = null) | ||||
: base(config ?? new DiscordClientConfig()) | : 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) | if (_config.LogLevel >= LogMessageSeverity.Info) | ||||
{ | { | ||||
ServerCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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, | 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) | if (_config.LogLevel >= LogMessageSeverity.Verbose) | ||||
{ | { | ||||
UserIsTyping += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | 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, | 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, | 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, | 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) => | _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})"); | RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms ({e.Payload})"); | ||||
else | else | ||||
RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms"); | 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) | 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"); | _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"); | _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"); | _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"); | _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.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}"); | _servers.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Server {e.Item.Id}"); | ||||
@@ -295,7 +293,7 @@ namespace Discord | |||||
case "READY": //Resync | case "READY": //Resync | ||||
{ | { | ||||
var data = e.Payload.ToObject<ReadyEvent>(_serializer); | 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); | _currentUser.Update(data.User); | ||||
foreach (var model in data.Guilds) | foreach (var model in data.Guilds) | ||||
{ | { | ||||
@@ -307,7 +305,7 @@ namespace Discord | |||||
} | } | ||||
foreach (var model in data.PrivateChannels) | 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); | user.Update(model.Recipient); | ||||
var channel = _channels.GetOrAdd(model.Id, null, user.Id); | var channel = _channels.GetOrAdd(model.Id, null, user.Id); | ||||
channel.Update(model); | channel.Update(model); | ||||
@@ -362,9 +360,9 @@ namespace Discord | |||||
Channel channel; | Channel channel; | ||||
if (data.IsPrivate) | 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 | else | ||||
channel = _channels.GetOrAdd(data.Id, data.GuildId, null); | channel = _channels.GetOrAdd(data.Id, data.GuildId, null); | ||||
@@ -396,8 +394,6 @@ namespace Discord | |||||
case "GUILD_MEMBER_ADD": | case "GUILD_MEMBER_ADD": | ||||
{ | { | ||||
var data = e.Payload.ToObject<MemberAddEvent>(_serializer); | 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); | var member = _members.GetOrAdd(data.User.Id, data.GuildId); | ||||
member.Update(data); | member.Update(data); | ||||
if (Config.TrackActivity) | if (Config.TrackActivity) | ||||
@@ -433,7 +429,7 @@ namespace Discord | |||||
role.Update(data.Data); | role.Update(data.Data); | ||||
var server = _servers[data.GuildId]; | var server = _servers[data.GuildId]; | ||||
if (server != null) | if (server != null) | ||||
server.AddRole(data.Data.Id); | |||||
server.AddRole(role); | |||||
RaiseRoleUpdated(role); | RaiseRoleUpdated(role); | ||||
} | } | ||||
break; | break; | ||||
@@ -442,19 +438,23 @@ namespace Discord | |||||
var data = e.Payload.ToObject<RoleUpdateEvent>(_serializer); | var data = e.Payload.ToObject<RoleUpdateEvent>(_serializer); | ||||
var role = _roles[data.Data.Id]; | var role = _roles[data.Data.Id]; | ||||
if (role != null) | if (role != null) | ||||
{ | |||||
role.Update(data.Data); | role.Update(data.Data); | ||||
RaiseRoleUpdated(role); | |||||
RaiseRoleUpdated(role); | |||||
} | |||||
} | } | ||||
break; | break; | ||||
case "GUILD_ROLE_DELETE": | case "GUILD_ROLE_DELETE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<RoleDeleteEvent>(_serializer); | 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); | var role = _roles.TryRemove(data.RoleId); | ||||
if (role != null) | if (role != null) | ||||
{ | |||||
RaiseRoleDeleted(role); | RaiseRoleDeleted(role); | ||||
var server = _servers[data.GuildId]; | |||||
if (server != null) | |||||
server.RemoveRole(role); | |||||
} | |||||
} | } | ||||
break; | break; | ||||
@@ -485,7 +485,7 @@ namespace Discord | |||||
var data = e.Payload.ToObject<MessageCreateEvent>(_serializer); | var data = e.Payload.ToObject<MessageCreateEvent>(_serializer); | ||||
Message msg = null; | Message msg = null; | ||||
bool isAuthor = data.Author.Id == CurrentUserId; | |||||
bool isAuthor = data.Author.Id == _userId; | |||||
bool hasFinishedSending = false; | bool hasFinishedSending = false; | ||||
if (Config.UseMessageQueue && isAuthor && data.Nonce != null) | if (Config.UseMessageQueue && isAuthor && data.Nonce != null) | ||||
{ | { | ||||
@@ -566,7 +566,7 @@ namespace Discord | |||||
var channel = _channels[data.ChannelId]; | var channel = _channels[data.ChannelId]; | ||||
if (channel != null) | if (channel != null) | ||||
{ | { | ||||
var user = _members[data.UserId, channel.ServerId]; | |||||
var user = _members[data.UserId, channel.Server.Id]; | |||||
if (user != null) | if (user != null) | ||||
{ | { | ||||
@@ -577,7 +577,7 @@ namespace Discord | |||||
{ | { | ||||
if (!channel.IsPrivate) | if (!channel.IsPrivate) | ||||
{ | { | ||||
var member = _members[data.UserId, channel.ServerId]; | |||||
var member = _members[data.UserId, channel.Server.Id]; | |||||
if (member != null) | if (member != null) | ||||
member.UpdateActivity(); | member.UpdateActivity(); | ||||
} | } | ||||
@@ -612,7 +612,7 @@ namespace Discord | |||||
if (user != null) | if (user != null) | ||||
{ | { | ||||
user.Update(data); | user.Update(data); | ||||
RaiseUserUpdated(user); | |||||
RaiseProfileUpdated(user); | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
@@ -17,7 +17,7 @@ namespace Discord | |||||
await _voiceSocket.SetChannel(_voiceServerId, channelId).ConfigureAwait(false); | await _voiceSocket.SetChannel(_voiceServerId, channelId).ConfigureAwait(false); | ||||
_dataSocket.SendJoinVoice(_voiceServerId, channelId); | _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> | /// <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> | /// <param name="count">Number of bytes in this frame. </param> | ||||
void IDiscordVoiceClient.SendVoicePCM(byte[] data, int count) | void IDiscordVoiceClient.SendVoicePCM(byte[] data, int count) | ||||
{ | { | ||||
CheckReady(checkVoice: true); | |||||
if (data == null) throw new ArgumentException(nameof(data)); | if (data == null) throw new ArgumentException(nameof(data)); | ||||
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); | 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> | /// <summary> Clears the PCM buffer. </summary> | ||||
void IDiscordVoiceClient.ClearVoicePCM() | void IDiscordVoiceClient.ClearVoicePCM() | ||||
@@ -20,27 +20,21 @@ namespace Discord | |||||
/// <summary> Provides a minimalistic websocket connection to the Discord service. </summary> | /// <summary> Provides a minimalistic websocket connection to the Discord service. </summary> | ||||
public partial class DiscordWSClient | public partial class DiscordWSClient | ||||
{ | { | ||||
internal readonly DataWebSocket _dataSocket; | |||||
internal readonly VoiceWebSocket _voiceSocket; | |||||
protected readonly DiscordWSClientConfig _config; | |||||
protected readonly ManualResetEvent _disconnectedEvent; | protected readonly ManualResetEvent _disconnectedEvent; | ||||
protected readonly ManualResetEventSlim _connectedEvent; | 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 _gateway, _token; | ||||
protected string _voiceServerId; | |||||
protected string _userId, _voiceServerId; | |||||
private Task _runTask; | private Task _runTask; | ||||
protected ExceptionDispatchInfo _disconnectReason; | |||||
private bool _wasDisconnectUnexpected; | 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> | /// <summary> Returns the current connection state of this client. </summary> | ||||
public DiscordClientState State => (DiscordClientState)_state; | public DiscordClientState State => (DiscordClientState)_state; | ||||
@@ -56,15 +50,13 @@ namespace Discord | |||||
_config = config ?? new DiscordWSClientConfig(); | _config = config ?? new DiscordWSClientConfig(); | ||||
_config.Lock(); | _config.Lock(); | ||||
_enableVoice = _config.EnableVoice; | |||||
_state = (int)DiscordClientState.Disconnected; | _state = (int)DiscordClientState.Disconnected; | ||||
_cancelToken = new CancellationToken(true); | _cancelToken = new CancellationToken(true); | ||||
_disconnectedEvent = new ManualResetEvent(true); | _disconnectedEvent = new ManualResetEvent(true); | ||||
_connectedEvent = new ManualResetEventSlim(false); | _connectedEvent = new ManualResetEventSlim(false); | ||||
_dataSocket = CreateDataSocket(); | _dataSocket = CreateDataSocket(); | ||||
if (_enableVoice) | |||||
if (_config.EnableVoice) | |||||
_voiceSocket = CreateVoiceSocket(); | _voiceSocket = CreateVoiceSocket(); | ||||
} | } | ||||
internal DiscordWSClient(DiscordWSClientConfig config = null, string voiceServerId = null) | internal DiscordWSClient(DiscordWSClientConfig config = null, string voiceServerId = null) | ||||
@@ -247,7 +239,7 @@ namespace Discord | |||||
protected virtual async Task Cleanup() | protected virtual async Task Cleanup() | ||||
{ | { | ||||
if (_enableVoice) | |||||
if (_config.EnableVoice) | |||||
{ | { | ||||
string voiceServerId = _voiceSocket.CurrentServerId; | string voiceServerId = _voiceSocket.CurrentServerId; | ||||
if (voiceServerId != null) | if (voiceServerId != null) | ||||
@@ -256,7 +248,7 @@ namespace Discord | |||||
} | } | ||||
await _dataSocket.Disconnect().ConfigureAwait(false); | await _dataSocket.Disconnect().ConfigureAwait(false); | ||||
_currentUserId = null; | |||||
_userId = null; | |||||
_gateway = null; | _gateway = null; | ||||
_token = null; | _token = null; | ||||
} | } | ||||
@@ -286,7 +278,7 @@ namespace Discord | |||||
throw new InvalidOperationException("The client is connecting."); | throw new InvalidOperationException("The client is connecting."); | ||||
} | } | ||||
if (checkVoice && !_enableVoice) | |||||
if (checkVoice && !_config.EnableVoice) | |||||
throw new InvalidOperationException("Voice is not enabled for this client."); | throw new InvalidOperationException("Voice is not enabled for this client."); | ||||
} | } | ||||
protected void RaiseEvent(string name, Action action) | protected void RaiseEvent(string name, Action action) | ||||
@@ -307,17 +299,17 @@ namespace Discord | |||||
switch (e.Type) | switch (e.Type) | ||||
{ | { | ||||
case "READY": | case "READY": | ||||
_currentUserId = e.Payload["user"].Value<string>("id"); | |||||
_userId = e.Payload["user"].Value<string>("id"); | |||||
break; | break; | ||||
case "VOICE_SERVER_UPDATE": | case "VOICE_SERVER_UPDATE": | ||||
{ | { | ||||
string guildId = e.Payload.Value<string>("guild_id"); | 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"); | string token = e.Payload.Value<string>("token"); | ||||
_voiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; | _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; | break; | ||||
@@ -7,7 +7,7 @@ using System.Linq; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
internal abstract class AsyncCollection<TValue> : IEnumerable<TValue> | internal abstract class AsyncCollection<TValue> : IEnumerable<TValue> | ||||
where TValue : class | |||||
where TValue : CachedObject | |||||
{ | { | ||||
private readonly object _writerLock; | 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 | 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> | /// <summary> Returns the string used to create a user mention. </summary> | ||||
public static string User(Member member) | 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> | /// <summary> Returns the string used to create a channel mention. </summary> | ||||
public static string Channel(Channel channel) | public static string Channel(Channel channel) | ||||
=> $"<#{channel.Id}>"; | => $"<#{channel.Id}>"; | ||||
/// <summary> Returns the string used to create a channel mention. </summary> | /// <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() | public static string Everyone() | ||||
=> $"@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 | namespace Discord | ||||
{ | { | ||||
public sealed class Channel | |||||
public sealed class Channel : CachedObject | |||||
{ | { | ||||
public sealed class PermissionOverwrite | public sealed class PermissionOverwrite | ||||
{ | { | ||||
@@ -21,44 +21,37 @@ namespace Discord | |||||
TargetType = targetType; | TargetType = targetType; | ||||
TargetId = targetId; | TargetId = targetId; | ||||
Allow = new ChannelPermissions(allow); | Allow = new ChannelPermissions(allow); | ||||
Deny = new ChannelPermissions(deny); | |||||
Allow.Lock(); | Allow.Lock(); | ||||
Deny = new ChannelPermissions(deny); | |||||
Deny.Lock(); | Deny.Lock(); | ||||
} | } | ||||
} | } | ||||
private readonly DiscordClient _client; | |||||
private readonly ConcurrentDictionary<string, bool> _messages; | private readonly ConcurrentDictionary<string, bool> _messages; | ||||
private bool _areMembersStale; | 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> | /// <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> | /// <summary> Returns the topic associated with this channel. </summary> | ||||
public string Topic { get; private set; } | public string Topic { get; private set; } | ||||
/// <summary> Returns the position of this channel in the channel list for this server. </summary> | /// <summary> Returns the position of this channel in the channel list for this server. </summary> | ||||
public int Position { get; private set; } | 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> | /// <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> | /// <summary> Returns the type of this channel (see ChannelTypes). </summary> | ||||
public string Type { get; private set; } | 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> | /// <summary> Returns the server containing this channel. </summary> | ||||
[JsonIgnore] | [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. | /// For private chats, returns the target user, otherwise null. | ||||
[JsonIgnore] | [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> | /// <summary> Returns a collection of the IDs of all users with read access to this channel. </summary> | ||||
public IEnumerable<string> UserIds | public IEnumerable<string> UserIds | ||||
@@ -68,7 +61,7 @@ namespace Discord | |||||
if (!_areMembersStale) | if (!_areMembersStale) | ||||
return _userIds; | 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; | _areMembersStale = false; | ||||
return _userIds; | return _userIds; | ||||
} | } | ||||
@@ -76,10 +69,7 @@ namespace Discord | |||||
private string[] _userIds; | private string[] _userIds; | ||||
/// <summary> Returns a collection of all users with read access to this channel. </summary> | /// <summary> Returns a collection of all users with read access to this channel. </summary> | ||||
[JsonIgnore] | [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> | /// <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] | [JsonIgnore] | ||||
@@ -94,64 +84,39 @@ namespace Discord | |||||
public IEnumerable<PermissionOverwrite> PermissionOverwrites => _permissionOverwrites; | public IEnumerable<PermissionOverwrite> PermissionOverwrites => _permissionOverwrites; | ||||
internal Channel(DiscordClient client, string id, string serverId, string recipientId) | 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; | _permissionOverwrites = _initialPermissionsOverwrites; | ||||
_areMembersStale = true; | _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) | 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) | internal void Update(ChannelReference model) | ||||
@@ -199,14 +164,14 @@ namespace Discord | |||||
{ | { | ||||
_areMembersStale = true; | _areMembersStale = true; | ||||
foreach (var member in Members) | foreach (var member in Members) | ||||
member.UpdatePermissions(Id); | |||||
member.UpdatePermissions(this); | |||||
} | } | ||||
internal void InvalidatePermissionsCache(string userId) | internal void InvalidatePermissionsCache(string userId) | ||||
{ | { | ||||
_areMembersStale = true; | _areMembersStale = true; | ||||
var member = _client.Members[userId, ServerId]; | |||||
var member = _client.Members[userId, _serverId]; | |||||
if (member != null) | 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; | using Newtonsoft.Json; | ||||
namespace Discord | 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> | /// <summary> Time (in seconds) until the invite expires. Set to 0 to never expire. </summary> | ||||
public int MaxAge { get; private set; } | public int MaxAge { get; private set; } | ||||
/// <summary> The amount of times this invite has been used. </summary> | /// <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> | /// <summary> Returns a URL for this invite using XkcdPass if available or Id if not. </summary> | ||||
public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Id); | 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> | /// <summary> Returns the user that created this invite. </summary> | ||||
[JsonIgnore] | [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> | /// <summary> Returns the server this invite is to. </summary> | ||||
[JsonIgnore] | [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> | /// <summary> Returns the channel this invite is to. </summary> | ||||
[JsonIgnore] | [JsonIgnore] | ||||
public Channel Channel => _client.Channels[ChannelId]; | |||||
public Channel Channel => _client.Channels[_channelId]; | |||||
internal Invite(DiscordClient client, string code, string xkcdPass, string serverId) | internal Invite(DiscordClient client, string code, string xkcdPass, string serverId) | ||||
: base(client, code) | |||||
{ | { | ||||
_client = client; | |||||
Id = code; | |||||
XkcdPass = xkcdPass; | XkcdPass = xkcdPass; | ||||
ServerId = serverId; | |||||
_serverId = serverId; | |||||
} | } | ||||
internal override void OnCached() { } | |||||
internal override void OnUncached() { } | |||||
public override string ToString() => XkcdPass ?? Id; | public override string ToString() => XkcdPass ?? Id; | ||||
internal void Update(InviteReference model) | internal void Update(InviteReference model) | ||||
{ | { | ||||
if (model.Channel != null) | if (model.Channel != null) | ||||
ChannelId = model.Channel.Id; | |||||
_channelId = model.Channel.Id; | |||||
if (model.Inviter != null) | if (model.Inviter != null) | ||||
InviterId = model.Inviter.Id; | |||||
_inviterId = model.Inviter.Id; | |||||
} | } | ||||
internal void Update(InviteInfo model) | 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 | namespace Discord | ||||
{ | { | ||||
public sealed class Message | |||||
public sealed class Message : CachedObject | |||||
{ | { | ||||
public sealed class Attachment : File | public sealed class Attachment : File | ||||
{ | { | ||||
@@ -91,12 +91,9 @@ namespace Discord | |||||
} | } | ||||
} | } | ||||
private readonly DiscordClient _client; | |||||
private string _cleanText; | 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> | /// <summary> Returns the local unique identifier for this message. </summary> | ||||
public string Nonce { get; internal set; } | public string Nonce { get; internal set; } | ||||
@@ -115,7 +112,7 @@ namespace Discord | |||||
public string RawText { get; private set; } | public string RawText { get; private set; } | ||||
/// <summary> Returns the content of this message with any special references such as mentions converted. </summary> | /// <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> | /// <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> | /// <summary> Returns the timestamp for when this message was sent. </summary> | ||||
public DateTime Timestamp { get; private set; } | public DateTime Timestamp { get; private set; } | ||||
/// <summary> Returns the timestamp for when this message was last edited. </summary> | /// <summary> Returns the timestamp for when this message was last edited. </summary> | ||||
@@ -132,19 +129,14 @@ namespace Discord | |||||
public string[] MentionIds { get; private set; } | public string[] MentionIds { get; private set; } | ||||
/// <summary> Returns a collection of all users mentioned in this message. </summary> | /// <summary> Returns a collection of all users mentioned in this message. </summary> | ||||
[JsonIgnore] | [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> | /// <summary> Returns the server containing the channel this message was sent to. </summary> | ||||
[JsonIgnore] | [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> | /// <summary> Returns the channel this message was sent to. </summary> | ||||
[JsonIgnore] | [JsonIgnore] | ||||
public Channel Channel => _client.Channels[ChannelId]; | |||||
public Channel Channel { get; private set; } | |||||
/// <summary> Returns true if the current user created this message. </summary> | /// <summary> Returns true if the current user created this message. </summary> | ||||
public bool IsAuthor => _client.CurrentUserId == UserId; | public bool IsAuthor => _client.CurrentUserId == UserId; | ||||
@@ -152,42 +144,28 @@ namespace Discord | |||||
public string UserId { get; } | public string UserId { get; } | ||||
/// <summary> Returns the author of this message. </summary> | /// <summary> Returns the author of this message. </summary> | ||||
[JsonIgnore] | [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) | 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; | Attachments = _initialAttachments; | ||||
Embeds = _initialEmbeds; | Embeds = _initialEmbeds; | ||||
MentionIds = _initialMentions; | 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; | var channel = Channel; | ||||
if (channel != null) | if (channel != null) | ||||
channel.RemoveMessage(Id); | channel.RemoveMessage(Id); | ||||
var user = User; | |||||
if (user != null && _gotRef) | |||||
user.RemoveRef(); | |||||
_gotRef = false; | |||||
} | } | ||||
internal void Update(MessageInfo model) | 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 | 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> | /// <summary> Returns the name of this role. </summary> | ||||
public string Name { get; private set; } | public string Name { get; private set; } | ||||
/// <summary> If true, this role is displayed isolated from other users. </summary> | /// <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> | /// <summary> Returns the the permissions contained by this role. </summary> | ||||
public ServerPermissions Permissions { get; } | 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> | /// <summary> Returns the server this role is a member of. </summary> | ||||
[JsonIgnore] | [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> | /// <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> | /// <summary> Returns a list of all members in this role. </summary> | ||||
[JsonIgnore] | [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) | internal Role(DiscordClient client, string id, string serverId) | ||||
: base(client, id) | |||||
{ | { | ||||
_client = client; | |||||
Id = id; | |||||
ServerId = serverId; | |||||
_serverId = serverId; | |||||
Permissions = new ServerPermissions(0); | Permissions = new ServerPermissions(0); | ||||
Permissions.Lock(); | Permissions.Lock(); | ||||
Color = new Color(0); | Color = new Color(0); | ||||
@@ -53,18 +46,17 @@ namespace Discord | |||||
if (IsEveryone) | if (IsEveryone) | ||||
Position = int.MinValue; | 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) | internal void Update(RoleInfo model) | ||||
{ | { | ||||
@@ -7,13 +7,16 @@ using System.Linq; | |||||
namespace Discord | 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> | /// <summary> Returns the name of this channel. </summary> | ||||
public string Name { get; private set; } | public string Name { get; private set; } | ||||
/// <summary> Returns the current logged-in user's data for this server. </summary> | /// <summary> Returns the current logged-in user's data for this server. </summary> | ||||
@@ -31,12 +34,10 @@ namespace Discord | |||||
internal string VoiceServer { get; set; }*/ | internal string VoiceServer { get; set; }*/ | ||||
/// <summary> Returns true if the current user created this server. </summary> | /// <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> | /// <summary> Returns the user that first created this server. </summary> | ||||
[JsonIgnore] | [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> | /// <summary> Returns the id of the AFK voice channel for this server (see AFKTimeout). </summary> | ||||
public string AFKChannelId { get; private set; } | 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> | /// <summary> Returns a collection of the ids of all users banned on this server. </summary> | ||||
[JsonIgnore] | [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> | /// <summary> Returns a collection of all channels within this server. </summary> | ||||
[JsonIgnore] | [JsonIgnore] | ||||
public IEnumerable<Channel> Channels => _channels.Select(x => _client.Channels[x.Key]); | public IEnumerable<Channel> Channels => _channels.Select(x => _client.Channels[x.Key]); | ||||
@@ -67,62 +65,58 @@ namespace Discord | |||||
[JsonIgnore] | [JsonIgnore] | ||||
public IEnumerable<Channel> VoiceChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelTypes.Voice); | 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] | [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> | /// <summary> Returns a collection of all users within this server with their server-specific data. </summary> | ||||
[JsonIgnore] | [JsonIgnore] | ||||
public IEnumerable<Member> Members => _members.Select(x => _client.Members[x.Key, Id]); | 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> | /// <summary> Return the id of the role representing all users in a server. </summary> | ||||
public string EveryoneRoleId => Id; | public string EveryoneRoleId => Id; | ||||
/// <summary> Return the the role representing all users in a server. </summary> | /// <summary> Return the the role representing all users in a server. </summary> | ||||
[JsonIgnore] | [JsonIgnore] | ||||
public Role EveryoneRole => _client.Roles[EveryoneRoleId]; | 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> | /// <summary> Returns a collection of all roles within this server. </summary> | ||||
[JsonIgnore] | [JsonIgnore] | ||||
public IEnumerable<Role> Roles => _roles.Select(x => _client.Roles[x.Key]); | public IEnumerable<Role> Roles => _roles.Select(x => _client.Roles[x.Key]); | ||||
internal Server(DiscordClient client, string id) | 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>(); | _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; | 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; | 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; | 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) | internal void Update(GuildInfo model) | ||||
{ | { | ||||
@@ -136,7 +130,7 @@ namespace Discord | |||||
if (model.Name != null) | if (model.Name != null) | ||||
Name = model.Name; | Name = model.Name; | ||||
if (model.OwnerId != null) | if (model.OwnerId != null) | ||||
OwnerId = model.OwnerId; | |||||
_ownerId = model.OwnerId; | |||||
if (model.Region != null) | if (model.Region != null) | ||||
Region = model.Region; | Region = model.Region; | ||||
@@ -165,8 +159,6 @@ namespace Discord | |||||
var members = _client.Members; | var members = _client.Members; | ||||
foreach (var subModel in model.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); | var member = members.GetOrAdd(subModel.User.Id, Id); | ||||
member.Update(subModel); | member.Update(subModel); | ||||
} | } | ||||
@@ -196,62 +188,43 @@ namespace Discord | |||||
return _bans.TryRemove(banId, out ignored); | 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) | 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) | 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) | internal void AddMember(Member member) | ||||
{ | { | ||||
_members.TryAdd(member.UserId, true); | |||||
_members.TryAdd(member.Id, member); | |||||
foreach (var channel in Channels) | 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) | 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 Discord.API; | ||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using System; | |||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Threading; | |||||
namespace Discord | 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> | /// <summary> Returns the name of this user on this server. </summary> | ||||
public string Name { get; private set; } | public string Name { get; private set; } | ||||
/// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary> | /// <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; } | public string AvatarId { get; private set; } | ||||
/// <summary> Returns the URL to this user's current avatar. </summary> | /// <summary> Returns the URL to this user's current avatar. </summary> | ||||
public string AvatarUrl => API.Endpoints.UserAvatar(Id, AvatarId); | 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] | [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] | [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] | [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] | [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] | [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] | [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) | internal void Update(UserReference model) | ||||
{ | { | ||||
if (model.Avatar != null) | if (model.Avatar != null) | ||||
@@ -85,36 +124,157 @@ namespace Discord | |||||
if (model.Username != null) | if (model.Username != null) | ||||
Name = model.Username; | 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()}"; | string name = $"#test_{_random.Next()}"; | ||||
AssertEvent<ChannelEventArgs>( | AssertEvent<ChannelEventArgs>( | ||||
"ChannelCreated event never received", | "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, | ||||
x => _targetBot.ChannelCreated -= x, | x => _targetBot.ChannelCreated -= x, | ||||
(s, e) => e.Channel.Name == name); | (s, e) => e.Channel.Name == name); | ||||
AssertEvent<ChannelEventArgs>( | AssertEvent<ChannelEventArgs>( | ||||
"ChannelDestroyed event never received", | "ChannelDestroyed event never received", | ||||
() => _hostClient.DestroyChannel(channel), | |||||
async () => await _hostClient.DestroyChannel(channel), | |||||
x => _targetBot.ChannelDestroyed += x, | x => _targetBot.ChannelDestroyed += x, | ||||
x => _targetBot.ChannelDestroyed -= x, | x => _targetBot.ChannelDestroyed -= x, | ||||
(s, e) => e.Channel.Name == name); | (s, e) => e.Channel.Name == name); | ||||
@@ -120,15 +120,15 @@ namespace Discord.Tests | |||||
_observerBot.Disconnect()); | _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); | 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); | 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); | ManualResetEventSlim trigger = new ManualResetEventSlim(false); | ||||
bool result = false; | bool result = false; | ||||
@@ -145,8 +145,9 @@ namespace Discord.Tests | |||||
}; | }; | ||||
addEvent(handler); | addEvent(handler); | ||||
action(); | |||||
var task = action(); | |||||
trigger.Wait(EventTimeout); | trigger.Wait(EventTimeout); | ||||
task.Wait(); | |||||
removeEvent(handler); | removeEvent(handler); | ||||
Assert.AreEqual(assertTrue, result, msg); | Assert.AreEqual(assertTrue, result, msg); | ||||