@@ -36,18 +36,20 @@ namespace Discord | |||
public Task Ban(User member) | |||
{ | |||
if (member == null) throw new ArgumentNullException(nameof(member)); | |||
if (member.Server == null) throw new ArgumentException("Unable to ban a user in a private chat."); | |||
CheckReady(); | |||
return _api.Ban(member.ServerId, member.Id); | |||
return _api.Ban(member.Server.Id, member.Id); | |||
} | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public async Task Unban(User member) | |||
public async Task Unban(Server server, string userId) | |||
{ | |||
if (member == null) throw new ArgumentNullException(nameof(member)); | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
CheckReady(); | |||
try { await _api.Unban(member.ServerId, member.Id).ConfigureAwait(false); } | |||
try { await _api.Unban(server.Id, userId).ConfigureAwait(false); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
} |
@@ -10,6 +10,8 @@ namespace Discord | |||
/// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks> | |||
public async Task<Invite> GetInvite(string inviteIdOrXkcd) | |||
{ | |||
//This doesn't work well if it's an invite to a different server! | |||
if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); | |||
CheckReady(); | |||
@@ -22,8 +24,8 @@ namespace Discord | |||
inviteIdOrXkcd = inviteIdOrXkcd.Substring(index + 1); | |||
var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); | |||
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); | |||
invite.Update(response); | |||
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id, response.Inviter?.Id, response.Channel?.Id); | |||
invite.Cache(); //Builds references | |||
return invite; | |||
} | |||
@@ -53,8 +55,8 @@ namespace Discord | |||
var response = await _api.CreateInvite(channel.Id, maxAge: maxAge, maxUses: maxUses, | |||
tempMembership: tempMembership, hasXkcd: hasXkcd).ConfigureAwait(false); | |||
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); | |||
invite.Update(response); | |||
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id, response.Inviter?.Id, response.Channel?.Id); | |||
invite.Cache(); //Builds references | |||
return invite; | |||
} | |||
@@ -121,7 +121,7 @@ namespace Discord | |||
if (member == null) throw new ArgumentNullException(nameof(member)); | |||
CheckReady(); | |||
return _api.EditMember(member.ServerId, member.Id, mute: mute, deaf: deaf, roles: roles.Select(x => x.Id)); | |||
return _api.EditMember(member.Server?.Id, member.Id, mute: mute, deaf: deaf, roles: roles.Select(x => x.Id)); | |||
} | |||
} | |||
} |
@@ -25,7 +25,7 @@ namespace Discord | |||
else | |||
{ | |||
var msg = new Message(_client, id, channelId, userId); | |||
msg.Cache(); //Creates references to channel/server | |||
msg.Cache(); //Builds references | |||
return msg; | |||
} | |||
} | |||
@@ -81,9 +81,17 @@ namespace Discord | |||
if (changed) | |||
{ | |||
if (targetType == PermissionTarget.Role) | |||
channel.InvalidatePermissionsCache(); | |||
{ | |||
var role = _roles[targetId]; | |||
if (role != null) | |||
channel.InvalidatePermissionsCache(role); | |||
} | |||
else if (targetType == PermissionTarget.User) | |||
channel.InvalidatePermissionsCache(targetId); | |||
{ | |||
var user = _users[targetId, channel.Server?.Id]; | |||
if (user != null) | |||
channel.InvalidatePermissionsCache(user); | |||
} | |||
} | |||
} | |||
@@ -114,9 +122,16 @@ namespace Discord | |||
channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).ToArray(); | |||
if (targetType == PermissionTarget.Role) | |||
channel.InvalidatePermissionsCache(); | |||
{ | |||
var role = _roles[userOrRoleId]; | |||
channel.InvalidatePermissionsCache(role); | |||
} | |||
else if (targetType == PermissionTarget.User) | |||
channel.InvalidatePermissionsCache(userOrRoleId); | |||
{ | |||
var user = _users[userOrRoleId, channel.Server?.Id]; | |||
if (user != null) | |||
channel.InvalidatePermissionsCache(user); | |||
} | |||
} | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
@@ -7,11 +7,13 @@ namespace Discord | |||
{ | |||
internal sealed class Roles : AsyncCollection<Role> | |||
{ | |||
private const string VirtualEveryoneId = "[Virtual]"; | |||
public Role VirtualEveryone { get; private set; } | |||
public Roles(DiscordClient client, object writerLock) | |||
: base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } | |||
: base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) | |||
{ | |||
VirtualEveryone = new Role(client, "Private", null); | |||
} | |||
public Role GetOrAdd(string id, string serverId) | |||
=> GetOrAdd(id, () => new Role(_client, id, serverId)); | |||
@@ -57,7 +57,7 @@ namespace Discord | |||
} | |||
/// <summary> Returns a collection of all servers this client is a member of. </summary> | |||
public IEnumerable<Server> AllServers => _servers.Where(x => !x.IsVirtual); | |||
public IEnumerable<Server> AllServers => _servers; | |||
internal Servers Servers => _servers; | |||
private readonly Servers _servers; | |||
@@ -59,12 +59,16 @@ namespace Discord | |||
VoiceDisconnected += (s, e) => | |||
{ | |||
foreach (var member in _users) | |||
var server = _servers[e.ServerId]; | |||
if (server != null) | |||
{ | |||
if (member.ServerId == e.ServerId && member.IsSpeaking) | |||
foreach (var member in server.Members) | |||
{ | |||
member.IsSpeaking = false; | |||
RaiseUserIsSpeaking(member, _channels[_voiceSocket.CurrentChannelId], false); | |||
if (member.IsSpeaking) | |||
{ | |||
member.IsSpeaking = false; | |||
RaiseUserIsSpeaking(member, _channels[_voiceSocket.CurrentChannelId], false); | |||
} | |||
} | |||
} | |||
}; | |||
@@ -104,13 +108,13 @@ namespace Discord | |||
BanRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Removed Ban: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); | |||
UserAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Added Member: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); | |||
$"Added Member: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); | |||
UserRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Removed Member: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); | |||
$"Removed Member: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); | |||
MemberUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Updated Member: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); | |||
$"Updated Member: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); | |||
UserVoiceStateUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Updated Member (Voice State): {e.Server?.Name ?? "[Private]"}/{e.UserId}"); | |||
$"Updated Member (Voice State): {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); | |||
ProfileUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
"Updated Profile"); | |||
} | |||
@@ -281,13 +285,12 @@ namespace Discord | |||
{ | |||
try | |||
{ | |||
await base.OnReceivedEvent(e); | |||
switch (e.Type) | |||
{ | |||
//Global | |||
case "READY": //Resync | |||
case "READY": //Resync | |||
{ | |||
base.OnReceivedEvent(e).Wait(); //This cannot be an await, or we'll get later messages before we're ready | |||
var data = e.Payload.ToObject<ReadyEvent>(_serializer); | |||
_currentUser = _users.GetOrAdd(data.User.Id, null); | |||
_currentUser.Update(data.User); | |||
@@ -576,10 +579,11 @@ namespace Discord | |||
var member = _users[data.UserId, data.GuildId]; | |||
if (member != null) | |||
{ | |||
if (data.ChannelId != member.VoiceChannelId && member.IsSpeaking) | |||
var voiceChannel = member.VoiceChannel; | |||
if (voiceChannel != null && data.ChannelId != voiceChannel.Id && member.IsSpeaking) | |||
{ | |||
member.IsSpeaking = false; | |||
RaiseUserIsSpeaking(member, _channels[member.VoiceChannelId], false); | |||
RaiseUserIsSpeaking(member, _channels[voiceChannel.Id], false); | |||
} | |||
member.Update(data); | |||
RaiseUserVoiceStateUpdated(member); | |||
@@ -611,6 +615,7 @@ namespace Discord | |||
//Internal (handled in DiscordSimpleClient) | |||
case "VOICE_SERVER_UPDATE": | |||
await base.OnReceivedEvent(e); | |||
break; | |||
//Others | |||
@@ -90,7 +90,7 @@ namespace Discord | |||
} | |||
} | |||
socket.ReceivedEvent += (s, e) => OnReceivedEvent(e); | |||
socket.ReceivedEvent += async (s, e) => await OnReceivedEvent(e); | |||
return socket; | |||
} | |||
internal virtual VoiceWebSocket CreateVoiceSocket() | |||
@@ -292,7 +292,7 @@ namespace Discord | |||
} | |||
} | |||
internal virtual Task OnReceivedEvent(WebSocketEventEventArgs e) | |||
internal virtual async Task OnReceivedEvent(WebSocketEventEventArgs e) | |||
{ | |||
try | |||
{ | |||
@@ -309,7 +309,7 @@ namespace Discord | |||
{ | |||
string token = e.Payload.Value<string>("token"); | |||
_voiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; | |||
return _voiceSocket.Login(_userId, _dataSocket.SessionId, token, CancelToken); | |||
await _voiceSocket.Login(_userId, _dataSocket.SessionId, token, CancelToken); | |||
} | |||
} | |||
break; | |||
@@ -319,7 +319,6 @@ namespace Discord | |||
{ | |||
RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.Client, $"Error handling {e.Type} event: {ex.GetBaseException().Message}"); | |||
} | |||
return TaskHelper.CompletedTask; | |||
} | |||
} | |||
} |
@@ -53,11 +53,8 @@ namespace Discord | |||
{ | |||
get | |||
{ | |||
if (!_areMembersStale) | |||
return _members.Select(x => x.Value); | |||
_members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x); | |||
_areMembersStale = false; | |||
if (_areMembersStale) | |||
UpdateMembersCache(); | |||
return _members.Select(x => x.Value); | |||
} | |||
} | |||
@@ -157,22 +154,32 @@ namespace Discord | |||
} | |||
internal void RemoveMessage(Message message) => _messages.TryRemove(message.Id, out message); | |||
internal void InvalidMembersCache() | |||
internal void InvalidateMembersCache() | |||
{ | |||
_areMembersStale = true; | |||
} | |||
internal void InvalidatePermissionsCache() | |||
private void UpdateMembersCache() | |||
{ | |||
_members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x); | |||
_areMembersStale = false; | |||
} | |||
internal void InvalidatePermissionsCache() | |||
{ | |||
UpdateMembersCache(); | |||
foreach (var member in _members) | |||
member.Value.UpdateChannelPermissions(this); | |||
} | |||
internal void InvalidatePermissionsCache(Role role) | |||
{ | |||
_areMembersStale = true; | |||
foreach (var member in Members) | |||
foreach (var member in role.Members) | |||
member.UpdateChannelPermissions(this); | |||
} | |||
internal void InvalidatePermissionsCache(string userId) | |||
internal void InvalidatePermissionsCache(User user) | |||
{ | |||
_areMembersStale = true; | |||
var user = _members[userId] | |||
if (user != null) | |||
user.UpdateChannelPermissions(this); | |||
user.UpdateChannelPermissions(this); | |||
} | |||
} | |||
} |
@@ -34,7 +34,10 @@ namespace Discord | |||
_users = new ConcurrentDictionary<string, User>(); | |||
} | |||
internal override void OnCached() { } | |||
internal override void OnUncached() { } | |||
internal override void OnUncached() | |||
{ | |||
//Don't need to clean _users - they're considered owned by server | |||
} | |||
internal void Update(UserInfo model) | |||
{ | |||
@@ -44,10 +47,10 @@ namespace Discord | |||
IsVerified = model.IsVerified; | |||
} | |||
internal void AddUser(User user) => _users.TryAdd(user.Id, user); | |||
internal void AddUser(User user) => _users.TryAdd(user.UniqueId, user); | |||
internal void RemoveUser(User user) | |||
{ | |||
if (_users.TryRemove(user.Id, out user)) | |||
if (_users.TryRemove(user.UniqueId, out user)) | |||
{ | |||
if (_users.Count == 0) | |||
_client.GlobalUsers.TryRemove(Id); | |||
@@ -5,10 +5,11 @@ using Newtonsoft.Json; | |||
namespace Discord | |||
{ | |||
public sealed class Invite : CachedObject | |||
{ | |||
private readonly string _serverId; | |||
private string _inviterId, _channelId; | |||
{ | |||
/// <summary> Returns the unique code for this invite. </summary> | |||
public string Code { get; private set; } | |||
/// <summary> Returns, if enabled, an alternative human-readable code for URLs. </summary> | |||
public string XkcdCode { get; } | |||
/// <summary> Time (in seconds) until the invite expires. Set to 0 to never expire. </summary> | |||
public int MaxAge { get; private set; } | |||
/// <summary> The amount of times this invite has been used. </summary> | |||
@@ -19,47 +20,72 @@ namespace Discord | |||
public bool IsRevoked { get; private set; } | |||
/// <summary> If true, a user accepting this invite will be kicked from the server after closing their client. </summary> | |||
public bool IsTemporary { get; private set; } | |||
/// <summary> Returns, if enabled, an alternative human-readable code for URLs. </summary> | |||
public string XkcdPass { get; } | |||
/// <summary> Returns a URL for this invite using XkcdPass if available or Id if not. </summary> | |||
public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Id); | |||
/// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary> | |||
public string Url => API.Endpoints.InviteUrl(XkcdCode ?? Code); | |||
/// <summary> Returns the user that created this invite. </summary> | |||
[JsonIgnore] | |||
public User Inviter => _client.Users[_inviterId, _serverId]; | |||
public User Inviter { get; private set; } | |||
[JsonProperty("InviterId")] | |||
private readonly string _inviterId; | |||
/// <summary> Returns the server this invite is to. </summary> | |||
[JsonIgnore] | |||
public Server Server => _client.Servers[_serverId]; | |||
public Server Server { get; private set; } | |||
[JsonProperty("ServerId")] | |||
private readonly string _serverId; | |||
/// <summary> Returns the channel this invite is to. </summary> | |||
[JsonIgnore] | |||
public Channel Channel => _client.Channels[_channelId]; | |||
public Channel Channel { get; private set; } | |||
[JsonProperty("ChannelId")] | |||
private readonly string _channelId; | |||
internal Invite(DiscordClient client, string code, string xkcdPass, string serverId) | |||
internal Invite(DiscordClient client, string code, string xkcdPass, string serverId, string inviterId, string channelId) | |||
: base(client, code) | |||
{ | |||
XkcdPass = xkcdPass; | |||
XkcdCode = xkcdPass; | |||
_serverId = serverId; | |||
_inviterId = inviterId; | |||
_channelId = channelId; | |||
} | |||
internal override void OnCached() { } | |||
internal override void OnUncached() { } | |||
public override string ToString() => XkcdPass ?? Id; | |||
internal override void OnCached() | |||
{ | |||
var server = _client.Servers[_serverId]; | |||
if (server == null) | |||
server = new Server(_client, _serverId); | |||
Server = server; | |||
if (_inviterId != null) | |||
{ | |||
var inviter = _client.Users[_inviterId, _serverId]; | |||
if (inviter == null) | |||
inviter = new User(_client, _inviterId, _serverId); | |||
Inviter = inviter; | |||
} | |||
internal void Update(InviteReference model) | |||
if (_channelId != null) | |||
{ | |||
var channel = _client.Channels[_channelId]; | |||
if (channel == null) | |||
channel = new Channel(_client, _channelId, _serverId, null); | |||
Channel = channel; | |||
} | |||
} | |||
internal override void OnUncached() | |||
{ | |||
if (model.Channel != null) | |||
_channelId = model.Channel.Id; | |||
if (model.Inviter != null) | |||
_inviterId = model.Inviter.Id; | |||
Server = null; | |||
Inviter = null; | |||
Channel = null; | |||
} | |||
public override string ToString() => XkcdCode ?? Id; | |||
internal void Update(InviteInfo model) | |||
{ | |||
Update(model as InviteReference); | |||
if (model.IsRevoked != null) | |||
IsRevoked = model.IsRevoked.Value; | |||
if (model.IsTemporary != null) | |||
@@ -157,15 +157,18 @@ namespace Discord | |||
} | |||
internal override void OnCached() | |||
{ | |||
//References | |||
var channel = _client.Channels[_channelId]; | |||
channel.AddMessage(this); | |||
Channel = channel; | |||
} | |||
internal override void OnUncached() | |||
{ | |||
//References | |||
var channel = Channel; | |||
if (channel != null) | |||
channel.RemoveMessage(this); | |||
Channel = null; | |||
} | |||
internal void Update(MessageInfo model) | |||
@@ -28,10 +28,11 @@ namespace Discord | |||
public Server Server { get; private set; } | |||
/// <summary> Returns true if this is the role representing all users in a server. </summary> | |||
public bool IsEveryone => Id == _serverId; | |||
public bool IsEveryone => _serverId == null || Id == _serverId; | |||
/// <summary> Returns a list of all members in this role. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<User> Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this)); | |||
//TODO: Add local members cache | |||
internal Role(DiscordClient client, string id, string serverId) | |||
: base(client, id) | |||
@@ -41,18 +42,17 @@ namespace Discord | |||
Permissions.Lock(); | |||
Color = new Color(0); | |||
Color.Lock(); | |||
if (IsEveryone) | |||
Position = int.MinValue; | |||
} | |||
internal override void OnCached() | |||
{ | |||
//References | |||
var server = _client.Servers[_serverId]; | |||
server.AddRole(this); | |||
Server = server; | |||
} | |||
internal override void OnUncached() | |||
{ | |||
//References | |||
var server = Server; | |||
if (server != null) | |||
server.RemoveRole(this); | |||
@@ -8,21 +8,11 @@ using System.Linq; | |||
namespace Discord | |||
{ | |||
public sealed class Server : CachedObject | |||
{ | |||
private readonly ConcurrentDictionary<string, bool> _bans; | |||
private readonly ConcurrentDictionary<string, Channel> _channels; | |||
private readonly ConcurrentDictionary<string, User> _members; | |||
private readonly ConcurrentDictionary<string, Role> _roles; | |||
private readonly ConcurrentDictionary<string, Invite> _invites; | |||
private string _ownerId; | |||
{ | |||
/// <summary> Returns the name of this channel. </summary> | |||
public string Name { get; private set; } | |||
/// <summary> Returns the current logged-in user's data for this server. </summary> | |||
public User CurrentMember { get; internal set; } | |||
/// <summary> Returns true if this is a virtual server used by Discord.Net and not a real Discord server. </summary> | |||
public bool IsVirtual { get; internal set; } | |||
/// <summary> Returns the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK channel (see AFKChannel). </summary> | |||
public int AFKTimeout { get; private set; } | |||
@@ -38,49 +28,49 @@ namespace Discord | |||
/// <summary> Returns the user that first created this server. </summary> | |||
[JsonIgnore] | |||
public User Owner { get; private set; } | |||
/// <summary> Returns the id of the AFK voice channel for this server (see AFKTimeout). </summary> | |||
public string AFKChannelId { get; private set; } | |||
private string _ownerId; | |||
/// <summary> Returns the AFK voice channel for this server (see AFKTimeout). </summary> | |||
[JsonIgnore] | |||
public Channel AFKChannel => _client.Channels[AFKChannelId]; | |||
/// <summary> Returns the id of the default channel for this server. </summary> | |||
public string DefaultChannelId => Id; | |||
public Channel AFKChannel { get; private set; } | |||
/// <summary> Returns the default channel for this server. </summary> | |||
[JsonIgnore] | |||
public Channel DefaultChannel => _client.Channels[DefaultChannelId]; | |||
public Channel DefaultChannel { get; private set; } | |||
/// <summary> Returns a collection of the ids of all users banned on this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<string> Bans => _bans.Select(x => x.Key); | |||
private ConcurrentDictionary<string, bool> _bans; | |||
/// <summary> Returns a collection of all channels within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Channel> Channels => _channels.Select(x => _client.Channels[x.Key]); | |||
public IEnumerable<Channel> Channels => _channels.Select(x => x.Value); | |||
/// <summary> Returns a collection of all channels within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Channel> TextChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelType.Text); | |||
public IEnumerable<Channel> TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text); | |||
/// <summary> Returns a collection of all channels within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Channel> VoiceChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelType.Voice); | |||
public IEnumerable<Channel> VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice); | |||
private ConcurrentDictionary<string, Channel> _channels; | |||
/// <summary> Returns a collection of all invites to this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Invite> Invites => _invites.Values; | |||
private ConcurrentDictionary<string, Invite> _invites; | |||
/// <summary> Returns a collection of all users within this server with their server-specific data. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<User> Members => _members.Select(x => _client.Users[x.Key, Id]); | |||
public IEnumerable<User> Members => _members.Select(x => x.Value); | |||
private ConcurrentDictionary<string, User> _members; | |||
/// <summary> Return the id of the role representing all users in a server. </summary> | |||
public string EveryoneRoleId => Id; | |||
/// <summary> Return the the role representing all users in a server. </summary> | |||
[JsonIgnore] | |||
public Role EveryoneRole => _client.Roles[EveryoneRoleId]; | |||
public Role EveryoneRole { get; private set; } | |||
/// <summary> Returns a collection of all roles within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Role> Roles => _roles.Select(x => _client.Roles[x.Key]); | |||
public IEnumerable<Role> Roles => _roles.Select(x => x.Value); | |||
private ConcurrentDictionary<string, Role> _roles; | |||
internal Server(DiscordClient client, string id) | |||
: base(client, id) | |||
@@ -98,30 +88,37 @@ namespace Discord | |||
internal override void OnUncached() | |||
{ | |||
//Global Cache | |||
var channels = _client.Channels; | |||
foreach (var channel in _channels) | |||
channels.TryRemove(channel.Key); | |||
var members = _client.Users; | |||
foreach (var user in _members) | |||
members.TryRemove(user.Key, Id); | |||
var roles = _client.Roles; | |||
foreach (var role in _roles) | |||
roles.TryRemove(role.Key); | |||
var globalChannels = _client.Channels; | |||
var channels = _channels; | |||
foreach (var channel in channels) | |||
globalChannels.TryRemove(channel.Key); | |||
channels.Clear(); | |||
var globalMembers = _client.Users; | |||
var members = _members; | |||
foreach (var user in members) | |||
globalMembers.TryRemove(user.Key, Id); | |||
members.Clear(); | |||
var globalRoles = _client.Roles; | |||
var roles = _roles; | |||
foreach (var role in roles) | |||
globalRoles.TryRemove(role.Key); | |||
roles.Clear(); | |||
//Local Cache | |||
foreach (var invite in _invites) | |||
var invites = _invites; | |||
foreach (var invite in invites) | |||
invite.Value.Uncache(); | |||
_invites.Clear(); | |||
invites.Clear(); | |||
_bans.Clear(); | |||
} | |||
} | |||
internal void Update(GuildInfo model) | |||
{ | |||
//Can be null | |||
AFKChannelId = model.AFKChannelId; | |||
AFKChannel = _client.Channels[model.AFKChannelId]; | |||
if (model.AFKTimeout != null) | |||
AFKTimeout = model.AFKTimeout.Value; | |||
@@ -139,18 +136,18 @@ namespace Discord | |||
if (model.Roles != null) | |||
{ | |||
var roles = _client.Roles; | |||
foreach (var subModel in model.Roles) | |||
var roleCache = _client.Roles; | |||
foreach (var x in model.Roles) | |||
{ | |||
var role = roles.GetOrAdd(subModel.Id, Id); | |||
role.Update(subModel); | |||
} | |||
} | |||
var role = roleCache.GetOrAdd(x.Id, Id); | |||
role.Update(x); | |||
} | |||
} | |||
} | |||
internal void Update(ExtendedGuildInfo model) | |||
{ | |||
Update(model as GuildInfo); | |||
var channels = _client.Channels; | |||
foreach (var subModel in model.Channels) | |||
{ | |||
@@ -158,22 +155,22 @@ namespace Discord | |||
channel.Update(subModel); | |||
} | |||
var users = _client.GlobalUsers; | |||
var members = _client.Users; | |||
var usersCache = _client.GlobalUsers; | |||
var membersCache = _client.Users; | |||
foreach (var subModel in model.Members) | |||
{ | |||
var member = members.GetOrAdd(subModel.User.Id, Id); | |||
var member = membersCache.GetOrAdd(subModel.User.Id, Id); | |||
member.Update(subModel); | |||
} | |||
foreach (var subModel in model.VoiceStates) | |||
{ | |||
var member = members[subModel.UserId, Id]; | |||
var member = membersCache[subModel.UserId, Id]; | |||
if (member != null) | |||
member.Update(subModel); | |||
} | |||
foreach (var subModel in model.Presences) | |||
{ | |||
var member = members[subModel.User.Id, Id]; | |||
var member = membersCache[subModel.User.Id, Id]; | |||
if (member != null) | |||
member.Update(subModel); | |||
} | |||
@@ -193,9 +190,13 @@ namespace Discord | |||
internal void AddChannel(Channel channel) | |||
{ | |||
_channels.TryAdd(channel.Id, channel); | |||
foreach (var member in Members) | |||
member.AddChannel(channel); | |||
if (_channels.TryAdd(channel.Id, channel)) | |||
{ | |||
if (channel.Id == Id) | |||
DefaultChannel = channel; | |||
foreach (var member in Members) | |||
member.AddChannel(channel); | |||
} | |||
} | |||
internal void RemoveChannel(Channel channel) | |||
{ | |||
@@ -213,7 +214,7 @@ namespace Discord | |||
foreach (var channel in Channels) | |||
{ | |||
member.AddChannel(channel); | |||
channel.InvalidatePermissionsCache(member.Id); | |||
channel.InvalidatePermissionsCache(member); | |||
} | |||
} | |||
internal void RemoveMember(User member) | |||
@@ -221,13 +222,27 @@ namespace Discord | |||
foreach (var channel in Channels) | |||
{ | |||
member.RemoveChannel(channel); | |||
channel.InvalidatePermissionsCache(member.Id); | |||
channel.InvalidatePermissionsCache(member); | |||
} | |||
_members.TryRemove(member.Id, out member); | |||
} | |||
internal void HasMember(User user) => _members.ContainsKey(user.Id); | |||
internal void AddRole(Role role) => _roles.TryAdd(role.Id, role); | |||
internal void RemoveRole(Role role) => _roles.TryRemove(role.Id, out role); | |||
internal void AddRole(Role role) | |||
{ | |||
if (_roles.TryAdd(role.Id, role)) | |||
{ | |||
if (role.Id == Id) | |||
EveryoneRole = role; | |||
} | |||
} | |||
internal void RemoveRole(Role role) | |||
{ | |||
if (_roles.TryRemove(role.Id, out role)) | |||
{ | |||
if (role.Id == Id) | |||
EveryoneRole = null; | |||
} | |||
} | |||
} | |||
} |
@@ -9,17 +9,14 @@ namespace Discord | |||
{ | |||
public class User : CachedObject | |||
{ | |||
private static readonly string[] _initialRoleIds = new string[0]; | |||
internal static string GetId(string userId, string serverId) => (serverId ?? "Private") + '_' + userId; | |||
private ConcurrentDictionary<string, Channel> _channels; | |||
private ConcurrentDictionary<string, ChannelPermissions> _permissions; | |||
private ServerPermissions _serverPermissions; | |||
private string[] _roleIds; | |||
/// <summary> Returns a unique identifier combining this user's id with its server's. </summary> | |||
internal string UniqueId => GetId(Id, ServerId); | |||
internal string UniqueId => GetId(Id, _serverId); | |||
/// <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> | |||
@@ -52,48 +49,55 @@ namespace Discord | |||
private DateTime _lastOnline; | |||
[JsonIgnore] | |||
internal GlobalUser GlobalUser => _client.GlobalUsers[Id]; | |||
internal GlobalUser GlobalUser { get; private set; } | |||
public string ServerId { get; } | |||
[JsonIgnore] | |||
public Server Server => _client.Servers[ServerId]; | |||
public Server Server { get; private set; } | |||
private string _serverId; | |||
public string VoiceChannelId { get; private set; } | |||
[JsonIgnore] | |||
public Channel VoiceChannel => _client.Channels[VoiceChannelId]; | |||
public Channel VoiceChannel { get; private set; } | |||
[JsonIgnore] | |||
public IEnumerable<Role> Roles => _roleIds.Select(x => _client.Roles[x]); | |||
public IEnumerable<Role> Roles => _roles.Select(x => x.Value); | |||
private Dictionary<string, Role> _roles; | |||
/// <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 == Id && x.Server.Id == ServerId); | |||
public IEnumerable<Message> Messages => _client.Messages.Where(x => x.UserId == Id && x.Server.Id == _serverId); | |||
/// <summary> Returns a collection of all channels this user is a member of. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Channel> Channels => _client.Channels.Where(x => x.Server.Id == ServerId && x.Members == this); | |||
public IEnumerable<Channel> Channels => _channels.Select(x => x.Value); | |||
internal User(DiscordClient client, string id, string serverId) | |||
: base(client, id) | |||
{ | |||
ServerId = serverId; | |||
_serverId = serverId; | |||
Status = UserStatus.Offline; | |||
_roleIds = _initialRoleIds; | |||
//_roles = new Dictionary<string, Role>(); | |||
_channels = new ConcurrentDictionary<string, Channel>(); | |||
_permissions = new ConcurrentDictionary<string, ChannelPermissions>(); | |||
_serverPermissions = new ServerPermissions(); | |||
} | |||
internal override void OnCached() | |||
{ | |||
var server = Server; | |||
server.AddMember(this); | |||
if (Id == _client.CurrentUserId) | |||
server.CurrentMember = this; | |||
var server = _client.Servers[_serverId]; | |||
if (server != null) | |||
{ | |||
server.AddMember(this); | |||
if (Id == _client.CurrentUserId) | |||
server.CurrentMember = this; | |||
Server = server; | |||
} | |||
var user = GlobalUser; | |||
if (server == null || !server.IsVirtual) | |||
user.AddUser(this); | |||
var user = _client.GlobalUsers.GetOrAdd(Id); | |||
user.AddUser(this); | |||
GlobalUser = user; | |||
} | |||
internal override void OnUncached() | |||
{ | |||
//References | |||
var server = Server; | |||
if (server != null) | |||
{ | |||
@@ -101,9 +105,12 @@ namespace Discord | |||
if (Id == _client.CurrentUserId) | |||
server.CurrentMember = null; | |||
} | |||
Server = null; | |||
var globalUser = GlobalUser; | |||
if (globalUser != null) | |||
globalUser.RemoveUser(this); | |||
GlobalUser = null; | |||
} | |||
public override string ToString() => Id; | |||
@@ -124,7 +131,7 @@ namespace Discord | |||
if (model.JoinedAt.HasValue) | |||
JoinedAt = model.JoinedAt.Value; | |||
if (model.Roles != null) | |||
UpdateRoles(model.Roles); | |||
UpdateRoles(model.Roles.Select(x => _client.Roles[x])); | |||
UpdateServerPermissions(); | |||
} | |||
@@ -142,7 +149,7 @@ namespace Discord | |||
Update(model.User as UserReference); | |||
if (model.Roles != null) | |||
UpdateRoles(model.Roles); | |||
UpdateRoles(model.Roles.Select(x => _client.Roles[x])); | |||
if (model.Status != null && Status != model.Status) | |||
{ | |||
Status = UserStatus.FromString(model.Status); | |||
@@ -165,7 +172,7 @@ namespace Discord | |||
Token = model.Token; | |||
if (model.ChannelId != null) | |||
VoiceChannelId = model.ChannelId; | |||
VoiceChannel = _client.Channels[model.ChannelId]; | |||
if (model.IsSelfDeafened != null) | |||
IsSelfDeafened = model.IsSelfDeafened.Value; | |||
if (model.IsSelfMuted != null) | |||
@@ -173,14 +180,16 @@ namespace Discord | |||
if (model.IsServerSuppressed != null) | |||
IsServerSuppressed = model.IsServerSuppressed.Value; | |||
} | |||
private void UpdateRoles(string[] roleIds) | |||
private void UpdateRoles(IEnumerable<Role> roles) | |||
{ | |||
//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; | |||
var newRoles = roles.ToDictionary(x => x.Id, x => x); | |||
Role everyone; | |||
if (_serverId != null) | |||
everyone = Server.EveryoneRole; | |||
else | |||
everyone = _client.Roles.VirtualEveryone; | |||
newRoles.Add(everyone.Id, everyone); | |||
_roles = newRoles; | |||
} | |||
internal void UpdateActivity(DateTime? activity = null) | |||
@@ -191,7 +200,7 @@ namespace Discord | |||
internal void UpdateChannelPermissions(Channel channel) | |||
{ | |||
if (_roleIds == null) return; // We don't have all our data processed yet, this will be called again soon | |||
if (_roles == 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; | |||
@@ -225,14 +234,14 @@ namespace Discord | |||
if (newPermissions != oldPermissions) | |||
{ | |||
permissions.SetRawValueInternal(newPermissions); | |||
channel.InvalidMembersCache(); | |||
channel.InvalidateMembersCache(); | |||
} | |||
permissions.SetRawValueInternal(newPermissions); | |||
} | |||
internal void UpdateServerPermissions() | |||
{ | |||
if (_roleIds == null) return; // We don't have all our data processed yet, this will be called again soon | |||
if (_roles == null) return; // We don't have all our data processed yet, this will be called again soon | |||
var server = Server; | |||
if (server == null) return; | |||
@@ -290,7 +299,7 @@ namespace Discord | |||
{ | |||
if (role == null) throw new ArgumentNullException(nameof(role)); | |||
return _roleIds.Contains(role.Id); | |||
return _roles.ContainsKey(role.Id); | |||
} | |||
} | |||
} |
@@ -53,21 +53,21 @@ namespace Discord.Net.WebSockets | |||
_connectedEvent = new ManualResetEventSlim(false); | |||
_engine = new WebSocketSharpEngine(this, client.Config); | |||
_engine.BinaryMessage += async (s, e) => | |||
_engine.BinaryMessage += (s, e) => | |||
{ | |||
using (var compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2)) | |||
using (var decompressed = new MemoryStream()) | |||
{ | |||
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) | |||
await zlib.CopyToAsync(decompressed); | |||
zlib.CopyTo(decompressed); | |||
decompressed.Position = 0; | |||
using (var reader = new StreamReader(decompressed)) | |||
await ProcessMessage(await reader.ReadToEndAsync()); | |||
ProcessMessage(reader.ReadToEnd()).Wait(); | |||
} | |||
}; | |||
_engine.TextMessage += async (s, e) => | |||
_engine.TextMessage += (s, e) => | |||
{ | |||
await ProcessMessage(e.Message); | |||
/*await*/ ProcessMessage(e.Message).Wait(); | |||
}; | |||
} | |||