@@ -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) | |||