| @@ -0,0 +1,190 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public partial class DiscordSocketClient | |||||
| { | |||||
| //General | |||||
| public event Func<Task> Connected | |||||
| { | |||||
| add { _connectedEvent.Add(value); } | |||||
| remove { _connectedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); | |||||
| public event Func<Task> Disconnected | |||||
| { | |||||
| add { _disconnectedEvent.Add(value); } | |||||
| remove { _disconnectedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<Task>> _disconnectedEvent = new AsyncEvent<Func<Task>>(); | |||||
| public event Func<Task> Ready | |||||
| { | |||||
| add { _readyEvent.Add(value); } | |||||
| remove { _readyEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<Task>> _readyEvent = new AsyncEvent<Func<Task>>(); | |||||
| public event Func<int, int, Task> LatencyUpdated | |||||
| { | |||||
| add { _latencyUpdatedEvent.Add(value); } | |||||
| remove { _latencyUpdatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | |||||
| //Channels | |||||
| public event Func<IChannel, Task> ChannelCreated | |||||
| { | |||||
| add { _channelCreatedEvent.Add(value); } | |||||
| remove { _channelCreatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<IChannel, Task>>(); | |||||
| public event Func<IChannel, Task> ChannelDestroyed | |||||
| { | |||||
| add { _channelDestroyedEvent.Add(value); } | |||||
| remove { _channelDestroyedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<IChannel, Task>>(); | |||||
| public event Func<IChannel, IChannel, Task> ChannelUpdated | |||||
| { | |||||
| add { _channelUpdatedEvent.Add(value); } | |||||
| remove { _channelUpdatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IChannel, IChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<IChannel, IChannel, Task>>(); | |||||
| //Messages | |||||
| public event Func<IMessage, Task> MessageReceived | |||||
| { | |||||
| add { _messageReceivedEvent.Add(value); } | |||||
| remove { _messageReceivedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<IMessage, Task>>(); | |||||
| public event Func<ulong, Optional<IMessage>, Task> MessageDeleted | |||||
| { | |||||
| add { _messageDeletedEvent.Add(value); } | |||||
| remove { _messageDeletedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<ulong, Optional<IMessage>, Task>> _messageDeletedEvent = new AsyncEvent<Func<ulong, Optional<IMessage>, Task>>(); | |||||
| public event Func<Optional<IMessage>, IMessage, Task> MessageUpdated | |||||
| { | |||||
| add { _messageUpdatedEvent.Add(value); } | |||||
| remove { _messageUpdatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<Optional<IMessage>, IMessage, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Optional<IMessage>, IMessage, Task>>(); | |||||
| //Roles | |||||
| public event Func<IRole, Task> RoleCreated | |||||
| { | |||||
| add { _roleCreatedEvent.Add(value); } | |||||
| remove { _roleCreatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<IRole, Task>>(); | |||||
| public event Func<IRole, Task> RoleDeleted | |||||
| { | |||||
| add { _roleDeletedEvent.Add(value); } | |||||
| remove { _roleDeletedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<IRole, Task>>(); | |||||
| public event Func<IRole, IRole, Task> RoleUpdated | |||||
| { | |||||
| add { _roleUpdatedEvent.Add(value); } | |||||
| remove { _roleUpdatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IRole, IRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<IRole, IRole, Task>>(); | |||||
| //Guilds | |||||
| public event Func<IGuild, Task> JoinedGuild | |||||
| { | |||||
| add { _joinedGuildEvent.Add(value); } | |||||
| remove { _joinedGuildEvent.Remove(value); } | |||||
| } | |||||
| private AsyncEvent<Func<IGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> LeftGuild | |||||
| { | |||||
| add { _leftGuildEvent.Add(value); } | |||||
| remove { _leftGuildEvent.Remove(value); } | |||||
| } | |||||
| private AsyncEvent<Func<IGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> GuildAvailable | |||||
| { | |||||
| add { _guildAvailableEvent.Add(value); } | |||||
| remove { _guildAvailableEvent.Remove(value); } | |||||
| } | |||||
| private AsyncEvent<Func<IGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> GuildUnavailable | |||||
| { | |||||
| add { _guildUnavailableEvent.Add(value); } | |||||
| remove { _guildUnavailableEvent.Remove(value); } | |||||
| } | |||||
| private AsyncEvent<Func<IGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> GuildDownloadedMembers | |||||
| { | |||||
| add { _guildDownloadedMembersEvent.Add(value); } | |||||
| remove { _guildDownloadedMembersEvent.Remove(value); } | |||||
| } | |||||
| private AsyncEvent<Func<IGuild, Task>> _guildDownloadedMembersEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, IGuild, Task> GuildUpdated | |||||
| { | |||||
| add { _guildUpdatedEvent.Add(value); } | |||||
| remove { _guildUpdatedEvent.Remove(value); } | |||||
| } | |||||
| private AsyncEvent<Func<IGuild, IGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<IGuild, IGuild, Task>>(); | |||||
| //Users | |||||
| public event Func<IGuildUser, Task> UserJoined | |||||
| { | |||||
| add { _userJoinedEvent.Add(value); } | |||||
| remove { _userJoinedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<IGuildUser, Task>>(); | |||||
| public event Func<IGuildUser, Task> UserLeft | |||||
| { | |||||
| add { _userLeftEvent.Add(value); } | |||||
| remove { _userLeftEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<IGuildUser, Task>>(); | |||||
| public event Func<IUser, IGuild, Task> UserBanned | |||||
| { | |||||
| add { _userBannedEvent.Add(value); } | |||||
| remove { _userBannedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IUser, IGuild, Task>> _userBannedEvent = new AsyncEvent<Func<IUser, IGuild, Task>>(); | |||||
| public event Func<IUser, IGuild, Task> UserUnbanned | |||||
| { | |||||
| add { _userUnbannedEvent.Add(value); } | |||||
| remove { _userUnbannedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IUser, IGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<IUser, IGuild, Task>>(); | |||||
| public event Func<IGuildUser, IGuildUser, Task> UserUpdated | |||||
| { | |||||
| add { _userUpdatedEvent.Add(value); } | |||||
| remove { _userUpdatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IGuildUser, IGuildUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<IGuildUser, IGuildUser, Task>>(); | |||||
| public event Func<IGuildUser, IPresence, IPresence, Task> UserPresenceUpdated | |||||
| { | |||||
| add { _userPresenceUpdatedEvent.Add(value); } | |||||
| remove { _userPresenceUpdatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IGuildUser, IPresence, IPresence, Task>> _userPresenceUpdatedEvent = new AsyncEvent<Func<IGuildUser, IPresence, IPresence, Task>>(); | |||||
| public event Func<IGuildUser, IVoiceState, IVoiceState, Task> UserVoiceStateUpdated | |||||
| { | |||||
| add { _userVoiceStateUpdatedEvent.Add(value); } | |||||
| remove { _userVoiceStateUpdatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IGuildUser, IVoiceState, IVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<IGuildUser, IVoiceState, IVoiceState, Task>>(); | |||||
| public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated | |||||
| { | |||||
| add { _selfUpdatedEvent.Add(value); } | |||||
| remove { _selfUpdatedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<ISelfUser, ISelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<ISelfUser, ISelfUser, Task>>(); | |||||
| public event Func<IUser, IChannel, Task> UserIsTyping | |||||
| { | |||||
| add { _userIsTypingEvent.Add(value); } | |||||
| remove { _userIsTypingEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IUser, IChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<IUser, IChannel, Task>>(); | |||||
| //TODO: Add PresenceUpdated? VoiceStateUpdated?, VoiceConnected, VoiceDisconnected; | |||||
| } | |||||
| } | |||||
| @@ -19,77 +19,8 @@ namespace Discord | |||||
| //TODO: Add event docstrings | //TODO: Add event docstrings | ||||
| //TODO: Add reconnect logic (+ensure the heartbeat task to shut down) | //TODO: Add reconnect logic (+ensure the heartbeat task to shut down) | ||||
| //TODO: Add resume logic | //TODO: Add resume logic | ||||
| public class DiscordSocketClient : DiscordClient, IDiscordClient | |||||
| public partial class DiscordSocketClient : DiscordClient, IDiscordClient | |||||
| { | { | ||||
| private object _eventLock = new object(); | |||||
| //General | |||||
| public event Func<Task> Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); | |||||
| public event Func<Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<Task>> _disconnectedEvent = new AsyncEvent<Func<Task>>(); | |||||
| public event Func<Task> Ready { add { _readyEvent.Add(value); } remove { _readyEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<Task>> _readyEvent = new AsyncEvent<Func<Task>>(); | |||||
| public event Func<int, int, Task> LatencyUpdated { add { _latencyUpdatedEvent.Add(value); } remove { _latencyUpdatedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | |||||
| //Channels | |||||
| public event Func<IChannel, Task> ChannelCreated { add { _channelCreatedEvent.Add(value); } remove { _channelCreatedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<IChannel, Task>>(); | |||||
| public event Func<IChannel, Task> ChannelDestroyed { add { _channelDestroyedEvent.Add(value); } remove { _channelDestroyedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<IChannel, Task>>(); | |||||
| public event Func<IChannel, IChannel, Task> ChannelUpdated { add { _channelUpdatedEvent.Add(value); } remove { _channelUpdatedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IChannel, IChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<IChannel, IChannel, Task>>(); | |||||
| //Messages | |||||
| public event Func<IMessage, Task> MessageReceived { add { _messageReceivedEvent.Add(value); } remove { _messageReceivedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<IMessage, Task>>(); | |||||
| public event Func<ulong, Optional<IMessage>, Task> MessageDeleted { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<ulong, Optional<IMessage>, Task>> _messageDeletedEvent = new AsyncEvent<Func<ulong, Optional<IMessage>, Task>>(); | |||||
| public event Func<Optional<IMessage>, IMessage, Task> MessageUpdated { add { _messageUpdatedEvent.Add(value); } remove { _messageUpdatedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<Optional<IMessage>, IMessage, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Optional<IMessage>, IMessage, Task>>(); | |||||
| //Roles | |||||
| public event Func<IRole, Task> RoleCreated { add { _roleCreatedEvent.Add(value); } remove { _roleCreatedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<IRole, Task>>(); | |||||
| public event Func<IRole, Task> RoleDeleted { add { _roleDeletedEvent.Add(value); } remove { _roleDeletedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<IRole, Task>>(); | |||||
| public event Func<IRole, IRole, Task> RoleUpdated { add { _roleUpdatedEvent.Add(value); } remove { _roleUpdatedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IRole, IRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<IRole, IRole, Task>>(); | |||||
| //Guilds | |||||
| public event Func<IGuild, Task> JoinedGuild { add { _joinedGuildEvent.Add(value); } remove { _joinedGuildEvent.Remove(value); } } | |||||
| private AsyncEvent<Func<IGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> LeftGuild { add { _leftGuildEvent.Add(value); } remove { _leftGuildEvent.Remove(value); } } | |||||
| private AsyncEvent<Func<IGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> GuildAvailable { add { _guildAvailableEvent.Add(value); } remove { _guildAvailableEvent.Remove(value); } } | |||||
| private AsyncEvent<Func<IGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> GuildUnavailable { add { _guildUnavailableEvent.Add(value); } remove { _guildUnavailableEvent.Remove(value); } } | |||||
| private AsyncEvent<Func<IGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, Task> GuildDownloadedMembers { add { _guildDownloadedMembersEvent.Add(value); } remove { _guildDownloadedMembersEvent.Remove(value); } } | |||||
| private AsyncEvent<Func<IGuild, Task>> _guildDownloadedMembersEvent = new AsyncEvent<Func<IGuild, Task>>(); | |||||
| public event Func<IGuild, IGuild, Task> GuildUpdated { add { _guildUpdatedEvent.Add(value); } remove { _guildUpdatedEvent.Remove(value); } } | |||||
| private AsyncEvent<Func<IGuild, IGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<IGuild, IGuild, Task>>(); | |||||
| //Users | |||||
| public event Func<IGuildUser, Task> UserJoined { add { _userJoinedEvent.Add(value); } remove { _userJoinedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<IGuildUser, Task>>(); | |||||
| public event Func<IGuildUser, Task> UserLeft { add { _userLeftEvent.Add(value); } remove { _userLeftEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<IGuildUser, Task>>(); | |||||
| public event Func<IUser, IGuild, Task> UserBanned { add { _userBannedEvent.Add(value); } remove { _userBannedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IUser, IGuild, Task>> _userBannedEvent = new AsyncEvent<Func<IUser, IGuild, Task>>(); | |||||
| public event Func<IUser, IGuild, Task> UserUnbanned { add { _userUnbannedEvent.Add(value); } remove { _userUnbannedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IUser, IGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<IUser, IGuild, Task>>(); | |||||
| public event Func<Optional<IGuildUser>, IGuildUser, Task> UserUpdated { add { _userUpdatedEvent.Add(value); } remove { _userUpdatedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<Optional<IGuildUser>, IGuildUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<Optional<IGuildUser>, IGuildUser, Task>>(); | |||||
| public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated { add { _selfUpdatedEvent.Add(value); } remove { _selfUpdatedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<ISelfUser, ISelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<ISelfUser, ISelfUser, Task>>(); | |||||
| public event Func<IUser, IChannel, Task> UserIsTyping { add { _userIsTypingEvent.Add(value); } remove { _userIsTypingEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<IUser, IChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<IUser, IChannel, Task>>(); | |||||
| //TODO: Add PresenceUpdated? VoiceStateUpdated?, VoiceConnected, VoiceDisconnected; | |||||
| private readonly ConcurrentQueue<ulong> _largeGuilds; | private readonly ConcurrentQueue<ulong> _largeGuilds; | ||||
| private readonly Logger _gatewayLogger; | private readonly Logger _gatewayLogger; | ||||
| #if BENCHMARK | #if BENCHMARK | ||||
| @@ -1066,19 +997,20 @@ namespace Discord | |||||
| break; | break; | ||||
| } | } | ||||
| IPresence before; | |||||
| var user = guild.GetUser(data.User.Id); | var user = guild.GetUser(data.User.Id); | ||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| var before = user.Clone(); | |||||
| before = user.Presence.Clone(); | |||||
| user.Update(data, UpdateSource.WebSocket); | user.Update(data, UpdateSource.WebSocket); | ||||
| await _userUpdatedEvent.InvokeAsync(before, user).ConfigureAwait(false); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| before = new Presence(null, UserStatus.Offline); | |||||
| user = guild.AddOrUpdateUser(data, DataStore); | user = guild.AddOrUpdateUser(data, DataStore); | ||||
| user.Update(data, UpdateSource.WebSocket); | |||||
| await _userUpdatedEvent.InvokeAsync(Optional.Create<IGuildUser>(), user).ConfigureAwait(false); | |||||
| } | } | ||||
| await _userPresenceUpdatedEvent.InvokeAsync(user, before, user).ConfigureAwait(false); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -1103,6 +1035,26 @@ namespace Discord | |||||
| } | } | ||||
| break; | break; | ||||
| //Users | |||||
| case "USER_UPDATE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<API.User>(_serializer); | |||||
| if (data.Id == CurrentUser.Id) | |||||
| { | |||||
| var before = CurrentUser.Clone(); | |||||
| CurrentUser.Update(data, UpdateSource.WebSocket); | |||||
| await _selfUpdatedEvent.InvokeAsync(before, CurrentUser).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| { | |||||
| await _gatewayLogger.WarningAsync("Received USER_UPDATE for wrong user.").ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| } | |||||
| break; | |||||
| //Voice | //Voice | ||||
| case "VOICE_STATE_UPDATE": | case "VOICE_STATE_UPDATE": | ||||
| { | { | ||||
| @@ -1114,17 +1066,27 @@ namespace Discord | |||||
| var guild = DataStore.GetGuild(data.GuildId.Value); | var guild = DataStore.GetGuild(data.GuildId.Value); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| if (data.ChannelId == null) | |||||
| guild.RemoveVoiceState(data.UserId); | |||||
| VoiceState before, after; | |||||
| if (data.ChannelId != null) | |||||
| { | |||||
| before = guild.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false); | |||||
| after = guild.AddOrUpdateVoiceState(data, DataStore); | |||||
| } | |||||
| else | else | ||||
| guild.AddOrUpdateVoiceState(data, DataStore); | |||||
| { | |||||
| before = guild.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false); | |||||
| after = new VoiceState(null, data); | |||||
| } | |||||
| var user = guild.GetUser(data.UserId); | var user = guild.GetUser(data.UserId); | ||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| var before = user.Clone(); | |||||
| user.Update(data, UpdateSource.WebSocket); | |||||
| await _userUpdatedEvent.InvokeAsync(before, user).ConfigureAwait(false); | |||||
| await _userVoiceStateUpdatedEvent.InvokeAsync(user, before, after).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| { | |||||
| await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown user.").ConfigureAwait(false); | |||||
| return; | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| @@ -1136,26 +1098,6 @@ namespace Discord | |||||
| } | } | ||||
| break; | break; | ||||
| //Settings | |||||
| case "USER_UPDATE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<API.User>(_serializer); | |||||
| if (data.Id == CurrentUser.Id) | |||||
| { | |||||
| var before = CurrentUser.Clone(); | |||||
| CurrentUser.Update(data, UpdateSource.WebSocket); | |||||
| await _selfUpdatedEvent.InvokeAsync(before, CurrentUser).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| { | |||||
| await _gatewayLogger.WarningAsync("Received USER_UPDATE for wrong user.").ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| } | |||||
| break; | |||||
| //Ignored | //Ignored | ||||
| case "USER_SETTINGS_UPDATE": | case "USER_SETTINGS_UPDATE": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | ||||
| @@ -15,9 +15,7 @@ namespace Discord | |||||
| internal class GuildUser : IGuildUser, ISnowflakeEntity | internal class GuildUser : IGuildUser, ISnowflakeEntity | ||||
| { | { | ||||
| private long? _joinedAtTicks; | private long? _joinedAtTicks; | ||||
| public bool IsDeaf { get; private set; } | |||||
| public bool IsMute { get; private set; } | |||||
| public string Nickname { get; private set; } | public string Nickname { get; private set; } | ||||
| public GuildPermissions GuildPermissions { get; private set; } | public GuildPermissions GuildPermissions { get; private set; } | ||||
| @@ -59,11 +57,7 @@ namespace Discord | |||||
| public void Update(Model model, UpdateSource source) | public void Update(Model model, UpdateSource source) | ||||
| { | { | ||||
| if (source == UpdateSource.Rest && IsAttached) return; | if (source == UpdateSource.Rest && IsAttached) return; | ||||
| //if (model.Deaf.IsSpecified) | |||||
| IsDeaf = model.Deaf; | |||||
| //if (model.Mute.IsSpecified) | |||||
| IsMute = model.Mute; | |||||
| //if (model.JoinedAt.IsSpecified) | //if (model.JoinedAt.IsSpecified) | ||||
| _joinedAtTicks = model.JoinedAt.UtcTicks; | _joinedAtTicks = model.JoinedAt.UtcTicks; | ||||
| if (model.Nick.IsSpecified) | if (model.Nick.IsSpecified) | ||||
| @@ -81,13 +75,6 @@ namespace Discord | |||||
| if (model.Nick.IsSpecified) | if (model.Nick.IsSpecified) | ||||
| Nickname = model.Nick.Value; | Nickname = model.Nick.Value; | ||||
| } | } | ||||
| public void Update(VoiceStateModel model, UpdateSource source) | |||||
| { | |||||
| if (source == UpdateSource.Rest && IsAttached) return; | |||||
| IsDeaf = model.Deaf; | |||||
| IsMute = model.Mute; | |||||
| } | |||||
| private void UpdateRoles(ulong[] roleIds) | private void UpdateRoles(ulong[] roleIds) | ||||
| { | { | ||||
| var roles = ImmutableArray.CreateBuilder<Role>(roleIds.Length + 1); | var roles = ImmutableArray.CreateBuilder<Role>(roleIds.Length + 1); | ||||
| @@ -127,10 +114,6 @@ namespace Discord | |||||
| if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.RoleIds.IsSpecified) | if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.RoleIds.IsSpecified) | ||||
| { | { | ||||
| await Discord.ApiClient.ModifyGuildMemberAsync(Guild.Id, Id, args).ConfigureAwait(false); | await Discord.ApiClient.ModifyGuildMemberAsync(Guild.Id, Id, args).ConfigureAwait(false); | ||||
| if (args.Deaf.IsSpecified) | |||||
| IsDeaf = args.Deaf.Value; | |||||
| if (args.Mute.IsSpecified) | |||||
| IsMute = args.Mute.Value; | |||||
| if (args.Nickname.IsSpecified) | if (args.Nickname.IsSpecified) | ||||
| Nickname = args.Nickname.Value ?? ""; | Nickname = args.Nickname.Value ?? ""; | ||||
| if (args.RoleIds.IsSpecified) | if (args.RoleIds.IsSpecified) | ||||
| @@ -161,6 +144,8 @@ namespace Discord | |||||
| IGuild IGuildUser.Guild => Guild; | IGuild IGuildUser.Guild => Guild; | ||||
| IReadOnlyCollection<IRole> IGuildUser.Roles => Roles; | IReadOnlyCollection<IRole> IGuildUser.Roles => Roles; | ||||
| bool IVoiceState.IsDeafened => false; | |||||
| bool IVoiceState.IsMuted => false; | |||||
| bool IVoiceState.IsSelfDeafened => false; | bool IVoiceState.IsSelfDeafened => false; | ||||
| bool IVoiceState.IsSelfMuted => false; | bool IVoiceState.IsSelfMuted => false; | ||||
| bool IVoiceState.IsSuppressed => false; | bool IVoiceState.IsSuppressed => false; | ||||
| @@ -8,10 +8,6 @@ namespace Discord | |||||
| /// <summary> A Guild-User pairing. </summary> | /// <summary> A Guild-User pairing. </summary> | ||||
| public interface IGuildUser : IUpdateable, IUser, IVoiceState | public interface IGuildUser : IUpdateable, IUser, IVoiceState | ||||
| { | { | ||||
| /// <summary> Returns true if the guild has deafened this user. </summary> | |||||
| bool IsDeaf { get; } | |||||
| /// <summary> Returns true if the guild has muted this user. </summary> | |||||
| bool IsMute { get; } | |||||
| /// <summary> Gets when this user joined this guild. </summary> | /// <summary> Gets when this user joined this guild. </summary> | ||||
| DateTimeOffset? JoinedAt { get; } | DateTimeOffset? JoinedAt { get; } | ||||
| /// <summary> Gets the nickname for this user. </summary> | /// <summary> Gets the nickname for this user. </summary> | ||||
| @@ -2,6 +2,10 @@ | |||||
| { | { | ||||
| public interface IVoiceState | public interface IVoiceState | ||||
| { | { | ||||
| /// <summary> Returns true if the guild has deafened this user. </summary> | |||||
| bool IsDeafened { get; } | |||||
| /// <summary> Returns true if the guild has muted this user. </summary> | |||||
| bool IsMuted { get; } | |||||
| /// <summary> Returns true if this user has marked themselves as deafened. </summary> | /// <summary> Returns true if this user has marked themselves as deafened. </summary> | ||||
| bool IsSelfDeafened { get; } | bool IsSelfDeafened { get; } | ||||
| /// <summary> Returns true if this user has marked themselves as muted. </summary> | /// <summary> Returns true if this user has marked themselves as muted. </summary> | ||||
| @@ -204,7 +204,7 @@ namespace Discord | |||||
| public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null) | public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null) | ||||
| { | { | ||||
| var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as CachedVoiceChannel; | var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as CachedVoiceChannel; | ||||
| var voiceState = new VoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress); | |||||
| var voiceState = new VoiceState(voiceChannel, model); | |||||
| (voiceStates ?? _voiceStates)[model.UserId] = voiceState; | (voiceStates ?? _voiceStates)[model.UserId] = voiceState; | ||||
| return voiceState; | return voiceState; | ||||
| } | } | ||||
| @@ -3,17 +3,31 @@ using PresenceModel = Discord.API.Presence; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| //TODO: C#7 Candidate for record type | |||||
| internal struct Presence : IPresence | |||||
| { | |||||
| public Game Game { get; } | |||||
| public UserStatus Status { get; } | |||||
| public Presence(Game game, UserStatus status) | |||||
| { | |||||
| Game = game; | |||||
| Status = status; | |||||
| } | |||||
| public Presence Clone() => this; | |||||
| } | |||||
| internal class CachedGuildUser : GuildUser, ICachedUser | internal class CachedGuildUser : GuildUser, ICachedUser | ||||
| { | { | ||||
| private Game _game; | |||||
| private UserStatus _status; | |||||
| public Presence Presence { get; private set; } | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
| public new CachedGuild Guild => base.Guild as CachedGuild; | public new CachedGuild Guild => base.Guild as CachedGuild; | ||||
| public new CachedGlobalUser User => base.User as CachedGlobalUser; | public new CachedGlobalUser User => base.User as CachedGlobalUser; | ||||
| public override Game Game => _game; | |||||
| public override UserStatus Status => _status; | |||||
| public override Game Game => Presence.Game; | |||||
| public override UserStatus Status => Presence.Status; | |||||
| public VoiceState? VoiceState => Guild.GetVoiceState(Id); | public VoiceState? VoiceState => Guild.GetVoiceState(Id); | ||||
| public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; | public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; | ||||
| @@ -34,8 +48,8 @@ namespace Discord | |||||
| { | { | ||||
| base.Update(model, source); | base.Update(model, source); | ||||
| _status = model.Status; | |||||
| _game = model.Game != null ? new Game(model.Game) : (Game)null; | |||||
| var game = model.Game != null ? new Game(model.Game) : null; | |||||
| Presence = new Presence(game, model.Status); | |||||
| } | } | ||||
| public CachedGuildUser Clone() => MemberwiseClone() as CachedGuildUser; | public CachedGuildUser Clone() => MemberwiseClone() as CachedGuildUser; | ||||
| @@ -1,16 +1,20 @@ | |||||
| using System; | using System; | ||||
| using Model = Discord.API.VoiceState; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| //TODO: C#7 Candidate for record type | |||||
| internal struct VoiceState : IVoiceState | internal struct VoiceState : IVoiceState | ||||
| { | { | ||||
| [Flags] | [Flags] | ||||
| private enum Flags : byte | private enum Flags : byte | ||||
| { | { | ||||
| None = 0x0, | |||||
| Suppressed = 0x1, | |||||
| SelfMuted = 0x2, | |||||
| SelfDeafened = 0x4, | |||||
| None = 0x00, | |||||
| Suppressed = 0x01, | |||||
| Muted = 0x02, | |||||
| Deafened = 0x04, | |||||
| SelfMuted = 0x08, | |||||
| SelfDeafened = 0x10, | |||||
| } | } | ||||
| private readonly Flags _voiceStates; | private readonly Flags _voiceStates; | ||||
| @@ -18,10 +22,14 @@ namespace Discord | |||||
| public CachedVoiceChannel VoiceChannel { get; } | public CachedVoiceChannel VoiceChannel { get; } | ||||
| public string VoiceSessionId { get; } | public string VoiceSessionId { get; } | ||||
| public bool IsMuted => (_voiceStates & Flags.Muted) != 0; | |||||
| public bool IsDeafened => (_voiceStates & Flags.Deafened) != 0; | |||||
| public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0; | |||||
| public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0; | public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0; | ||||
| public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; | public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; | ||||
| public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0; | |||||
| public VoiceState(CachedVoiceChannel voiceChannel, Model model) | |||||
| : this(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress) { } | |||||
| public VoiceState(CachedVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isSuppressed) | public VoiceState(CachedVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isSuppressed) | ||||
| { | { | ||||
| VoiceChannel = voiceChannel; | VoiceChannel = voiceChannel; | ||||
| @@ -37,6 +45,8 @@ namespace Discord | |||||
| _voiceStates = voiceStates; | _voiceStates = voiceStates; | ||||
| } | } | ||||
| public VoiceState Clone() => this; | |||||
| IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; | IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; | ||||
| } | } | ||||
| } | } | ||||