| @@ -63,4 +63,60 @@ namespace Discord | |||
| /// </returns> | |||
| public async Task<TEntity> GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync().ConfigureAwait(false); | |||
| } | |||
| public struct Cacheable<TCachedEntity, TDownloadableEntity, TRelationship, TId> | |||
| where TCachedEntity : IEntity<TId>, TRelationship | |||
| where TDownloadableEntity : IEntity<TId>, TRelationship | |||
| where TId : IEquatable<TId> | |||
| { | |||
| /// <summary> | |||
| /// Gets whether this entity is cached. | |||
| /// </summary> | |||
| public bool HasValue { get; } | |||
| /// <summary> | |||
| /// Gets the ID of this entity. | |||
| /// </summary> | |||
| public TId Id { get; } | |||
| /// <summary> | |||
| /// Gets the entity if it could be pulled from cache. | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// This value is not guaranteed to be set; in cases where the entity cannot be pulled from cache, it is | |||
| /// <c>null</c>. | |||
| /// </remarks> | |||
| public TCachedEntity Value { get; } | |||
| private Func<Task<TDownloadableEntity>> DownloadFunc { get; } | |||
| internal Cacheable(TCachedEntity value, TId id, bool hasValue, Func<Task<TDownloadableEntity>> downloadFunc) | |||
| { | |||
| Value = value; | |||
| Id = id; | |||
| HasValue = hasValue; | |||
| DownloadFunc = downloadFunc; | |||
| } | |||
| /// <summary> | |||
| /// Downloads this entity. | |||
| /// </summary> | |||
| /// <exception cref="Discord.Net.HttpException">Thrown when used from a user account.</exception> | |||
| /// <exception cref="NullReferenceException">Thrown when the message is deleted.</exception> | |||
| /// <returns> | |||
| /// A task that represents the asynchronous download operation. The task result contains the downloaded | |||
| /// entity. | |||
| /// </returns> | |||
| public async Task<TDownloadableEntity> DownloadAsync() | |||
| { | |||
| return await DownloadFunc().ConfigureAwait(false); | |||
| } | |||
| /// <summary> | |||
| /// Returns the cached entity if it exists; otherwise downloads it. | |||
| /// </summary> | |||
| /// <exception cref="Discord.Net.HttpException">Thrown when used from a user account.</exception> | |||
| /// <exception cref="NullReferenceException">Thrown when the message is deleted and is not in cache.</exception> | |||
| /// <returns> | |||
| /// A task that represents the asynchronous operation that attempts to get the message via cache or to | |||
| /// download the message. The task result contains the downloaded entity. | |||
| /// </returns> | |||
| public async Task<TRelationship> GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync().ConfigureAwait(false); | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.API.Gateway | |||
| { | |||
| internal class GuildScheduledEventUserAddRemoveEvent | |||
| { | |||
| [JsonProperty("guild_scheduled_event_id")] | |||
| public ulong EventId { get; set; } | |||
| [JsonProperty("guild_id")] | |||
| public ulong GuildId { get; set; } | |||
| [JsonProperty("user_id")] | |||
| public ulong UserId { get; set; } | |||
| } | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| using Discord.Rest; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Threading.Tasks; | |||
| @@ -397,6 +398,21 @@ namespace Discord.WebSocket | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuildEvent, Task>> _guildScheduledEventStarted = new AsyncEvent<Func<SocketGuildEvent, Task>>(); | |||
| public event Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task> GuildScheduledEventUserAdd | |||
| { | |||
| add { _guildScheduledEventUserAdd.Add(value); } | |||
| remove { _guildScheduledEventUserAdd.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task>> _guildScheduledEventUserAdd = new AsyncEvent<Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task>>(); | |||
| public event Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task> GuildScheduledEventUserRemove | |||
| { | |||
| add { _guildScheduledEventUserRemove.Add(value); } | |||
| remove { _guildScheduledEventUserRemove.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task>> _guildScheduledEventUserRemove = new AsyncEvent<Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task>>(); | |||
| #endregion | |||
| #region Users | |||
| @@ -2597,6 +2597,7 @@ namespace Discord.WebSocket | |||
| } | |||
| var before = guild.GetEvent(data.Id); | |||
| var beforeCacheable = new Cacheable<SocketGuildEvent, ulong>(before, data.Id, before != null, () => Task.FromResult((SocketGuildEvent)null)); | |||
| var after = guild.AddOrUpdateEvent(data); | |||
| @@ -2631,6 +2632,44 @@ namespace Discord.WebSocket | |||
| await TimedInvokeAsync(_guildScheduledEventCancelled, nameof(GuildScheduledEventCancelled), guildEvent).ConfigureAwait(false); | |||
| } | |||
| break; | |||
| case "GUILD_SCHEDULED_EVENT_USER_ADD" or "GUILD_SCHEDULED_EVENT_USER_REMOVE": | |||
| { | |||
| await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); | |||
| var data = (payload as JToken).ToObject<GuildScheduledEventUserAddRemoveEvent>(_serializer); | |||
| var guild = State.GetGuild(data.GuildId); | |||
| if(guild == null) | |||
| { | |||
| await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| var guildEvent = guild.GetEvent(data.EventId); | |||
| if (guildEvent == null) | |||
| { | |||
| await UnknownGuildEventAsync(type, data.EventId, data.GuildId).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| var user = (SocketUser)guild.GetUser(data.UserId) ?? State.GetUser(data.UserId); | |||
| var cacheableUser = new Cacheable<SocketUser, RestUser, IUser, ulong>(user, data.UserId, user != null, () => Rest.GetUserAsync(data.UserId)); | |||
| switch (type) | |||
| { | |||
| case "GUILD_SCHEDULED_EVENT_USER_ADD": | |||
| await TimedInvokeAsync(_guildScheduledEventUserAdd, nameof(GuildScheduledEventUserAdd), cacheableUser, guildEvent).ConfigureAwait(false); | |||
| break; | |||
| case "GUILD_SCHEDULED_EVENT_USER_REMOVE": | |||
| await TimedInvokeAsync(_guildScheduledEventUserRemove, nameof(GuildScheduledEventUserRemove), cacheableUser, guildEvent).ConfigureAwait(false); | |||
| break; | |||
| } | |||
| } | |||
| break; | |||
| #endregion | |||
| @@ -2947,6 +2986,12 @@ namespace Discord.WebSocket | |||
| string details = $"{evnt} Guild={guildId}"; | |||
| await _gatewayLogger.WarningAsync($"Unknown Guild ({details}).").ConfigureAwait(false); | |||
| } | |||
| private async Task UnknownGuildEventAsync(string evnt, ulong eventId, ulong guildId) | |||
| { | |||
| string details = $"{evnt} Event={eventId} Guild={guildId}"; | |||
| await _gatewayLogger.WarningAsync($"Unknown Guild Event ({details}).").ConfigureAwait(false); | |||
| } | |||
| private async Task UnsyncedGuildAsync(string evnt, ulong guildId) | |||
| { | |||
| string details = $"{evnt} Guild={guildId}"; | |||