Lots of bug fixes
This commit is contained in:
@@ -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 void Update(InviteReference model)
|
||||
internal override void OnCached()
|
||||
{
|
||||
if (model.Channel != null)
|
||||
_channelId = model.Channel.Id;
|
||||
if (model.Inviter != null)
|
||||
_inviterId = model.Inviter.Id;
|
||||
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;
|
||||
}
|
||||
|
||||
if (_channelId != null)
|
||||
{
|
||||
var channel = _client.Channels[_channelId];
|
||||
if (channel == null)
|
||||
channel = new Channel(_client, _channelId, _serverId, null);
|
||||
Channel = channel;
|
||||
}
|
||||
}
|
||||
internal override void OnUncached()
|
||||
{
|
||||
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 globalChannels = _client.Channels;
|
||||
var channels = _channels;
|
||||
foreach (var channel in channels)
|
||||
globalChannels.TryRemove(channel.Key);
|
||||
channels.Clear();
|
||||
|
||||
var members = _client.Users;
|
||||
foreach (var user in _members)
|
||||
members.TryRemove(user.Key, Id);
|
||||
var globalMembers = _client.Users;
|
||||
var members = _members;
|
||||
foreach (var user in members)
|
||||
globalMembers.TryRemove(user.Key, Id);
|
||||
members.Clear();
|
||||
|
||||
var roles = _client.Roles;
|
||||
foreach (var role in _roles)
|
||||
roles.TryRemove(role.Key);
|
||||
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];
|
||||
|
||||
public string ServerId { get; }
|
||||
[JsonIgnore]
|
||||
public Server Server => _client.Servers[ServerId];
|
||||
|
||||
public string VoiceChannelId { get; private set; }
|
||||
[JsonIgnore]
|
||||
public Channel VoiceChannel => _client.Channels[VoiceChannelId];
|
||||
internal GlobalUser GlobalUser { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Role> Roles => _roleIds.Select(x => _client.Roles[x]);
|
||||
public Server Server { get; private set; }
|
||||
private string _serverId;
|
||||
|
||||
[JsonIgnore]
|
||||
public Channel VoiceChannel { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user