| @@ -28,7 +28,7 @@ public async Task SendRichEmbedAsync() | |||
| var embed = new EmbedBuilder | |||
| { | |||
| // Embed property can be set within object initializer | |||
| Title = "Hello world!" | |||
| Title = "Hello world!", | |||
| Description = "I am a description set by initializer." | |||
| }; | |||
| // Or with methods | |||
| @@ -134,7 +134,7 @@ If, for whatever reason, you have two commands which are ambiguous to | |||
| each other, you may use the @Discord.Commands.PriorityAttribute to | |||
| specify which should be tested before the other. | |||
| The `Priority` attributes are sorted in ascending order; the higher | |||
| The `Priority` attributes are sorted in descending order; the higher | |||
| priority will be called first. | |||
| ### Command Context | |||
| @@ -16,7 +16,7 @@ namespace Discord | |||
| /// <see href="https://discord.com/developers/docs/reference#api-versioning">Discord API documentation</see> | |||
| /// .</para> | |||
| /// </returns> | |||
| public const int APIVersion = 6; | |||
| public const int APIVersion = 9; | |||
| /// <summary> | |||
| /// Returns the Voice API version Discord.Net uses. | |||
| /// </summary> | |||
| @@ -43,7 +43,7 @@ namespace Discord | |||
| /// <returns> | |||
| /// The user agent used in each Discord.Net request. | |||
| /// </returns> | |||
| public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; | |||
| public static string UserAgent { get; } = $"DiscordBot (https://github.com/discord-net/Discord.Net, v{Version})"; | |||
| /// <summary> | |||
| /// Returns the base Discord API URL. | |||
| /// </summary> | |||
| @@ -141,18 +141,6 @@ namespace Discord | |||
| /// </remarks> | |||
| internal bool DisplayInitialLog { get; set; } = true; | |||
| /// <summary> | |||
| /// Gets or sets the level of precision of the rate limit reset response. | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// If set to <see cref="RateLimitPrecision.Second"/>, this value will be rounded up to the | |||
| /// nearest second. | |||
| /// </remarks> | |||
| /// <returns> | |||
| /// The currently set <see cref="RateLimitPrecision"/>. | |||
| /// </returns> | |||
| public RateLimitPrecision RateLimitPrecision { get; set; } = RateLimitPrecision.Millisecond; | |||
| /// <summary> | |||
| /// Gets or sets whether or not rate-limits should use the system clock. | |||
| /// </summary> | |||
| @@ -8,10 +8,10 @@ namespace Discord | |||
| /// <summary> | |||
| /// The target of the permission is a role. | |||
| /// </summary> | |||
| Role, | |||
| Role = 0, | |||
| /// <summary> | |||
| /// The target of the permission is a user. | |||
| /// </summary> | |||
| User | |||
| User = 1, | |||
| } | |||
| } | |||
| @@ -36,18 +36,6 @@ namespace Discord | |||
| /// </returns> | |||
| Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Modifies the suppression of this message. | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// This method modifies whether or not embeds in this message are suppressed (hidden). | |||
| /// </remarks> | |||
| /// <param name="suppressEmbeds">Whether or not embeds in this message should be suppressed.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| /// <returns> | |||
| /// A task that represents the asynchronous modification operation. | |||
| /// </returns> | |||
| Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Adds this message to its channel's pinned messages. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| @@ -60,9 +60,6 @@ namespace Discord | |||
| /// <summary> | |||
| /// The message is an inline reply. | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// Only available in API v8. | |||
| /// </remarks> | |||
| Reply = 19, | |||
| } | |||
| } | |||
| @@ -87,6 +87,9 @@ namespace Discord | |||
| /// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary> | |||
| public GuildPermissions(ulong rawValue) { RawValue = rawValue; } | |||
| /// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value after converting to ulong. </summary> | |||
| public GuildPermissions(string rawValue) { RawValue = ulong.Parse(rawValue); } | |||
| private GuildPermissions(ulong initialValue, | |||
| bool? createInstantInvite = null, | |||
| bool? kickMembers = null, | |||
| @@ -90,6 +90,13 @@ namespace Discord | |||
| DenyValue = denyValue; | |||
| } | |||
| /// <summary> Creates a new OverwritePermissions with the provided allow and deny packed values after converting to ulong. </summary> | |||
| public OverwritePermissions(string allowValue, string denyValue) | |||
| { | |||
| AllowValue = ulong.Parse(allowValue); | |||
| DenyValue = ulong.Parse(denyValue); | |||
| } | |||
| private OverwritePermissions(ulong allowValue, ulong denyValue, | |||
| PermValue? createInstantInvite = null, | |||
| PermValue? manageChannel = null, | |||
| @@ -7,10 +7,6 @@ namespace Discord | |||
| /// </summary> | |||
| public interface IPresence | |||
| { | |||
| /// <summary> | |||
| /// Gets the activity this user is currently doing. | |||
| /// </summary> | |||
| IActivity Activity { get; } | |||
| /// <summary> | |||
| /// Gets the current status of this user. | |||
| /// </summary> | |||
| @@ -87,7 +87,7 @@ namespace Discord | |||
| UserProperties? PublicFlags { get; } | |||
| /// <summary> | |||
| /// Gets the direct message channel of this user, or create one if it does not already exist. | |||
| /// Creates the direct message channel of this user. | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// This method is used to obtain or create a channel used to send a direct message. | |||
| @@ -102,7 +102,7 @@ namespace Discord | |||
| /// <example> | |||
| /// <para>The following example attempts to send a direct message to the target user and logs the incident should | |||
| /// it fail.</para> | |||
| /// <code region="GetOrCreateDMChannelAsync" language="cs" | |||
| /// <code region="CreateDMChannelAsync" language="cs" | |||
| /// source="../../../Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs"/> | |||
| /// </example> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| @@ -110,6 +110,6 @@ namespace Discord | |||
| /// A task that represents the asynchronous operation for getting or creating a DM channel. The task result | |||
| /// contains the DM channel associated with this user. | |||
| /// </returns> | |||
| Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null); | |||
| Task<IDMChannel> CreateDMChannelAsync(RequestOptions options = null); | |||
| } | |||
| } | |||
| @@ -42,7 +42,7 @@ namespace Discord | |||
| RequestOptions options = null, | |||
| AllowedMentions allowedMentions = null) | |||
| { | |||
| return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||
| return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||
| } | |||
| /// <summary> | |||
| @@ -94,7 +94,7 @@ namespace Discord | |||
| RequestOptions options = null | |||
| ) | |||
| { | |||
| return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||
| return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||
| } | |||
| /// <summary> | |||
| @@ -149,7 +149,7 @@ namespace Discord | |||
| Embed embed = null, | |||
| RequestOptions options = null) | |||
| { | |||
| return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||
| return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||
| } | |||
| /// <summary> | |||
| @@ -39,5 +39,16 @@ namespace Discord | |||
| DirectMessageReactions = 1 << 13, | |||
| /// <summary> This intent includes TYPING_START </summary> | |||
| DirectMessageTyping = 1 << 14, | |||
| /// <summary> | |||
| /// This intent includes all but <see cref="GuildMembers"/> and <see cref="GuildMembers"/> | |||
| /// that are privileged must be enabled for the application. | |||
| /// </summary> | |||
| AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites | | |||
| GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages | | |||
| DirectMessageReactions | DirectMessageTyping, | |||
| /// <summary> | |||
| /// This intent includes all of them, including privileged ones. | |||
| /// </summary> | |||
| All = AllUnprivileged | GuildMembers | GuildPresences | |||
| } | |||
| } | |||
| @@ -1,18 +0,0 @@ | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Specifies the level of precision to request in the rate limit | |||
| /// response header. | |||
| /// </summary> | |||
| public enum RateLimitPrecision | |||
| { | |||
| /// <summary> | |||
| /// Specifies precision rounded up to the nearest whole second | |||
| /// </summary> | |||
| Second, | |||
| /// <summary> | |||
| /// Specifies precision rounded to the nearest millisecond. | |||
| /// </summary> | |||
| Millisecond | |||
| } | |||
| } | |||
| @@ -18,11 +18,11 @@ namespace Discord.Net.Examples.Core.Entities.Users | |||
| #endregion | |||
| #region GetOrCreateDMChannelAsync | |||
| #region CreateDMChannelAsync | |||
| public async Task MessageUserAsync(IUser user) | |||
| { | |||
| var channel = await user.GetOrCreateDMChannelAsync(); | |||
| var channel = await user.CreateDMChannelAsync(); | |||
| try | |||
| { | |||
| await channel.SendMessageAsync("Awesome stuff!"); | |||
| @@ -10,8 +10,8 @@ namespace Discord.API | |||
| [JsonProperty("type")] | |||
| public PermissionTarget TargetType { get; set; } | |||
| [JsonProperty("deny"), Int53] | |||
| public ulong Deny { get; set; } | |||
| public string Deny { get; set; } | |||
| [JsonProperty("allow"), Int53] | |||
| public ulong Allow { get; set; } | |||
| public string Allow { get; set; } | |||
| } | |||
| } | |||
| @@ -13,8 +13,6 @@ namespace Discord.API | |||
| public Optional<ulong> GuildId { get; set; } | |||
| [JsonProperty("status")] | |||
| public UserStatus Status { get; set; } | |||
| [JsonProperty("game")] | |||
| public Game Game { get; set; } | |||
| [JsonProperty("roles")] | |||
| public Optional<ulong[]> Roles { get; set; } | |||
| @@ -18,7 +18,7 @@ namespace Discord.API | |||
| [JsonProperty("position")] | |||
| public int Position { get; set; } | |||
| [JsonProperty("permissions"), Int53] | |||
| public ulong Permissions { get; set; } | |||
| public string Permissions { get; set; } | |||
| [JsonProperty("managed")] | |||
| public bool Managed { get; set; } | |||
| [JsonProperty("tags")] | |||
| @@ -1,4 +1,4 @@ | |||
| #pragma warning disable CS1591 | |||
| #pragma warning disable CS1591 | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API | |||
| @@ -14,6 +14,6 @@ namespace Discord.API | |||
| [JsonProperty("owner")] | |||
| public bool Owner { get; set; } | |||
| [JsonProperty("permissions"), Int53] | |||
| public ulong Permissions { get; set; } | |||
| public string Permissions { get; set; } | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| #pragma warning disable CS1591 | |||
| #pragma warning disable CS1591 | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API.Rest | |||
| @@ -7,13 +7,13 @@ namespace Discord.API.Rest | |||
| internal class ModifyChannelPermissionsParams | |||
| { | |||
| [JsonProperty("type")] | |||
| public string Type { get; } | |||
| public int Type { get; } | |||
| [JsonProperty("allow")] | |||
| public ulong Allow { get; } | |||
| public string Allow { get; } | |||
| [JsonProperty("deny")] | |||
| public ulong Deny { get; } | |||
| public string Deny { get; } | |||
| public ModifyChannelPermissionsParams(string type, ulong allow, ulong deny) | |||
| public ModifyChannelPermissionsParams(int type, string allow, string deny) | |||
| { | |||
| Type = type; | |||
| Allow = allow; | |||
| @@ -1,4 +1,4 @@ | |||
| #pragma warning disable CS1591 | |||
| #pragma warning disable CS1591 | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API.Rest | |||
| @@ -9,7 +9,7 @@ namespace Discord.API.Rest | |||
| [JsonProperty("name")] | |||
| public Optional<string> Name { get; set; } | |||
| [JsonProperty("permissions")] | |||
| public Optional<ulong> Permissions { get; set; } | |||
| public Optional<string> Permissions { get; set; } | |||
| [JsonProperty("color")] | |||
| public Optional<uint> Color { get; set; } | |||
| [JsonProperty("hoist")] | |||
| @@ -1,11 +0,0 @@ | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API.Rest | |||
| { | |||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||
| internal class SuppressEmbedParams | |||
| { | |||
| [JsonProperty("suppress")] | |||
| public bool Suppressed { get; set; } | |||
| } | |||
| } | |||
| @@ -45,20 +45,18 @@ namespace Discord.API | |||
| internal string AuthToken { get; private set; } | |||
| internal IRestClient RestClient { get; private set; } | |||
| internal ulong? CurrentUserId { get; set; } | |||
| public RateLimitPrecision RateLimitPrecision { get; private set; } | |||
| internal bool UseSystemClock { get; set; } | |||
| internal JsonSerializer Serializer => _serializer; | |||
| /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | |||
| public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | |||
| JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true) | |||
| JsonSerializer serializer = null, bool useSystemClock = true) | |||
| { | |||
| _restClientProvider = restClientProvider; | |||
| UserAgent = userAgent; | |||
| DefaultRetryMode = defaultRetryMode; | |||
| _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||
| RateLimitPrecision = rateLimitPrecision; | |||
| UseSystemClock = useSystemClock; | |||
| RequestQueue = new RequestQueue(); | |||
| @@ -75,7 +73,6 @@ namespace Discord.API | |||
| RestClient.SetHeader("accept", "*/*"); | |||
| RestClient.SetHeader("user-agent", UserAgent); | |||
| RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); | |||
| RestClient.SetHeader("X-RateLimit-Precision", RateLimitPrecision.ToString().ToLower()); | |||
| } | |||
| /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | |||
| internal static string GetPrefixedToken(TokenType tokenType, string token) | |||
| @@ -645,16 +642,6 @@ namespace Discord.API | |||
| return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task SuppressEmbedAsync(ulong channelId, ulong messageId, Rest.SuppressEmbedParams args, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||
| options = RequestOptions.CreateOrClone(options); | |||
| var ids = new BucketIds(channelId: channelId); | |||
| await SendJsonAsync("POST", () => $"channels/{channelId}/messages/{messageId}/suppress-embeds", args, ids, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
| @@ -936,7 +923,7 @@ namespace Discord.API | |||
| var ids = new BucketIds(guildId: guildId); | |||
| string reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={Uri.EscapeDataString(args.Reason)}"; | |||
| await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false); | |||
| await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete_message_days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false); | |||
| } | |||
| /// <exception cref="ArgumentException"><paramref name="guildId"/> and <paramref name="userId"/> must not be equal to zero.</exception> | |||
| public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null) | |||
| @@ -29,10 +29,7 @@ namespace Discord.Rest | |||
| internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } | |||
| private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | |||
| => new API.DiscordRestApiClient(config.RestClientProvider, | |||
| DiscordRestConfig.UserAgent, | |||
| rateLimitPrecision: config.RateLimitPrecision, | |||
| useSystemClock: config.UseSystemClock); | |||
| => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, useSystemClock: config.UseSystemClock); | |||
| internal override void Dispose(bool disposing) | |||
| { | |||
| @@ -33,8 +33,8 @@ namespace Discord.Rest | |||
| { | |||
| TargetId = overwrite.TargetId, | |||
| TargetType = overwrite.TargetType, | |||
| Allow = overwrite.Permissions.AllowValue, | |||
| Deny = overwrite.Permissions.DenyValue | |||
| Allow = overwrite.Permissions.AllowValue.ToString(), | |||
| Deny = overwrite.Permissions.DenyValue.ToString() | |||
| }).ToArray() | |||
| : Optional.Create<API.Overwrite[]>(), | |||
| }; | |||
| @@ -59,8 +59,8 @@ namespace Discord.Rest | |||
| { | |||
| TargetId = overwrite.TargetId, | |||
| TargetType = overwrite.TargetType, | |||
| Allow = overwrite.Permissions.AllowValue, | |||
| Deny = overwrite.Permissions.DenyValue | |||
| Allow = overwrite.Permissions.AllowValue.ToString(), | |||
| Deny = overwrite.Permissions.DenyValue.ToString() | |||
| }).ToArray() | |||
| : Optional.Create<API.Overwrite[]>(), | |||
| }; | |||
| @@ -84,8 +84,8 @@ namespace Discord.Rest | |||
| { | |||
| TargetId = overwrite.TargetId, | |||
| TargetType = overwrite.TargetType, | |||
| Allow = overwrite.Permissions.AllowValue, | |||
| Deny = overwrite.Permissions.DenyValue | |||
| Allow = overwrite.Permissions.AllowValue.ToString(), | |||
| Deny = overwrite.Permissions.DenyValue.ToString() | |||
| }).ToArray() | |||
| : Optional.Create<API.Overwrite[]>(), | |||
| }; | |||
| @@ -328,13 +328,13 @@ namespace Discord.Rest | |||
| public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | |||
| IUser user, OverwritePermissions perms, RequestOptions options) | |||
| { | |||
| var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); | |||
| var args = new ModifyChannelPermissionsParams((int)PermissionTarget.User, perms.AllowValue.ToString(), perms.DenyValue.ToString()); | |||
| await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args, options).ConfigureAwait(false); | |||
| } | |||
| public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | |||
| IRole role, OverwritePermissions perms, RequestOptions options) | |||
| { | |||
| var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); | |||
| var args = new ModifyChannelPermissionsParams((int)PermissionTarget.Role, perms.AllowValue.ToString(), perms.DenyValue.ToString()); | |||
| await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args, options).ConfigureAwait(false); | |||
| } | |||
| public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | |||
| @@ -450,8 +450,8 @@ namespace Discord.Rest | |||
| { | |||
| TargetId = overwrite.TargetId, | |||
| TargetType = overwrite.TargetType, | |||
| Allow = overwrite.Permissions.AllowValue, | |||
| Deny = overwrite.Permissions.DenyValue | |||
| Allow = overwrite.Permissions.AllowValue.ToString(), | |||
| Deny = overwrite.Permissions.DenyValue.ToString() | |||
| }).ToArray() | |||
| }; | |||
| await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | |||
| @@ -184,8 +184,8 @@ namespace Discord.Rest | |||
| { | |||
| TargetId = overwrite.TargetId, | |||
| TargetType = overwrite.TargetType, | |||
| Allow = overwrite.Permissions.AllowValue, | |||
| Deny = overwrite.Permissions.DenyValue | |||
| Allow = overwrite.Permissions.AllowValue.ToString(), | |||
| Deny = overwrite.Permissions.DenyValue.ToString() | |||
| }).ToArray() | |||
| : Optional.Create<API.Overwrite[]>(), | |||
| }; | |||
| @@ -212,8 +212,8 @@ namespace Discord.Rest | |||
| { | |||
| TargetId = overwrite.TargetId, | |||
| TargetType = overwrite.TargetType, | |||
| Allow = overwrite.Permissions.AllowValue, | |||
| Deny = overwrite.Permissions.DenyValue | |||
| Allow = overwrite.Permissions.AllowValue.ToString(), | |||
| Deny = overwrite.Permissions.DenyValue.ToString() | |||
| }).ToArray() | |||
| : Optional.Create<API.Overwrite[]>(), | |||
| }; | |||
| @@ -237,8 +237,8 @@ namespace Discord.Rest | |||
| { | |||
| TargetId = overwrite.TargetId, | |||
| TargetType = overwrite.TargetType, | |||
| Allow = overwrite.Permissions.AllowValue, | |||
| Deny = overwrite.Permissions.DenyValue | |||
| Allow = overwrite.Permissions.AllowValue.ToString(), | |||
| Deny = overwrite.Permissions.DenyValue.ToString() | |||
| }).ToArray() | |||
| : Optional.Create<API.Overwrite[]>(), | |||
| }; | |||
| @@ -299,7 +299,7 @@ namespace Discord.Rest | |||
| Hoist = isHoisted, | |||
| Mentionable = isMentionable, | |||
| Name = name, | |||
| Permissions = permissions?.RawValue ?? Optional.Create<ulong>() | |||
| Permissions = permissions?.RawValue.ToString() ?? Optional.Create<string>() | |||
| }; | |||
| var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, createGuildRoleParams, options).ConfigureAwait(false); | |||
| @@ -122,15 +122,6 @@ namespace Discord.Rest | |||
| await client.ApiClient.DeleteMessageAsync(channelId, msgId, options).ConfigureAwait(false); | |||
| } | |||
| public static async Task SuppressEmbedsAsync(IMessage msg, BaseDiscordClient client, bool suppress, RequestOptions options) | |||
| { | |||
| var apiArgs = new API.Rest.SuppressEmbedParams | |||
| { | |||
| Suppressed = suppress | |||
| }; | |||
| await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | |||
| } | |||
| public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) | |||
| { | |||
| await client.ApiClient.AddReactionAsync(channelId, messageId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); | |||
| @@ -164,9 +164,6 @@ namespace Discord.Rest | |||
| /// <inheritdoc /> | |||
| public Task UnpinAsync(RequestOptions options = null) | |||
| => MessageHelper.UnpinAsync(this, Discord, options); | |||
| /// <inheritdoc /> | |||
| public Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null) | |||
| => MessageHelper.SuppressEmbedsAsync(this, Discord, suppressEmbeds, options); | |||
| public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
| TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
| @@ -1,4 +1,4 @@ | |||
| using System; | |||
| using System; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Role; | |||
| using BulkParams = Discord.API.Rest.ModifyGuildRolesParams; | |||
| @@ -24,7 +24,7 @@ namespace Discord.Rest | |||
| Hoist = args.Hoist, | |||
| Mentionable = args.Mentionable, | |||
| Name = args.Name, | |||
| Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue : Optional.Create<ulong>() | |||
| Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue.ToString() : Optional.Create<string>() | |||
| }; | |||
| var model = await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, apiArgs, options).ConfigureAwait(false); | |||
| @@ -79,13 +79,13 @@ namespace Discord.Rest | |||
| } | |||
| /// <summary> | |||
| /// Returns a direct message channel to this user, or create one if it does not already exist. | |||
| /// Creates a direct message channel to this user. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| /// <returns> | |||
| /// A task that represents the asynchronous get operation. The task result contains a rest DM channel where the user is the recipient. | |||
| /// </returns> | |||
| public Task<RestDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null) | |||
| public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | |||
| => UserHelper.CreateDMChannelAsync(this, Discord, options); | |||
| /// <inheritdoc /> | |||
| @@ -107,7 +107,7 @@ namespace Discord.Rest | |||
| //IUser | |||
| /// <inheritdoc /> | |||
| async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) | |||
| => await GetOrCreateDMChannelAsync(options).ConfigureAwait(false); | |||
| async Task<IDMChannel> IUser.CreateDMChannelAsync(RequestOptions options) | |||
| => await CreateDMChannelAsync(options).ConfigureAwait(false); | |||
| } | |||
| } | |||
| @@ -73,8 +73,6 @@ namespace Discord.Net.Converters | |||
| } | |||
| //Enums | |||
| if (type == typeof(PermissionTarget)) | |||
| return PermissionTargetConverter.Instance; | |||
| if (type == typeof(UserStatus)) | |||
| return UserStatusConverter.Instance; | |||
| if (type == typeof(EmbedType)) | |||
| @@ -1,44 +0,0 @@ | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| namespace Discord.Net.Converters | |||
| { | |||
| internal class PermissionTargetConverter : JsonConverter | |||
| { | |||
| public static readonly PermissionTargetConverter Instance = new PermissionTargetConverter(); | |||
| public override bool CanConvert(Type objectType) => true; | |||
| public override bool CanRead => true; | |||
| public override bool CanWrite => true; | |||
| /// <exception cref="JsonSerializationException">Unknown permission target.</exception> | |||
| public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||
| { | |||
| switch ((string)reader.Value) | |||
| { | |||
| case "member": | |||
| return PermissionTarget.User; | |||
| case "role": | |||
| return PermissionTarget.Role; | |||
| default: | |||
| throw new JsonSerializationException("Unknown permission target."); | |||
| } | |||
| } | |||
| /// <exception cref="JsonSerializationException">Invalid permission target.</exception> | |||
| public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |||
| { | |||
| switch ((PermissionTarget)value) | |||
| { | |||
| case PermissionTarget.User: | |||
| writer.WriteValue("member"); | |||
| break; | |||
| case PermissionTarget.Role: | |||
| writer.WriteValue("role"); | |||
| break; | |||
| default: | |||
| throw new JsonSerializationException("Invalid permission target."); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -17,8 +17,6 @@ namespace Discord.API.Gateway | |||
| public Optional<int[]> ShardingParams { get; set; } | |||
| [JsonProperty("presence")] | |||
| public Optional<StatusUpdateParams> Presence { get; set; } | |||
| [JsonProperty("guild_subscriptions")] | |||
| public Optional<bool> GuildSubscriptions { get; set; } | |||
| [JsonProperty("intents")] | |||
| public Optional<int> Intents { get; set; } | |||
| } | |||
| @@ -70,20 +70,11 @@ namespace Discord.WebSocket | |||
| /// A read-only collection of private channels that the user currently partakes in. | |||
| /// </returns> | |||
| public abstract IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels { get; } | |||
| /// <summary> | |||
| /// Gets a collection of available voice regions. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A read-only collection of voice regions that the user has access to. | |||
| /// </returns> | |||
| [Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] | |||
| public abstract IReadOnlyCollection<RestVoiceRegion> VoiceRegions { get; } | |||
| internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | |||
| : base(config, client) => BaseConfig = config; | |||
| private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
| => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | |||
| rateLimitPrecision: config.RateLimitPrecision, | |||
| useSystemClock: config.UseSystemClock); | |||
| /// <summary> | |||
| @@ -164,16 +155,6 @@ namespace Discord.WebSocket | |||
| /// </returns> | |||
| public abstract SocketGuild GetGuild(ulong id); | |||
| /// <summary> | |||
| /// Gets a voice region. | |||
| /// </summary> | |||
| /// <param name="id">The identifier of the voice region (e.g. <c>eu-central</c> ).</param> | |||
| /// <returns> | |||
| /// A REST-based voice region associated with the identifier; <c>null</c> if the voice region is not | |||
| /// found. | |||
| /// </returns> | |||
| [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||
| public abstract RestVoiceRegion GetVoiceRegion(string id); | |||
| /// <summary> | |||
| /// Gets all voice regions. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| @@ -327,10 +308,14 @@ namespace Discord.WebSocket | |||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
| /// <inheritdoc /> | |||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||
| async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| { | |||
| return await GetVoiceRegionAsync(id).ConfigureAwait(false); | |||
| } | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | |||
| async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| { | |||
| return await GetVoiceRegionsAsync().ConfigureAwait(false); | |||
| } | |||
| } | |||
| } | |||
| @@ -36,9 +36,6 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); | |||
| public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | |||
| /// <inheritdoc /> | |||
| [Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] | |||
| public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | |||
| /// <summary> | |||
| /// Provides access to a REST-only client with a shared state from this client. | |||
| @@ -91,8 +88,7 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | |||
| rateLimitPrecision: config.RateLimitPrecision); | |||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | |||
| internal async Task AcquireIdentifyLockAsync(int shardId, CancellationToken token) | |||
| { | |||
| @@ -264,11 +260,6 @@ namespace Discord.WebSocket | |||
| return null; | |||
| } | |||
| /// <inheritdoc /> | |||
| [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||
| public override RestVoiceRegion GetVoiceRegion(string id) | |||
| => _shards[0].GetVoiceRegion(id); | |||
| /// <inheritdoc /> | |||
| public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | |||
| { | |||
| @@ -432,11 +423,15 @@ namespace Discord.WebSocket | |||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | |||
| async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| { | |||
| return await GetVoiceRegionsAsync().ConfigureAwait(false); | |||
| } | |||
| /// <inheritdoc /> | |||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||
| async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| { | |||
| return await GetVoiceRegionAsync(id).ConfigureAwait(false); | |||
| } | |||
| internal override void Dispose(bool disposing) | |||
| { | |||
| @@ -40,9 +40,8 @@ namespace Discord.API | |||
| public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | |||
| string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, | |||
| RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, | |||
| bool useSystemClock = true) | |||
| : base(restClientProvider, userAgent, defaultRetryMode, serializer, rateLimitPrecision, useSystemClock) | |||
| : base(restClientProvider, userAgent, defaultRetryMode, serializer, useSystemClock) | |||
| { | |||
| _gatewayUrl = url; | |||
| if (url != null) | |||
| @@ -216,7 +215,7 @@ namespace Discord.API | |||
| await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | |||
| } | |||
| public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, bool guildSubscriptions = true, GatewayIntents? gatewayIntents = null, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null) | |||
| public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, GatewayIntents gatewayIntents = GatewayIntents.AllUnprivileged, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null) | |||
| { | |||
| options = RequestOptions.CreateOrClone(options); | |||
| var props = new Dictionary<string, string> | |||
| @@ -234,10 +233,7 @@ namespace Discord.API | |||
| options.BucketId = GatewayBucket.Get(GatewayBucketType.Identify).Id; | |||
| if (gatewayIntents.HasValue) | |||
| msg.Intents = (int)gatewayIntents.Value; | |||
| else | |||
| msg.GuildSubscriptions = guildSubscriptions; | |||
| msg.Intents = (int)gatewayIntents; | |||
| if (presence.HasValue) | |||
| { | |||
| @@ -43,8 +43,7 @@ namespace Discord.WebSocket | |||
| private DateTimeOffset? _statusSince; | |||
| private RestApplication _applicationInfo; | |||
| private bool _isDisposed; | |||
| private bool _guildSubscriptions; | |||
| private GatewayIntents? _gatewayIntents; | |||
| private GatewayIntents _gatewayIntents; | |||
| /// <summary> | |||
| /// Provides access to a REST-only client with a shared state from this client. | |||
| @@ -109,9 +108,6 @@ namespace Discord.WebSocket | |||
| /// </returns> | |||
| public IReadOnlyCollection<SocketGroupChannel> GroupChannels | |||
| => State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); | |||
| /// <inheritdoc /> | |||
| [Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] | |||
| public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => GetVoiceRegionsAsync().GetAwaiter().GetResult(); | |||
| /// <summary> | |||
| /// Initializes a new REST/WebSocket-based Discord client. | |||
| @@ -140,7 +136,6 @@ namespace Discord.WebSocket | |||
| State = new ClientState(0, 0); | |||
| Rest = new DiscordSocketRestClient(config, ApiClient); | |||
| _heartbeatTimes = new ConcurrentQueue<long>(); | |||
| _guildSubscriptions = config.GuildSubscriptions; | |||
| _gatewayIntents = config.GatewayIntents; | |||
| _stateLock = new SemaphoreSlim(1, 1); | |||
| @@ -182,8 +177,7 @@ namespace Discord.WebSocket | |||
| _largeGuilds = new ConcurrentQueue<ulong>(); | |||
| } | |||
| private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost, | |||
| rateLimitPrecision: config.RateLimitPrecision); | |||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); | |||
| /// <inheritdoc /> | |||
| internal override void Dispose(bool disposing) | |||
| { | |||
| @@ -243,7 +237,7 @@ namespace Discord.WebSocket | |||
| else | |||
| { | |||
| await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); | |||
| await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
| await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
| } | |||
| } | |||
| finally | |||
| @@ -308,7 +302,7 @@ namespace Discord.WebSocket | |||
| /// <summary> | |||
| /// Clears cached DM channels from the client. | |||
| /// </summary> | |||
| public void PurgeDMChannelCache() => State.PurgeDMChannels(); | |||
| public void PurgeDMChannelCache() => RemoveDMChannels(); | |||
| /// <inheritdoc /> | |||
| public override SocketUser GetUser(ulong id) | |||
| @@ -320,14 +314,11 @@ namespace Discord.WebSocket | |||
| /// Clears cached users from the client. | |||
| /// </summary> | |||
| public void PurgeUserCache() => State.PurgeUsers(); | |||
| internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) | |||
| internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model, bool cache) | |||
| { | |||
| return state.GetOrAddUser(model.Id, x => | |||
| { | |||
| var user = SocketGlobalUser.Create(this, state, model); | |||
| user.GlobalUser.AddRef(); | |||
| return user; | |||
| }); | |||
| if (cache) | |||
| return state.GetOrAddUser(model.Id, x => SocketGlobalUser.Create(this, state, model)); | |||
| return state.GetUser(model.Id) ?? SocketGlobalUser.Create(this, state, model); | |||
| } | |||
| internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, Discord.API.User model) | |||
| { | |||
| @@ -335,18 +326,13 @@ namespace Discord.WebSocket | |||
| { | |||
| var user = SocketGlobalUser.Create(this, state, model); | |||
| user.GlobalUser.AddRef(); | |||
| user.Presence = new SocketPresence(UserStatus.Online, null, null, null); | |||
| user.Presence = new SocketPresence(UserStatus.Online, null, null); | |||
| return user; | |||
| }); | |||
| } | |||
| internal void RemoveUser(ulong id) | |||
| => State.RemoveUser(id); | |||
| /// <inheritdoc /> | |||
| [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||
| public override RestVoiceRegion GetVoiceRegion(string id) | |||
| => GetVoiceRegionAsync(id).GetAwaiter().GetResult(); | |||
| /// <inheritdoc /> | |||
| public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | |||
| { | |||
| @@ -469,7 +455,8 @@ namespace Discord.WebSocket | |||
| { | |||
| if (CurrentUser == null) | |||
| return; | |||
| CurrentUser.Presence = new SocketPresence(Status, Activity, null, null); | |||
| var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | |||
| CurrentUser.Presence = new SocketPresence(Status, null, activities); | |||
| var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null); | |||
| @@ -564,7 +551,7 @@ namespace Discord.WebSocket | |||
| await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false); | |||
| try | |||
| { | |||
| await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
| await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
| } | |||
| finally | |||
| { | |||
| @@ -572,7 +559,7 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| else | |||
| await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
| await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
| } | |||
| break; | |||
| case GatewayOpCode.Reconnect: | |||
| @@ -595,7 +582,8 @@ namespace Discord.WebSocket | |||
| var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | |||
| var currentUser = SocketSelfUser.Create(this, state, data.User); | |||
| currentUser.Presence = new SocketPresence(Status, Activity, null, null); | |||
| var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | |||
| currentUser.Presence = new SocketPresence(Status, null, activities); | |||
| ApiClient.CurrentUserId = currentUser.Id; | |||
| int unavailableGuilds = 0; | |||
| for (int i = 0; i < data.Guilds.Length; i++) | |||
| @@ -1237,56 +1225,63 @@ namespace Discord.WebSocket | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | |||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
| var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
| var guild = (channel as SocketGuildChannel)?.Guild; | |||
| if (guild != null && !guild.IsSynced) | |||
| { | |||
| var guild = (channel as SocketGuildChannel)?.Guild; | |||
| if (guild != null && !guild.IsSynced) | |||
| await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| if (channel == null) | |||
| { | |||
| if (!data.GuildId.IsSpecified) // assume it is a DM | |||
| { | |||
| await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
| return; | |||
| channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); | |||
| } | |||
| SocketUser author; | |||
| if (guild != null) | |||
| else | |||
| { | |||
| if (data.WebhookId.IsSpecified) | |||
| author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); | |||
| else | |||
| author = guild.GetUser(data.Author.Value.Id); | |||
| await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| } | |||
| SocketUser author; | |||
| if (guild != null) | |||
| { | |||
| if (data.WebhookId.IsSpecified) | |||
| author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); | |||
| else | |||
| author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||
| author = guild.GetUser(data.Author.Value.Id); | |||
| } | |||
| else | |||
| author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||
| if (author == null) | |||
| if (author == null) | |||
| { | |||
| if (guild != null) | |||
| { | |||
| if (guild != null) | |||
| if (data.Member.IsSpecified) // member isn't always included, but use it when we can | |||
| { | |||
| if (data.Member.IsSpecified) // member isn't always included, but use it when we can | |||
| { | |||
| data.Member.Value.User = data.Author.Value; | |||
| author = guild.AddOrUpdateUser(data.Member.Value); | |||
| } | |||
| else | |||
| author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data | |||
| data.Member.Value.User = data.Author.Value; | |||
| author = guild.AddOrUpdateUser(data.Member.Value); | |||
| } | |||
| else if (channel is SocketGroupChannel) | |||
| author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value); | |||
| else | |||
| { | |||
| await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data | |||
| } | |||
| else if (channel is SocketGroupChannel groupChannel) | |||
| author = groupChannel.GetOrAddUser(data.Author.Value); | |||
| else | |||
| { | |||
| await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| var msg = SocketMessage.Create(this, State, author, channel, data); | |||
| SocketChannelHelper.AddMessage(channel, this, msg); | |||
| await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); | |||
| } | |||
| else | |||
| { | |||
| await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| var msg = SocketMessage.Create(this, State, author, channel, data); | |||
| SocketChannelHelper.AddMessage(channel, this, msg); | |||
| await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); | |||
| } | |||
| break; | |||
| case "MESSAGE_UPDATE": | |||
| @@ -1938,24 +1933,29 @@ namespace Discord.WebSocket | |||
| { | |||
| var channel = SocketChannel.CreatePrivate(this, state, model); | |||
| state.AddChannel(channel as SocketChannel); | |||
| if (channel is SocketDMChannel dm) | |||
| dm.Recipient.GlobalUser.DMChannel = dm; | |||
| return channel; | |||
| } | |||
| internal SocketDMChannel CreateDMChannel(ulong channelId, API.User model, ClientState state) | |||
| { | |||
| return SocketDMChannel.Create(this, state, channelId, model); | |||
| } | |||
| internal ISocketPrivateChannel RemovePrivateChannel(ulong id) | |||
| { | |||
| var channel = State.RemoveChannel(id) as ISocketPrivateChannel; | |||
| if (channel != null) | |||
| { | |||
| if (channel is SocketDMChannel dmChannel) | |||
| dmChannel.Recipient.GlobalUser.DMChannel = null; | |||
| foreach (var recipient in channel.Recipients) | |||
| recipient.GlobalUser.RemoveRef(this); | |||
| } | |||
| return channel; | |||
| } | |||
| internal void RemoveDMChannels() | |||
| { | |||
| var channels = State.DMChannels; | |||
| State.PurgeDMChannels(); | |||
| foreach (var channel in channels) | |||
| channel.Recipient.GlobalUser.RemoveRef(this); | |||
| } | |||
| private async Task GuildAvailableAsync(SocketGuild guild) | |||
| { | |||
| @@ -127,12 +127,6 @@ namespace Discord.WebSocket | |||
| /// </remarks> | |||
| public bool? ExclusiveBulkDelete { get; set; } = null; | |||
| /// <summary> | |||
| /// Gets or sets enabling dispatching of guild subscription events e.g. presence and typing events. | |||
| /// This is not used if <see cref="GatewayIntents"/> are provided. | |||
| /// </summary> | |||
| public bool GuildSubscriptions { get; set; } = true; | |||
| /// <summary> | |||
| /// Gets or sets the maximum identify concurrency. | |||
| /// </summary> | |||
| @@ -172,14 +166,15 @@ namespace Discord.WebSocket | |||
| private int maxWaitForGuildAvailable = 10000; | |||
| /// <summary> | |||
| /// Gets or sets gateway intents to limit what events are sent from Discord. Allows for more granular control than the <see cref="GuildSubscriptions"/> property. | |||
| /// Gets or sets gateway intents to limit what events are sent from Discord. | |||
| /// The default is <see cref="GatewayIntents.AllUnprivileged"/>. | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// For more information, please see | |||
| /// <see href="https://discord.com/developers/docs/topics/gateway#gateway-intents">GatewayIntents</see> | |||
| /// on the official Discord API documentation. | |||
| /// </remarks> | |||
| public GatewayIntents? GatewayIntents { get; set; } | |||
| public GatewayIntents GatewayIntents { get; set; } = GatewayIntents.AllUnprivileged; | |||
| /// <summary> | |||
| /// Initializes a new instance of the <see cref="DiscordSocketConfig"/> class with the default configuration. | |||
| @@ -16,15 +16,13 @@ namespace Discord.WebSocket | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel | |||
| { | |||
| private readonly MessageCache _messages; | |||
| /// <summary> | |||
| /// Gets the recipient of the channel. | |||
| /// </summary> | |||
| public SocketUser Recipient { get; } | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | |||
| public IReadOnlyCollection<SocketMessage> CachedMessages => ImmutableArray.Create<SocketMessage>(); | |||
| /// <summary> | |||
| /// Gets a collection that is the current logged-in user and the recipient. | |||
| @@ -35,13 +33,10 @@ namespace Discord.WebSocket | |||
| : base(discord, id) | |||
| { | |||
| Recipient = recipient; | |||
| recipient.GlobalUser.AddRef(); | |||
| if (Discord.MessageCacheSize > 0) | |||
| _messages = new MessageCache(Discord); | |||
| } | |||
| internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) | |||
| { | |||
| var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateUser(state, model.Recipients.Value[0])); | |||
| var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateUser(state, model.Recipients.Value[0], false)); | |||
| entity.Update(state, model); | |||
| return entity; | |||
| } | |||
| @@ -49,6 +44,16 @@ namespace Discord.WebSocket | |||
| { | |||
| Recipient.Update(state, model.Recipients.Value[0]); | |||
| } | |||
| internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, ulong channelId, API.User recipient) | |||
| { | |||
| var entity = new SocketDMChannel(discord, channelId, discord.GetOrCreateUser(state, recipient, false)); | |||
| entity.Update(state, recipient); | |||
| return entity; | |||
| } | |||
| internal void Update(ClientState state, API.User recipient) | |||
| { | |||
| Recipient.Update(state, recipient); | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task CloseAsync(RequestOptions options = null) | |||
| @@ -57,7 +62,7 @@ namespace Discord.WebSocket | |||
| //Messages | |||
| /// <inheritdoc /> | |||
| public SocketMessage GetCachedMessage(ulong id) | |||
| => _messages?.Get(id); | |||
| => null; | |||
| /// <summary> | |||
| /// Gets the message associated with the given <paramref name="id"/>. | |||
| /// </summary> | |||
| @@ -68,10 +73,7 @@ namespace Discord.WebSocket | |||
| /// </returns> | |||
| public async Task<IMessage> GetMessageAsync(ulong id, RequestOptions options = null) | |||
| { | |||
| IMessage msg = _messages?.Get(id); | |||
| if (msg == null) | |||
| msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); | |||
| return msg; | |||
| return await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); | |||
| } | |||
| /// <summary> | |||
| @@ -87,7 +89,7 @@ namespace Discord.WebSocket | |||
| /// Paged collection of messages. | |||
| /// </returns> | |||
| public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); | |||
| => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); | |||
| /// <summary> | |||
| /// Gets a collection of messages in this channel. | |||
| /// </summary> | |||
| @@ -103,7 +105,7 @@ namespace Discord.WebSocket | |||
| /// Paged collection of messages. | |||
| /// </returns> | |||
| public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); | |||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); | |||
| /// <summary> | |||
| /// Gets a collection of messages in this channel. | |||
| /// </summary> | |||
| @@ -119,16 +121,16 @@ namespace Discord.WebSocket | |||
| /// Paged collection of messages. | |||
| /// </returns> | |||
| public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); | |||
| => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | |||
| => ImmutableArray.Create<SocketMessage>(); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | |||
| => ImmutableArray.Create<SocketMessage>(); | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | |||
| => ImmutableArray.Create<SocketMessage>(); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
| => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
| @@ -164,9 +166,12 @@ namespace Discord.WebSocket | |||
| => ChannelHelper.EnterTypingState(this, Discord, options); | |||
| internal void AddMessage(SocketMessage msg) | |||
| => _messages?.Add(msg); | |||
| { | |||
| } | |||
| internal SocketMessage RemoveMessage(ulong id) | |||
| => _messages?.Remove(id); | |||
| { | |||
| return null; | |||
| } | |||
| //Users | |||
| /// <summary> | |||
| @@ -222,13 +227,13 @@ namespace Discord.WebSocket | |||
| } | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) | |||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); | |||
| => mode == CacheMode.CacheOnly ? null : GetMessagesAsync(limit, options); | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) | |||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); | |||
| => mode == CacheMode.CacheOnly ? null : GetMessagesAsync(fromMessageId, dir, limit, options); | |||
| /// <inheritdoc /> | |||
| IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) | |||
| => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); | |||
| => mode == CacheMode.CacheOnly ? null : GetMessagesAsync(fromMessage.Id, dir, limit, options); | |||
| /// <inheritdoc /> | |||
| async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | |||
| => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | |||
| @@ -189,9 +189,6 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public Task UnpinAsync(RequestOptions options = null) | |||
| => MessageHelper.UnpinAsync(this, Discord, options); | |||
| /// <inheritdoc /> | |||
| public Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null) | |||
| => MessageHelper.SuppressEmbedsAsync(this, Discord, suppressEmbeds, options); | |||
| public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
| TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
| @@ -12,7 +12,6 @@ namespace Discord.WebSocket | |||
| public override string Username { get; internal set; } | |||
| public override ushort DiscriminatorValue { get; internal set; } | |||
| public override string AvatarId { get; internal set; } | |||
| public SocketDMChannel DMChannel { get; internal set; } | |||
| internal override SocketPresence Presence { get; set; } | |||
| public override bool IsWebhook => false; | |||
| @@ -52,7 +51,6 @@ namespace Discord.WebSocket | |||
| internal void Update(ClientState state, PresenceModel model) | |||
| { | |||
| Presence = SocketPresence.Create(model); | |||
| DMChannel = state.DMChannels.FirstOrDefault(x => x.Recipient.Id == Id); | |||
| } | |||
| private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)"; | |||
| @@ -38,7 +38,7 @@ namespace Discord.WebSocket | |||
| } | |||
| internal static SocketGroupUser Create(SocketGroupChannel channel, ClientState state, Model model) | |||
| { | |||
| var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model)); | |||
| var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model, true)); | |||
| entity.Update(state, model); | |||
| return entity; | |||
| } | |||
| @@ -116,20 +116,20 @@ namespace Discord.WebSocket | |||
| } | |||
| internal static SocketGuildUser Create(SocketGuild guild, ClientState state, UserModel model) | |||
| { | |||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model)); | |||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model, true)); | |||
| entity.Update(state, model); | |||
| entity.UpdateRoles(new ulong[0]); | |||
| return entity; | |||
| } | |||
| internal static SocketGuildUser Create(SocketGuild guild, ClientState state, MemberModel model) | |||
| { | |||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); | |||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User, true)); | |||
| entity.Update(state, model); | |||
| return entity; | |||
| } | |||
| internal static SocketGuildUser Create(SocketGuild guild, ClientState state, PresenceModel model) | |||
| { | |||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); | |||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User, true)); | |||
| entity.Update(state, model, false); | |||
| return entity; | |||
| } | |||
| @@ -2,6 +2,7 @@ using System; | |||
| using System.Collections.Generic; | |||
| using System.Collections.Immutable; | |||
| using System.Diagnostics; | |||
| using System.Linq; | |||
| using Model = Discord.API.Presence; | |||
| namespace Discord.WebSocket | |||
| @@ -15,15 +16,12 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public UserStatus Status { get; } | |||
| /// <inheritdoc /> | |||
| public IActivity Activity { get; } | |||
| /// <inheritdoc /> | |||
| public IImmutableSet<ClientType> ActiveClients { get; } | |||
| /// <inheritdoc /> | |||
| public IImmutableList<IActivity> Activities { get; } | |||
| internal SocketPresence(UserStatus status, IActivity activity, IImmutableSet<ClientType> activeClients, IImmutableList<IActivity> activities) | |||
| internal SocketPresence(UserStatus status, IImmutableSet<ClientType> activeClients, IImmutableList<IActivity> activities) | |||
| { | |||
| Status = status; | |||
| Activity = activity; | |||
| ActiveClients = activeClients ?? ImmutableHashSet<ClientType>.Empty; | |||
| Activities = activities ?? ImmutableList<IActivity>.Empty; | |||
| } | |||
| @@ -31,7 +29,7 @@ namespace Discord.WebSocket | |||
| { | |||
| var clients = ConvertClientTypesDict(model.ClientStatus.GetValueOrDefault()); | |||
| var activities = ConvertActivitiesList(model.Activities); | |||
| return new SocketPresence(model.Status, model.Game?.ToEntity(), clients, activities); | |||
| return new SocketPresence(model.Status, clients, activities); | |||
| } | |||
| /// <summary> | |||
| /// Creates a new <see cref="IReadOnlyCollection{T}"/> containing all of the client types | |||
| @@ -84,7 +82,7 @@ namespace Discord.WebSocket | |||
| /// A string that resolves to <see cref="Discord.WebSocket.SocketPresence.Status" />. | |||
| /// </returns> | |||
| public override string ToString() => Status.ToString(); | |||
| private string DebuggerDisplay => $"{Status}{(Activity != null ? $", {Activity.Name}": "")}"; | |||
| private string DebuggerDisplay => $"{Status}{(Activities?.FirstOrDefault()?.Name ?? "")}"; | |||
| internal SocketPresence Clone() => this; | |||
| } | |||
| @@ -25,7 +25,7 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public override bool IsWebhook => false; | |||
| /// <inheritdoc /> | |||
| internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null, null); } set { } } | |||
| internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null); } set { } } | |||
| /// <inheritdoc /> | |||
| /// <exception cref="NotSupportedException">This field is not supported for an unknown user.</exception> | |||
| internal override SocketGlobalUser GlobalUser => | |||
| @@ -38,8 +38,6 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public string Mention => MentionUtils.MentionUser(Id); | |||
| /// <inheritdoc /> | |||
| public IActivity Activity => Presence.Activity; | |||
| /// <inheritdoc /> | |||
| public UserStatus Status => Presence.Status; | |||
| /// <inheritdoc /> | |||
| public IImmutableSet<ClientType> ActiveClients => Presence.ActiveClients ?? ImmutableHashSet<ClientType>.Empty; | |||
| @@ -94,8 +92,8 @@ namespace Discord.WebSocket | |||
| } | |||
| /// <inheritdoc /> | |||
| public async Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null) | |||
| => GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options).ConfigureAwait(false) as IDMChannel; | |||
| public async Task<IDMChannel> CreateDMChannelAsync(RequestOptions options = null) | |||
| => await UserHelper.CreateDMChannelAsync(this, Discord, options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
| @@ -30,7 +30,7 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public override bool IsWebhook => true; | |||
| /// <inheritdoc /> | |||
| internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null, null); } set { } } | |||
| internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null); } set { } } | |||
| internal override SocketGlobalUser GlobalUser => | |||
| throw new NotSupportedException(); | |||