| @@ -0,0 +1,27 @@ | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| namespace Discord.API.Gateway | |||
| { | |||
| internal class InviteCreateEvent | |||
| { | |||
| [JsonProperty("channel_id")] | |||
| public ulong ChannelId { get; set; } | |||
| [JsonProperty("code")] | |||
| public string Code { get; set; } | |||
| [JsonProperty("created_at")] | |||
| public DateTimeOffset CreatedAt { get; set; } | |||
| [JsonProperty("guild_id")] | |||
| public Optional<ulong> GuildId { get; set; } | |||
| [JsonProperty("inviter")] | |||
| public Optional<User> Inviter { get; set; } | |||
| [JsonProperty("max_age")] | |||
| public int MaxAge { get; set; } | |||
| [JsonProperty("max_uses")] | |||
| public int MaxUses { get; set; } | |||
| [JsonProperty("temporary")] | |||
| public bool Temporary { get; set; } | |||
| [JsonProperty("uses")] | |||
| public int Uses { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API.Gateway | |||
| { | |||
| internal class InviteDeleteEvent | |||
| { | |||
| [JsonProperty("channel_id")] | |||
| public ulong ChannelId { get; set; } | |||
| [JsonProperty("code")] | |||
| public string Code { get; set; } | |||
| [JsonProperty("guild_id")] | |||
| public Optional<ulong> GuildId { get; set; } | |||
| } | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| using Discord.API; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Threading.Tasks; | |||
| @@ -367,5 +368,22 @@ namespace Discord.WebSocket | |||
| remove { _recipientRemovedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||
| //Invites | |||
| /// <summary> Fired when an invite is created. </summary> | |||
| public event Func<SocketInvite, Task> InviteCreated | |||
| { | |||
| add { _inviteCreatedEvent.Add(value); } | |||
| remove { _inviteCreatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketInvite, Task>> _inviteCreatedEvent = new AsyncEvent<Func<SocketInvite, Task>>(); | |||
| /// <summary> Fired when an invite is deleted. </summary> | |||
| /// <remarks> The string is the invite code. </remarks> | |||
| public event Func<SocketGuildChannel, string, Task> InviteDeleted | |||
| { | |||
| add { _inviteDeletedEvent.Add(value); } | |||
| remove { _inviteDeletedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuildChannel, string, Task>> _inviteDeletedEvent = new AsyncEvent<Func<SocketGuildChannel, string, Task>>(); | |||
| } | |||
| } | |||
| @@ -337,6 +337,9 @@ namespace Discord.WebSocket | |||
| client.UserIsTyping += (oldUser, newUser) => _userIsTypingEvent.InvokeAsync(oldUser, newUser); | |||
| client.RecipientAdded += (user) => _recipientAddedEvent.InvokeAsync(user); | |||
| client.RecipientRemoved += (user) => _recipientRemovedEvent.InvokeAsync(user); | |||
| client.InviteCreated += (invite) => _inviteCreatedEvent.InvokeAsync(invite); | |||
| client.InviteDeleted += (channel, invite) => _inviteDeletedEvent.InvokeAsync(channel, invite); | |||
| } | |||
| //IDiscordClient | |||
| @@ -1635,6 +1635,64 @@ namespace Discord.WebSocket | |||
| } | |||
| break; | |||
| //Invites | |||
| case "INVITE_CREATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false); | |||
| var data = (payload as JToken).ToObject<API.Gateway.InviteCreateEvent>(_serializer); | |||
| if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | |||
| { | |||
| var guild = channel.Guild; | |||
| if (!guild.IsSynced) | |||
| { | |||
| await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| SocketGuildUser inviter = null; | |||
| if (data.Inviter.IsSpecified) | |||
| { | |||
| inviter = guild.GetUser(data.Inviter.Value.Id); | |||
| if (inviter == null) | |||
| inviter = guild.AddOrUpdateUser(data.Inviter.Value); | |||
| } | |||
| var invite = SocketInvite.Create(this, guild, channel, inviter, data); | |||
| await TimedInvokeAsync(_inviteCreatedEvent, nameof(InviteCreated), invite).ConfigureAwait(false); | |||
| } | |||
| else | |||
| { | |||
| await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| } | |||
| break; | |||
| case "INVITE_DELETE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false); | |||
| var data = (payload as JToken).ToObject<API.Gateway.InviteDeleteEvent>(_serializer); | |||
| if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | |||
| { | |||
| var guild = channel.Guild; | |||
| if (!guild.IsSynced) | |||
| { | |||
| await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| await TimedInvokeAsync(_inviteDeletedEvent, nameof(InviteDeleted), channel, data.Code).ConfigureAwait(false); | |||
| } | |||
| else | |||
| { | |||
| await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| } | |||
| break; | |||
| //Ignored (User only) | |||
| case "CHANNEL_PINS_ACK": | |||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | |||
| @@ -0,0 +1,137 @@ | |||
| using Discord.Rest; | |||
| using System; | |||
| using System.Diagnostics; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Gateway.InviteCreateEvent; | |||
| namespace Discord.WebSocket | |||
| { | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketInvite : SocketEntity<string>, IInviteMetadata | |||
| { | |||
| private long _createdAtTicks; | |||
| /// <inheritdoc /> | |||
| public ulong ChannelId { get; private set; } | |||
| /// <summary> | |||
| /// Gets the channel where this invite was created. | |||
| /// </summary> | |||
| public SocketGuildChannel Channel { get; private set; } | |||
| /// <inheritdoc /> | |||
| public ulong? GuildId { get; private set; } | |||
| /// <summary> | |||
| /// Gets the guild where this invite was created. | |||
| /// </summary> | |||
| public SocketGuild Guild { get; private set; } | |||
| /// <inheritdoc /> | |||
| ChannelType IInvite.ChannelType => throw new NotImplementedException(); | |||
| /// <inheritdoc /> | |||
| string IInvite.ChannelName => throw new NotImplementedException(); | |||
| /// <inheritdoc /> | |||
| string IInvite.GuildName => throw new NotImplementedException(); | |||
| /// <inheritdoc /> | |||
| int? IInvite.PresenceCount => throw new NotImplementedException(); | |||
| /// <inheritdoc /> | |||
| int? IInvite.MemberCount => throw new NotImplementedException(); | |||
| /// <inheritdoc /> | |||
| bool IInviteMetadata.IsRevoked => throw new NotImplementedException(); | |||
| /// <inheritdoc /> | |||
| public bool IsTemporary { get; private set; } | |||
| /// <inheritdoc /> | |||
| int? IInviteMetadata.MaxAge { get => MaxAge; } | |||
| /// <inheritdoc /> | |||
| int? IInviteMetadata.MaxUses { get => MaxUses; } | |||
| /// <inheritdoc /> | |||
| int? IInviteMetadata.Uses { get => Uses; } | |||
| /// <summary> | |||
| /// Gets the time (in seconds) until the invite expires. | |||
| /// </summary> | |||
| public int MaxAge { get; private set; } | |||
| /// <summary> | |||
| /// Gets the max number of uses this invite may have. | |||
| /// </summary> | |||
| public int MaxUses { get; private set; } | |||
| /// <summary> | |||
| /// Gets the number of times this invite has been used. | |||
| /// </summary> | |||
| public int Uses { get; private set; } | |||
| /// <summary> | |||
| /// Gets the user that created this invite if available. | |||
| /// </summary> | |||
| public SocketGuildUser Inviter { get; private set; } | |||
| /// <inheritdoc /> | |||
| DateTimeOffset? IInviteMetadata.CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); | |||
| /// <summary> | |||
| /// Gets when this invite was created. | |||
| /// </summary> | |||
| public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); | |||
| /// <inheritdoc /> | |||
| public string Code => Id; | |||
| /// <inheritdoc /> | |||
| public string Url => $"{DiscordConfig.InviteUrl}{Code}"; | |||
| internal SocketInvite(DiscordSocketClient discord, SocketGuild guild, SocketGuildChannel channel, SocketGuildUser inviter, string id) | |||
| : base(discord, id) | |||
| { | |||
| Guild = guild; | |||
| Channel = channel; | |||
| Inviter = inviter; | |||
| } | |||
| internal static SocketInvite Create(DiscordSocketClient discord, SocketGuild guild, SocketGuildChannel channel, SocketGuildUser inviter, Model model) | |||
| { | |||
| var entity = new SocketInvite(discord, guild, channel, inviter, model.Code); | |||
| entity.Update(model); | |||
| return entity; | |||
| } | |||
| internal void Update(Model model) | |||
| { | |||
| ChannelId = model.ChannelId; | |||
| GuildId = model.GuildId.IsSpecified ? model.GuildId.Value : Guild.Id; | |||
| IsTemporary = model.Temporary; | |||
| MaxAge = model.MaxAge; | |||
| MaxUses = model.MaxUses; | |||
| Uses = model.Uses; | |||
| _createdAtTicks = model.CreatedAt.UtcTicks; | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task DeleteAsync(RequestOptions options = null) | |||
| => InviteHelper.DeleteAsync(this, Discord, options); | |||
| /// <summary> | |||
| /// Gets the URL of the invite. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A string that resolves to the Url of the invite. | |||
| /// </returns> | |||
| public override string ToString() => Url; | |||
| private string DebuggerDisplay => $"{Url} ({Guild?.Name} / {Channel.Name})"; | |||
| /// <inheritdoc /> | |||
| IGuild IInvite.Guild | |||
| { | |||
| get | |||
| { | |||
| if (Guild != null) | |||
| return Guild; | |||
| if (Channel is IGuildChannel guildChannel) | |||
| return guildChannel.Guild; //If it fails, it'll still return this exception | |||
| throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); | |||
| } | |||
| } | |||
| /// <inheritdoc /> | |||
| IChannel IInvite.Channel | |||
| { | |||
| get | |||
| { | |||
| if (Channel != null) | |||
| return Channel; | |||
| throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); | |||
| } | |||
| } | |||
| /// <inheritdoc /> | |||
| IUser IInviteMetadata.Inviter => Inviter; | |||
| } | |||
| } | |||