| @@ -5653,7 +5653,7 @@ | |||
| Gets or sets this menu options description. | |||
| </summary> | |||
| <exception cref="T:System.ArgumentException" accessor="set"><see cref="P:Discord.SelectMenuOptionBuilder.Description"/> length exceeds <see cref="F:Discord.SelectMenuOptionBuilder.MaxDescriptionLength"/>.</exception> | |||
| <exception cref="T:System.ArgumentException" accessor="set"><see cref="P:Discord.SelectMenuOptionBuilder.Label"/> length subceeds 1.</exception> | |||
| <exception cref="T:System.ArgumentException" accessor="set"><see cref="P:Discord.SelectMenuOptionBuilder.Description"/> length subceeds 1.</exception> | |||
| </member> | |||
| <member name="P:Discord.SelectMenuOptionBuilder.Emote"> | |||
| <summary> | |||
| @@ -10979,7 +10979,7 @@ | |||
| <seealso cref="M:Discord.IMessage.RemoveReactionAsync(Discord.IEmote,Discord.IUser,Discord.RequestOptions)"/> | |||
| <seealso cref="T:Discord.IEmote"/> | |||
| </member> | |||
| <member name="M:Discord.MessageExtensions.ReplyAsync(Discord.IUserMessage,System.String,System.Boolean,Discord.Embed,Discord.AllowedMentions,Discord.RequestOptions)"> | |||
| <member name="M:Discord.MessageExtensions.ReplyAsync(Discord.IUserMessage,System.String,System.Boolean,Discord.Embed,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.ISticker[])"> | |||
| <summary> | |||
| Sends an inline reply that references a message. | |||
| </summary> | |||
| @@ -367,8 +367,6 @@ namespace Discord | |||
| if (value.Length < 1) | |||
| throw new ArgumentException("Button label must be 1 character or more!", paramName: nameof(Label)); | |||
| } | |||
| else | |||
| throw new ArgumentException("Button label must not be null or empty!", paramName: nameof(Label)); | |||
| _label = value; | |||
| } | |||
| @@ -391,8 +389,6 @@ namespace Discord | |||
| if (value.Length < 1) | |||
| throw new ArgumentException("Custom Id must be 1 character or more!", paramName: nameof(CustomId)); | |||
| } | |||
| else | |||
| throw new ArgumentException("Custom Id must not be null or empty!", paramName: nameof(CustomId)); | |||
| _customId = value; | |||
| } | |||
| } | |||
| @@ -644,8 +640,6 @@ namespace Discord | |||
| if (value.Length < 1) | |||
| throw new ArgumentException("Custom Id must be 1 character or more!", paramName: nameof(CustomId)); | |||
| } | |||
| else | |||
| throw new ArgumentException("Custom Id must not be null or empty!", paramName: nameof(CustomId)); | |||
| _customId = value; | |||
| } | |||
| } | |||
| @@ -667,8 +661,6 @@ namespace Discord | |||
| if (value.Length < 1) | |||
| throw new ArgumentException("The placeholder must be 1 character or more!", paramName: nameof(Placeholder)); | |||
| } | |||
| else | |||
| throw new ArgumentException("The placeholder must not be null or empty!", paramName: nameof(Placeholder)); | |||
| _placeholder = value; | |||
| } | |||
| @@ -938,8 +930,6 @@ namespace Discord | |||
| if (value.Length < 1) | |||
| throw new ArgumentException("Select option label must be 1 character or more!", paramName: nameof(Label)); | |||
| } | |||
| else | |||
| throw new ArgumentException("Select option label must not be null or empty!", paramName: nameof(Label)); | |||
| _label = value; | |||
| } | |||
| @@ -973,7 +963,7 @@ namespace Discord | |||
| /// Gets or sets this menu options description. | |||
| /// </summary> | |||
| /// <exception cref="ArgumentException" accessor="set"><see cref="Description"/> length exceeds <see cref="MaxDescriptionLength"/>.</exception> | |||
| /// <exception cref="ArgumentException" accessor="set"><see cref="Label"/> length subceeds 1.</exception> | |||
| /// <exception cref="ArgumentException" accessor="set"><see cref="Description"/> length subceeds 1.</exception> | |||
| public string Description | |||
| { | |||
| get => _description; | |||
| @@ -986,8 +976,6 @@ namespace Discord | |||
| if (value.Length < 1) | |||
| throw new ArgumentException("The description must be 1 character or more!", paramName: nameof(Label)); | |||
| } | |||
| else | |||
| throw new ArgumentException("The description must not be null or empty!", paramName: nameof(Label)); | |||
| _description = value; | |||
| } | |||
| @@ -87,9 +87,9 @@ namespace Discord | |||
| /// A task that represents an asynchronous send operation for delivering the message. The task result | |||
| /// contains the sent message. | |||
| /// </returns> | |||
| public static async Task<IUserMessage> ReplyAsync(this IUserMessage msg, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, RequestOptions options = null) | |||
| public static async Task<IUserMessage> ReplyAsync(this IUserMessage msg, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, ISticker[] stickers = null) | |||
| { | |||
| return await msg.Channel.SendMessageAsync(text, isTTS, embed, options, allowedMentions, new MessageReference(messageId: msg.Id)).ConfigureAwait(false); | |||
| return await msg.Channel.SendMessageAsync(text, isTTS, embed, options, allowedMentions, new MessageReference(messageId: msg.Id), components, stickers).ConfigureAwait(false); | |||
| } | |||
| } | |||
| } | |||
| @@ -4,7 +4,7 @@ namespace Discord | |||
| { | |||
| internal static class Preconditions | |||
| { | |||
| //Objects | |||
| #region Objects | |||
| /// <exception cref="ArgumentNullException"><paramref name="obj"/> must not be <see langword="null"/>.</exception> | |||
| public static void NotNull<T>(T obj, string name, string msg = null) where T : class { if (obj == null) throw CreateNotNullException(name, msg); } | |||
| /// <exception cref="ArgumentNullException"><paramref name="obj"/> must not be <see langword="null"/>.</exception> | |||
| @@ -15,8 +15,9 @@ namespace Discord | |||
| if (msg == null) return new ArgumentNullException(paramName: name); | |||
| else return new ArgumentNullException(paramName: name, message: msg); | |||
| } | |||
| #endregion | |||
| //Strings | |||
| #region Strings | |||
| /// <exception cref="ArgumentException"><paramref name="obj"/> cannot be blank.</exception> | |||
| public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw CreateNotEmptyException(name, msg); } | |||
| /// <exception cref="ArgumentException"><paramref name="obj"/> cannot be blank.</exception> | |||
| @@ -58,8 +59,9 @@ namespace Discord | |||
| private static ArgumentException CreateNotEmptyException(string name, string msg) | |||
| => new ArgumentException(message: msg ?? "Argument cannot be blank.", paramName: name); | |||
| #endregion | |||
| //Numerics | |||
| #region Numerics | |||
| /// <exception cref="ArgumentException">Value may not be equal to <paramref name="value"/>.</exception> | |||
| public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
| /// <exception cref="ArgumentException">Value may not be equal to <paramref name="value"/>.</exception> | |||
| @@ -271,8 +273,9 @@ namespace Discord | |||
| private static ArgumentException CreateLessThanException<T>(string name, string msg, T value) | |||
| => new ArgumentException(message: msg ?? $"Value must be less than {value}.", paramName: name); | |||
| #endregion | |||
| // Bulk Delete | |||
| #region Bulk Delete | |||
| /// <exception cref="ArgumentOutOfRangeException">Messages are younger than 2 weeks.</exception> | |||
| public static void YoungerThanTwoWeeks(ulong[] collection, string name) | |||
| { | |||
| @@ -293,5 +296,6 @@ namespace Discord | |||
| throw new ArgumentException(message: "The everyone role cannot be assigned to a user.", paramName: name); | |||
| } | |||
| } | |||
| #endregion | |||
| } | |||
| } | |||
| @@ -12,7 +12,7 @@ namespace Discord.Rest | |||
| { | |||
| internal static class ChannelHelper | |||
| { | |||
| //General | |||
| #region General | |||
| public static async Task DeleteAsync(IChannel channel, BaseDiscordClient client, | |||
| RequestOptions options) | |||
| { | |||
| @@ -107,8 +107,9 @@ namespace Discord.Rest | |||
| return await client.ApiClient.ModifyStageInstanceAsync(channel.Id, apiArgs, options); | |||
| } | |||
| #endregion | |||
| //Invites | |||
| #region Invites | |||
| public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client, | |||
| RequestOptions options) | |||
| { | |||
| @@ -183,8 +184,9 @@ namespace Discord.Rest | |||
| var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options).ConfigureAwait(false); | |||
| return RestInviteMetadata.Create(client, null, channel, model); | |||
| } | |||
| #endregion | |||
| //Messages | |||
| #region Messages | |||
| public static async Task<RestMessage> GetMessageAsync(IMessageChannel channel, BaseDiscordClient client, | |||
| ulong id, RequestOptions options) | |||
| { | |||
| @@ -285,12 +287,12 @@ namespace Discord.Rest | |||
| } | |||
| } | |||
| if(stickers != null) | |||
| if (stickers != null) | |||
| { | |||
| Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); | |||
| } | |||
| var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel(), Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified}; | |||
| var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel(), Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified }; | |||
| var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | |||
| return RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||
| } | |||
| @@ -397,8 +399,9 @@ namespace Discord.Rest | |||
| await client.ApiClient.DeleteMessagesAsync(channel.Id, args, options).ConfigureAwait(false); | |||
| } | |||
| } | |||
| #endregion | |||
| //Permission Overwrites | |||
| #region Permission Overwrites | |||
| public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | |||
| IUser user, OverwritePermissions perms, RequestOptions options) | |||
| { | |||
| @@ -421,8 +424,9 @@ namespace Discord.Rest | |||
| { | |||
| await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id, options).ConfigureAwait(false); | |||
| } | |||
| #endregion | |||
| //Users | |||
| #region Users | |||
| /// <exception cref="InvalidOperationException">Resolving permissions requires the parent guild to be downloaded.</exception> | |||
| public static async Task<RestGuildUser> GetUserAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, | |||
| ulong id, RequestOptions options) | |||
| @@ -467,8 +471,9 @@ namespace Discord.Rest | |||
| count: limit | |||
| ); | |||
| } | |||
| #endregion | |||
| //Typing | |||
| #region Typing | |||
| public static async Task TriggerTypingAsync(IMessageChannel channel, BaseDiscordClient client, | |||
| RequestOptions options = null) | |||
| { | |||
| @@ -477,8 +482,9 @@ namespace Discord.Rest | |||
| public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, | |||
| RequestOptions options) | |||
| => new TypingNotifier(channel, options); | |||
| #endregion | |||
| //Webhooks | |||
| #region Webhooks | |||
| public static async Task<RestWebhook> CreateWebhookAsync(ITextChannel channel, BaseDiscordClient client, string name, Stream avatar, RequestOptions options) | |||
| { | |||
| var args = new CreateWebhookParams { Name = name }; | |||
| @@ -501,7 +507,9 @@ namespace Discord.Rest | |||
| return models.Select(x => RestWebhook.Create(client, channel, x)) | |||
| .ToImmutableArray(); | |||
| } | |||
| // Categories | |||
| #endregion | |||
| #region Categories | |||
| public static async Task<ICategoryChannel> GetCategoryAsync(INestedChannel channel, BaseDiscordClient client, RequestOptions options) | |||
| { | |||
| // if no category id specified, return null | |||
| @@ -515,7 +523,8 @@ namespace Discord.Rest | |||
| public static async Task SyncPermissionsAsync(INestedChannel channel, BaseDiscordClient client, RequestOptions options) | |||
| { | |||
| var category = await GetCategoryAsync(channel, client, options).ConfigureAwait(false); | |||
| if (category == null) throw new InvalidOperationException("This channel does not have a parent channel."); | |||
| if (category == null) | |||
| throw new InvalidOperationException("This channel does not have a parent channel."); | |||
| var apiArgs = new ModifyGuildChannelParams | |||
| { | |||
| @@ -530,5 +539,6 @@ namespace Discord.Rest | |||
| }; | |||
| await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | |||
| } | |||
| #endregion | |||
| } | |||
| } | |||
| @@ -15,6 +15,7 @@ namespace Discord.Rest | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel | |||
| { | |||
| #region RestDMChannel | |||
| /// <summary> | |||
| /// Gets the current logged-in user. | |||
| /// </summary> | |||
| @@ -154,20 +155,24 @@ namespace Discord.Rest | |||
| /// </returns> | |||
| public override string ToString() => $"@{Recipient}"; | |||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | |||
| #endregion | |||
| //IDMChannel | |||
| #region IDMChannel | |||
| /// <inheritdoc /> | |||
| IUser IDMChannel.Recipient => Recipient; | |||
| #endregion | |||
| //IRestPrivateChannel | |||
| #region IRestPrivateChannel | |||
| /// <inheritdoc /> | |||
| IReadOnlyCollection<RestUser> IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | |||
| #endregion | |||
| //IPrivateChannel | |||
| #region IPrivateChannel | |||
| /// <inheritdoc /> | |||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); | |||
| #endregion | |||
| //IMessageChannel | |||
| #region IMessageChannel | |||
| /// <inheritdoc /> | |||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| { | |||
| @@ -212,8 +217,9 @@ namespace Discord.Rest | |||
| /// <inheritdoc /> | |||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers) | |||
| => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, component, stickers).ConfigureAwait(false); | |||
| #endregion | |||
| //IChannel | |||
| #region IChannel | |||
| /// <inheritdoc /> | |||
| string IChannel.Name => $"@{Recipient}"; | |||
| @@ -223,5 +229,6 @@ namespace Discord.Rest | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||
| #endregion | |||
| } | |||
| } | |||
| @@ -16,6 +16,7 @@ namespace Discord.Rest | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel | |||
| { | |||
| #region RestGroupChannel | |||
| private string _iconId; | |||
| private ImmutableDictionary<ulong, RestGroupUser> _users; | |||
| @@ -143,14 +144,17 @@ namespace Discord.Rest | |||
| public override string ToString() => Name; | |||
| private string DebuggerDisplay => $"{Name} ({Id}, Group)"; | |||
| #endregion | |||
| //ISocketPrivateChannel | |||
| #region ISocketPrivateChannel | |||
| IReadOnlyCollection<RestUser> IRestPrivateChannel.Recipients => Recipients; | |||
| #endregion | |||
| //IPrivateChannel | |||
| #region IPrivateChannel | |||
| IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients; | |||
| #endregion | |||
| //IMessageChannel | |||
| #region IMessageChannel | |||
| async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| { | |||
| if (mode == CacheMode.AllowDownload) | |||
| @@ -190,17 +194,20 @@ namespace Discord.Rest | |||
| async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers) | |||
| => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, component, stickers).ConfigureAwait(false); | |||
| #endregion | |||
| //IAudioChannel | |||
| #region IAudioChannel | |||
| /// <inheritdoc /> | |||
| /// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception> | |||
| Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | |||
| Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | |||
| #endregion | |||
| //IChannel | |||
| #region IChannel | |||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(id)); | |||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); | |||
| #endregion | |||
| } | |||
| } | |||
| @@ -9,6 +9,7 @@ namespace Discord.Rest | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class RestBan : IBan | |||
| { | |||
| #region RestBan | |||
| /// <summary> | |||
| /// Gets the banned user. | |||
| /// </summary> | |||
| @@ -37,9 +38,11 @@ namespace Discord.Rest | |||
| /// </returns> | |||
| public override string ToString() => User.ToString(); | |||
| private string DebuggerDisplay => $"{User}: {Reason}"; | |||
| #endregion | |||
| //IBan | |||
| #region IBan | |||
| /// <inheritdoc /> | |||
| IUser IBan.User => User; | |||
| #endregion | |||
| } | |||
| } | |||
| @@ -340,9 +340,10 @@ namespace Discord.Rest | |||
| var embeds = args.Embeds; | |||
| bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content); | |||
| bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || message.Embeds.Any(); | |||
| bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0 || message.Embeds.Any(); | |||
| bool hasComponents = args.Components.IsSpecified && args.Components.Value != null; | |||
| if (!hasText && !hasEmbeds) | |||
| if (!hasComponents && !hasText && !hasEmbeds) | |||
| Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | |||
| var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List<API.Embed>() : null; | |||
| @@ -383,9 +384,10 @@ namespace Discord.Rest | |||
| var embeds = args.Embeds; | |||
| bool hasText = !string.IsNullOrEmpty(args.Content.GetValueOrDefault()); | |||
| bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0); | |||
| bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0; | |||
| bool hasComponents = args.Components.IsSpecified && args.Components.Value != null; | |||
| if (!hasText && !hasEmbeds) | |||
| if (!hasComponents && !hasText && !hasEmbeds) | |||
| Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | |||
| var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List<API.Embed>() : null; | |||
| @@ -38,7 +38,7 @@ namespace Discord.Rest | |||
| var embeds = args.Embeds; | |||
| bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content); | |||
| bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || msg.Embeds.Any(); | |||
| bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0 || msg.Embeds.Any(); | |||
| if (!hasText && !hasEmbeds) | |||
| Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | |||
| @@ -101,9 +101,10 @@ namespace Discord.Rest | |||
| var embeds = args.Embeds; | |||
| bool hasText = args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value); | |||
| bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0); | |||
| bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0; | |||
| bool hasComponents = args.Components.IsSpecified && args.Components.Value != null; | |||
| if (!hasText && !hasEmbeds) | |||
| if (!hasComponents && !hasText && !hasEmbeds) | |||
| Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | |||
| if (args.AllowedMentions.IsSpecified) | |||
| @@ -10,6 +10,7 @@ namespace Discord.Rest | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class RestGroupUser : RestUser, IGroupUser | |||
| { | |||
| #region RestGroupUser | |||
| internal RestGroupUser(BaseDiscordClient discord, ulong id) | |||
| : base(discord, id) | |||
| { | |||
| @@ -20,8 +21,9 @@ namespace Discord.Rest | |||
| entity.Update(model); | |||
| return entity; | |||
| } | |||
| #endregion | |||
| //IVoiceState | |||
| #region IVoiceState | |||
| /// <inheritdoc /> | |||
| bool IVoiceState.IsDeafened => false; | |||
| /// <inheritdoc /> | |||
| @@ -40,5 +42,6 @@ namespace Discord.Rest | |||
| bool IVoiceState.IsStreaming => false; | |||
| /// <inheritdoc /> | |||
| DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | |||
| #endregion | |||
| } | |||
| } | |||
| @@ -24,6 +24,7 @@ namespace Discord.WebSocket | |||
| /// </summary> | |||
| public partial class DiscordSocketClient : BaseSocketClient, IDiscordClient | |||
| { | |||
| #region DiscordSocketClient | |||
| private readonly ConcurrentQueue<ulong> _largeGuilds; | |||
| internal readonly JsonSerializer _serializer; | |||
| private readonly DiscordShardedClient _shardedClient; | |||
| @@ -62,6 +63,7 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public override IActivity Activity { get => _activity.GetValueOrDefault(); protected set => _activity = Optional.Create(value); } | |||
| private Optional<IActivity> _activity; | |||
| #endregion | |||
| //From DiscordSocketConfig | |||
| internal int TotalShards { get; private set; } | |||
| @@ -436,7 +438,7 @@ namespace Discord.WebSocket | |||
| var entity = State.GetOrAddCommand(model.Id, (id) => SocketApplicationCommand.Create(this, model)); | |||
| // update it incase it was cached | |||
| //Update it incase it was cached | |||
| entity.Update(model); | |||
| return entity; | |||
| @@ -448,7 +450,7 @@ namespace Discord.WebSocket | |||
| var entities = models.Select(x => SocketApplicationCommand.Create(this, x)); | |||
| // purge our previous commands | |||
| //Purge our previous commands | |||
| State.PurgeCommands(x => x.IsGlobalCommand); | |||
| foreach(var entity in entities) | |||
| @@ -513,7 +515,7 @@ namespace Discord.WebSocket | |||
| { | |||
| var guild = State.GetGuild(model.GuildId.Value); | |||
| // since the sticker can be from another guild, check if we are in the guild or its in the cache | |||
| //Since the sticker can be from another guild, check if we are in the guild or its in the cache | |||
| if (guild != null) | |||
| sticker = guild.AddOrUpdateSticker(model); | |||
| else | |||
| @@ -678,7 +680,7 @@ namespace Discord.WebSocket | |||
| return null; | |||
| GameModel game = null; | |||
| // Discord only accepts rich presence over RPC, don't even bother building a payload | |||
| //Discord only accepts rich presence over RPC, don't even bother building a payload | |||
| if (activity.GetValueOrDefault() != null) | |||
| { | |||
| @@ -700,6 +702,7 @@ namespace Discord.WebSocket | |||
| game); | |||
| } | |||
| #region ProcessMessageAsync | |||
| private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) | |||
| { | |||
| if (seq != null) | |||
| @@ -772,7 +775,7 @@ namespace Discord.WebSocket | |||
| case GatewayOpCode.Dispatch: | |||
| switch (type) | |||
| { | |||
| //Connection | |||
| #region Connection | |||
| case "READY": | |||
| { | |||
| try | |||
| @@ -849,8 +852,9 @@ namespace Discord.WebSocket | |||
| await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); | |||
| } | |||
| break; | |||
| #endregion | |||
| //Guilds | |||
| #region Guilds | |||
| case "GUILD_CREATE": | |||
| { | |||
| var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||
| @@ -1000,8 +1004,9 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| #endregion | |||
| //Channels | |||
| #region Channels | |||
| case "CHANNEL_CREATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | |||
| @@ -1103,8 +1108,9 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| #endregion | |||
| //Members | |||
| #region Members | |||
| case "GUILD_MEMBER_ADD": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); | |||
| @@ -1275,8 +1281,9 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| #endregion | |||
| //Roles | |||
| #region Roles | |||
| case "GUILD_ROLE_CREATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); | |||
| @@ -1368,8 +1375,9 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| #endregion | |||
| //Bans | |||
| #region Bans | |||
| case "GUILD_BAN_ADD": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); | |||
| @@ -1422,8 +1430,9 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| #endregion | |||
| //Messages | |||
| #region Messages | |||
| case "MESSAGE_CREATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | |||
| @@ -1754,8 +1763,9 @@ namespace Discord.WebSocket | |||
| await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, cacheableChannel).ConfigureAwait(false); | |||
| } | |||
| break; | |||
| #endregion | |||
| //Statuses | |||
| #region Statuses | |||
| case "PRESENCE_UPDATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); | |||
| @@ -1843,8 +1853,9 @@ namespace Discord.WebSocket | |||
| await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), cacheableUser, cacheableChannel).ConfigureAwait(false); | |||
| } | |||
| break; | |||
| #endregion | |||
| //Users | |||
| #region Users | |||
| case "USER_UPDATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); | |||
| @@ -1863,8 +1874,9 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| #endregion | |||
| //Voice | |||
| #region Voice | |||
| case "VOICE_STATE_UPDATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | |||
| @@ -1901,7 +1913,7 @@ namespace Discord.WebSocket | |||
| after = SocketVoiceState.Create(null, data); | |||
| } | |||
| // per g250k, this should always be sent, but apparently not always | |||
| //Per g250k, this should always be sent, but apparently not always | |||
| user = guild.GetUser(data.UserId) | |||
| ?? (data.Member.IsSpecified ? guild.AddOrUpdateUser(data.Member.Value) : null); | |||
| if (user == null) | |||
| @@ -1993,8 +2005,9 @@ namespace Discord.WebSocket | |||
| } | |||
| break; | |||
| #endregion | |||
| //Invites | |||
| #region Invites | |||
| case "INVITE_CREATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false); | |||
| @@ -2051,8 +2064,9 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| #endregion | |||
| // Interactions | |||
| #region Interactions | |||
| case "INTERACTION_CREATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); | |||
| @@ -2189,8 +2203,9 @@ namespace Discord.WebSocket | |||
| await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false); | |||
| } | |||
| break; | |||
| #endregion | |||
| // Threads | |||
| #region Threads | |||
| case "THREAD_CREATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_CREATE)").ConfigureAwait(false); | |||
| @@ -2251,7 +2266,7 @@ namespace Discord.WebSocket | |||
| } | |||
| else | |||
| { | |||
| // Thread is updated but was not cached, likely meaning the thread was unarchived. | |||
| //Thread is updated but was not cached, likely meaning the thread was unarchived. | |||
| threadChannel = (SocketThreadChannel)guild.AddChannel(State, data); | |||
| if (data.ThreadMember.IsSpecified) | |||
| threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); | |||
| @@ -2507,8 +2522,9 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| #endregion | |||
| //Ignored (User only) | |||
| #region Ignored (User only) | |||
| case "CHANNEL_PINS_ACK": | |||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | |||
| break; | |||
| @@ -2530,11 +2546,13 @@ namespace Discord.WebSocket | |||
| case "WEBHOOKS_UPDATE": | |||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); | |||
| break; | |||
| #endregion | |||
| //Others | |||
| #region Others | |||
| default: | |||
| await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false); | |||
| break; | |||
| #endregion | |||
| } | |||
| break; | |||
| default: | |||
| @@ -2548,6 +2566,7 @@ namespace Discord.WebSocket | |||
| Console.WriteLine(ex); | |||
| } | |||
| } | |||
| #endregion | |||
| private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken) | |||
| { | |||
| @@ -150,7 +150,7 @@ namespace Discord.WebSocket | |||
| var embeds = args.Embeds; | |||
| bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(Message.Content); | |||
| bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || Message.Embeds.Any(); | |||
| bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0 || Message.Embeds.Any(); | |||
| if (!hasText && !hasEmbeds) | |||
| Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | |||
| @@ -9,7 +9,7 @@ namespace Discord.WebSocket | |||
| { | |||
| public static IActivity ToEntity(this API.Game model) | |||
| { | |||
| // Custom Status Game | |||
| #region Custom Status Game | |||
| if (model.Id.IsSpecified && model.Id.Value == "custom") | |||
| { | |||
| return new CustomStatusGame() | |||
| @@ -21,13 +21,14 @@ namespace Discord.WebSocket | |||
| CreatedAt = DateTimeOffset.FromUnixTimeMilliseconds(model.CreatedAt.Value), | |||
| }; | |||
| } | |||
| #endregion | |||
| // Spotify Game | |||
| #region Spotify Game | |||
| if (model.SyncId.IsSpecified) | |||
| { | |||
| var assets = model.Assets.GetValueOrDefault()?.ToEntity(); | |||
| string albumText = assets?[1]?.Text; | |||
| string albumArtId = assets?[1]?.ImageId?.Replace("spotify:",""); | |||
| string albumArtId = assets?[1]?.ImageId?.Replace("spotify:", ""); | |||
| var timestamps = model.Timestamps.IsSpecified ? model.Timestamps.Value.ToEntity() : null; | |||
| return new SpotifyGame | |||
| { | |||
| @@ -37,7 +38,7 @@ namespace Discord.WebSocket | |||
| TrackUrl = CDN.GetSpotifyDirectUrl(model.SyncId.Value), | |||
| AlbumTitle = albumText, | |||
| TrackTitle = model.Details.GetValueOrDefault(), | |||
| Artists = model.State.GetValueOrDefault()?.Split(';').Select(x=>x?.Trim()).ToImmutableArray(), | |||
| Artists = model.State.GetValueOrDefault()?.Split(';').Select(x => x?.Trim()).ToImmutableArray(), | |||
| StartedAt = timestamps?.Start, | |||
| EndsAt = timestamps?.End, | |||
| Duration = timestamps?.End - timestamps?.Start, | |||
| @@ -46,8 +47,9 @@ namespace Discord.WebSocket | |||
| Flags = model.Flags.GetValueOrDefault(), | |||
| }; | |||
| } | |||
| #endregion | |||
| // Rich Game | |||
| #region Rich Game | |||
| if (model.ApplicationId.IsSpecified) | |||
| { | |||
| ulong appId = model.ApplicationId.Value; | |||
| @@ -66,7 +68,9 @@ namespace Discord.WebSocket | |||
| Flags = model.Flags.GetValueOrDefault() | |||
| }; | |||
| } | |||
| // Stream Game | |||
| #endregion | |||
| #region Stream Game | |||
| if (model.StreamUrl.IsSpecified) | |||
| { | |||
| return new StreamingGame( | |||
| @@ -77,10 +81,13 @@ namespace Discord.WebSocket | |||
| Details = model.Details.GetValueOrDefault() | |||
| }; | |||
| } | |||
| // Normal Game | |||
| #endregion | |||
| #region Normal Game | |||
| return new Game(model.Name, model.Type.GetValueOrDefault() ?? ActivityType.Playing, | |||
| model.Flags.IsSpecified ? model.Flags.Value : ActivityProperties.None, | |||
| model.Details.GetValueOrDefault()); | |||
| #endregion | |||
| } | |||
| // (Small, Large) | |||