@@ -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; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -367,5 +368,22 @@ namespace Discord.WebSocket | |||||
remove { _recipientRemovedEvent.Remove(value); } | remove { _recipientRemovedEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | 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.UserIsTyping += (oldUser, newUser) => _userIsTypingEvent.InvokeAsync(oldUser, newUser); | ||||
client.RecipientAdded += (user) => _recipientAddedEvent.InvokeAsync(user); | client.RecipientAdded += (user) => _recipientAddedEvent.InvokeAsync(user); | ||||
client.RecipientRemoved += (user) => _recipientRemovedEvent.InvokeAsync(user); | client.RecipientRemoved += (user) => _recipientRemovedEvent.InvokeAsync(user); | ||||
client.InviteCreated += (invite) => _inviteCreatedEvent.InvokeAsync(invite); | |||||
client.InviteDeleted += (channel, invite) => _inviteDeletedEvent.InvokeAsync(channel, invite); | |||||
} | } | ||||
//IDiscordClient | //IDiscordClient | ||||
@@ -1635,6 +1635,64 @@ namespace Discord.WebSocket | |||||
} | } | ||||
break; | 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) | //Ignored (User only) | ||||
case "CHANNEL_PINS_ACK": | case "CHANNEL_PINS_ACK": | ||||
await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | 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; | |||||
} | |||||
} |