@@ -1,5 +1,36 @@ | |||||
# Changelog | # Changelog | ||||
## [2.4.0] - 2021-05-22 | |||||
### Added | |||||
- #1726 Add stickers (91a9063) | |||||
- #1753 Webhook message edit & delete functionality (f67cd8e) | |||||
- #1757 Add ability to add/remove roles by id (4c9910c) | |||||
- #1781 Add GetEmotesAsync to IGuild (df23d57) | |||||
- #1801 Add missing property to MESSAGE_REACTION_ADD event (0715d7d) | |||||
- #1828 Add methods to interact with reactions without a message object (5b244f2) | |||||
- #1830 Add ModifyMessageAsync to IMessageChannel (365a848) | |||||
- #1844 Add Discord Certified Moderator user flag (4b8d444) | |||||
### Fixed | |||||
- #1486 Add type reader when entity type reader exists (c46daaa) | |||||
- #1835 Cached message emoji cleanup at MESSAGE_REACTION_REMOVE_EMOJI (8afef82) | |||||
### Misc | |||||
- #1778 Remove URI check from EmbedBuilder (25b04c4) | |||||
- #1800 Fix spelling in SnowflakeUtils.FromSnowflake (6aff419) | |||||
## [2.3.1] - 2021-03-10 | |||||
### Fixed | |||||
- #1761 Deadlock in DiscordShardedClient when Ready is never received (73e5cc2) | |||||
- #1773 Private methods aren't added as commands (0fc713a) | |||||
- #1780 NullReferenceException in pin/unpin audit logs (f794163) | |||||
- #1786 Add ChannelType property to ChannelInfo audit log (6ac5ea1) | |||||
- #1791 Update Webhook ChannelId from model change (d2518db) | |||||
- #1794 Audit log UserId can be null (d41aeee) | |||||
### Misc | |||||
- #1774 Add remark regarding CustomStatus as the activity (51b7afe) | |||||
## [2.3.0] - 2021-01-28 | ## [2.3.0] - 2021-01-28 | ||||
### Added | ### Added | ||||
- #1491 Add INVITE_CREATE and INVITE_DELETE events (1ab670b) | - #1491 Add INVITE_CREATE and INVITE_DELETE events (1ab670b) | ||||
@@ -1,6 +1,6 @@ | |||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
<PropertyGroup> | <PropertyGroup> | ||||
<VersionPrefix>2.3.1</VersionPrefix> | |||||
<VersionPrefix>3.0.0</VersionPrefix> | |||||
<VersionSuffix>dev</VersionSuffix> | <VersionSuffix>dev</VersionSuffix> | ||||
<LangVersion>latest</LangVersion> | <LangVersion>latest</LangVersion> | ||||
<Authors>Discord.Net Contributors</Authors> | <Authors>Discord.Net Contributors</Authors> | ||||
@@ -28,7 +28,7 @@ public async Task SendRichEmbedAsync() | |||||
var embed = new EmbedBuilder | var embed = new EmbedBuilder | ||||
{ | { | ||||
// Embed property can be set within object initializer | // Embed property can be set within object initializer | ||||
Title = "Hello world!" | |||||
Title = "Hello world!", | |||||
Description = "I am a description set by initializer." | Description = "I am a description set by initializer." | ||||
}; | }; | ||||
// Or with methods | // 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 | each other, you may use the @Discord.Commands.PriorityAttribute to | ||||
specify which should be tested before the other. | 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. | priority will be called first. | ||||
### Command Context | ### Command Context | ||||
@@ -80,15 +80,11 @@ recommended for these operations to be awaited in a | |||||
properly established async context whenever possible. | properly established async context whenever possible. | ||||
To establish an async context, we will be creating an async main method | To establish an async context, we will be creating an async main method | ||||
in your console application, and rewriting the static main method to | |||||
invoke the new async main. | |||||
in your console application. | |||||
[!code-csharp[Async Context](samples/first-bot/async-context.cs)] | [!code-csharp[Async Context](samples/first-bot/async-context.cs)] | ||||
As a result of this, your program will now start and immediately | |||||
jump into an async context. This allows us to create a connection | |||||
to Discord later on without having to worry about setting up the | |||||
correct async implementation. | |||||
As a result of this, your program will now start into an async context. | |||||
> [!WARNING] | > [!WARNING] | ||||
> If your application throws any exceptions within an async context, | > If your application throws any exceptions within an async context, | ||||
@@ -1,7 +1,6 @@ | |||||
public class Program | public class Program | ||||
{ | { | ||||
public static void Main(string[] args) | |||||
=> new Program().MainAsync().GetAwaiter().GetResult(); | |||||
public static Task Main(string[] args) => new Program().MainAsync(); | |||||
public async Task MainAsync() | public async Task MainAsync() | ||||
{ | { | ||||
@@ -2,8 +2,7 @@ public class Program | |||||
{ | { | ||||
private DiscordSocketClient _client; | private DiscordSocketClient _client; | ||||
public static void Main(string[] args) | |||||
=> new Program().MainAsync().GetAwaiter().GetResult(); | |||||
public static Task Main(string[] args) => new Program().MainAsync(); | |||||
public async Task MainAsync() | public async Task MainAsync() | ||||
{ | { | ||||
@@ -10,11 +10,11 @@ using Discord.WebSocket; | |||||
class Program | class Program | ||||
{ | { | ||||
// Program entry point | // Program entry point | ||||
static void Main(string[] args) | |||||
static Task Main(string[] args) | |||||
{ | { | ||||
// Call the Program constructor, followed by the | // Call the Program constructor, followed by the | ||||
// MainAsync method and wait until it finishes (which should be never). | // MainAsync method and wait until it finishes (which should be never). | ||||
new Program().MainAsync().GetAwaiter().GetResult(); | |||||
return new Program().MainAsync(); | |||||
} | } | ||||
private readonly DiscordSocketClient _client; | private readonly DiscordSocketClient _client; | ||||
@@ -9,7 +9,7 @@ namespace _03_sharded_client.Modules | |||||
[Command("info")] | [Command("info")] | ||||
public async Task InfoAsync() | public async Task InfoAsync() | ||||
{ | { | ||||
var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards} shards! | |||||
var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards.Count} shards! | |||||
This guild is being served by shard number {Context.Client.GetShardFor(Context.Guild).ShardId}"; | This guild is being served by shard number {Context.Client.GetShardFor(Context.Guild).ShardId}"; | ||||
await ReplyAsync(msg); | await ReplyAsync(msg); | ||||
} | } | ||||
@@ -16,7 +16,7 @@ namespace Discord | |||||
/// <see href="https://discord.com/developers/docs/reference#api-versioning">Discord API documentation</see> | /// <see href="https://discord.com/developers/docs/reference#api-versioning">Discord API documentation</see> | ||||
/// .</para> | /// .</para> | ||||
/// </returns> | /// </returns> | ||||
public const int APIVersion = 6; | |||||
public const int APIVersion = 9; | |||||
/// <summary> | /// <summary> | ||||
/// Returns the Voice API version Discord.Net uses. | /// Returns the Voice API version Discord.Net uses. | ||||
/// </summary> | /// </summary> | ||||
@@ -43,7 +43,7 @@ namespace Discord | |||||
/// <returns> | /// <returns> | ||||
/// The user agent used in each Discord.Net request. | /// The user agent used in each Discord.Net request. | ||||
/// </returns> | /// </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> | /// <summary> | ||||
/// Returns the base Discord API URL. | /// Returns the base Discord API URL. | ||||
/// </summary> | /// </summary> | ||||
@@ -141,18 +141,6 @@ namespace Discord | |||||
/// </remarks> | /// </remarks> | ||||
internal bool DisplayInitialLog { get; set; } = true; | 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> | /// <summary> | ||||
/// Gets or sets whether or not rate-limits should use the system clock. | /// Gets or sets whether or not rate-limits should use the system clock. | ||||
/// </summary> | /// </summary> | ||||
@@ -1,21 +0,0 @@ | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Provides properties that are used to modify the widget of an <see cref="IGuild" /> with the specified changes. | |||||
/// </summary> | |||||
public class GuildEmbedProperties | |||||
{ | |||||
/// <summary> | |||||
/// Sets whether the widget should be enabled. | |||||
/// </summary> | |||||
public Optional<bool> Enabled { get; set; } | |||||
/// <summary> | |||||
/// Sets the channel that the invite should place its users in, if not <c>null</c>. | |||||
/// </summary> | |||||
public Optional<IChannel> Channel { get; set; } | |||||
/// <summary> | |||||
/// Sets the channel the invite should place its users in, if not <c>null</c>. | |||||
/// </summary> | |||||
public Optional<ulong?> ChannelId { get; set; } | |||||
} | |||||
} |
@@ -28,13 +28,6 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
int AFKTimeout { get; } | int AFKTimeout { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets a value that indicates whether this guild is embeddable (i.e. can use widget). | |||||
/// </summary> | |||||
/// <returns> | |||||
/// <see langword="true" /> if this guild has a widget enabled; otherwise <see langword="false" />. | |||||
/// </returns> | |||||
bool IsEmbeddable { get; } | |||||
/// <summary> | |||||
/// Gets a value that indicates whether this guild has the widget enabled. | /// Gets a value that indicates whether this guild has the widget enabled. | ||||
/// </summary> | /// </summary> | ||||
/// <returns> | /// <returns> | ||||
@@ -132,29 +125,6 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
ulong? AFKChannelId { get; } | ulong? AFKChannelId { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets the ID of the default channel for this guild. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This property retrieves the snowflake identifier of the first viewable text channel for this guild. | |||||
/// <note type="warning"> | |||||
/// This channel does not guarantee the user can send message to it, as it only looks for the first viewable | |||||
/// text channel. | |||||
/// </note> | |||||
/// </remarks> | |||||
/// <returns> | |||||
/// A <see langword="ulong"/> representing the snowflake identifier of the default text channel; <c>0</c> if | |||||
/// none can be found. | |||||
/// </returns> | |||||
ulong DefaultChannelId { get; } | |||||
/// <summary> | |||||
/// Gets the ID of the widget embed channel of this guild. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// A <see langword="ulong"/> representing the snowflake identifier of the embedded channel found within the | |||||
/// widget settings of this guild; <see langword="null" /> if none is set. | |||||
/// </returns> | |||||
ulong? EmbedChannelId { get; } | |||||
/// <summary> | |||||
/// Gets the ID of the channel assigned to the widget of this guild. | /// Gets the ID of the channel assigned to the widget of this guild. | ||||
/// </summary> | /// </summary> | ||||
/// <returns> | /// <returns> | ||||
@@ -364,16 +334,6 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null); | Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null); | ||||
/// <summary> | /// <summary> | ||||
/// Modifies this guild's embed channel. | |||||
/// </summary> | |||||
/// <param name="func">The delegate containing the properties to modify the guild widget with.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous modification operation. | |||||
/// </returns> | |||||
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")] | |||||
Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null); | |||||
/// <summary> | |||||
/// Modifies this guild's widget. | /// Modifies this guild's widget. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="func">The delegate containing the properties to modify the guild widget with.</param> | /// <param name="func">The delegate containing the properties to modify the guild widget with.</param> | ||||
@@ -592,17 +552,6 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
/// <summary> | /// <summary> | ||||
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. | |||||
/// </summary> | |||||
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param> | |||||
/// <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 the embed channel set | |||||
/// within the server's widget settings; <see langword="null" /> if none is set. | |||||
/// </returns> | |||||
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")] | |||||
Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||||
/// <summary> | |||||
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. | /// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param> | /// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param> | ||||
@@ -8,10 +8,10 @@ namespace Discord | |||||
/// <summary> | /// <summary> | ||||
/// The target of the permission is a role. | /// The target of the permission is a role. | ||||
/// </summary> | /// </summary> | ||||
Role, | |||||
Role = 0, | |||||
/// <summary> | /// <summary> | ||||
/// The target of the permission is a user. | /// The target of the permission is a user. | ||||
/// </summary> | /// </summary> | ||||
User | |||||
User = 1, | |||||
} | } | ||||
} | } |
@@ -16,14 +16,6 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
bool IsTemporary { get; } | bool IsTemporary { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets a value that indicates whether the invite has been revoked. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// <c>true</c> if this invite was revoked; otherwise <c>false</c>. | |||||
/// </returns> | |||||
[Obsolete("This property doesn't exist anymore and shouldn't be used.")] | |||||
bool IsRevoked { get; } | |||||
/// <summary> | |||||
/// Gets the time (in seconds) until the invite expires. | /// Gets the time (in seconds) until the invite expires. | ||||
/// </summary> | /// </summary> | ||||
/// <returns> | /// <returns> | ||||
@@ -36,18 +36,6 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null); | Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null); | ||||
/// <summary> | /// <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. | /// Adds this message to its channel's pinned messages. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
@@ -60,9 +60,6 @@ namespace Discord | |||||
/// <summary> | /// <summary> | ||||
/// The message is an inline reply. | /// The message is an inline reply. | ||||
/// </summary> | /// </summary> | ||||
/// <remarks> | |||||
/// Only available in API v8. | |||||
/// </remarks> | |||||
Reply = 19, | Reply = 19, | ||||
} | } | ||||
} | } |
@@ -22,11 +22,6 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
AddReactions = 0x00_00_00_40, | AddReactions = 0x00_00_00_40, | ||||
/// <summary> | /// <summary> | ||||
/// Allows for reading of messages. This flag is obsolete, use <see cref = "ViewChannel" /> instead. | |||||
/// </summary> | |||||
[Obsolete("Use ViewChannel instead.")] | |||||
ReadMessages = ViewChannel, | |||||
/// <summary> | |||||
/// Allows guild members to view a channel, which includes reading messages in text channels. | /// Allows guild members to view a channel, which includes reading messages in text channels. | ||||
/// </summary> | /// </summary> | ||||
ViewChannel = 0x00_00_04_00, | ViewChannel = 0x00_00_04_00, | ||||
@@ -45,9 +45,6 @@ namespace Discord | |||||
/// <summary> If <c>true</c>, a user may add reactions. </summary> | /// <summary> If <c>true</c>, a user may add reactions. </summary> | ||||
public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); | public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); | ||||
/// <summary> If <c>true</c>, a user may join channels. </summary> | |||||
[Obsolete("Use ViewChannel instead.")] | |||||
public bool ReadMessages => ViewChannel; | |||||
/// <summary> If <c>true</c>, a user may view channels. </summary> | /// <summary> If <c>true</c>, a user may view channels. </summary> | ||||
public bool ViewChannel => Permissions.GetValue(RawValue, ChannelPermission.ViewChannel); | public bool ViewChannel => Permissions.GetValue(RawValue, ChannelPermission.ViewChannel); | ||||
@@ -65,8 +65,6 @@ namespace Discord | |||||
/// Allows for viewing of audit logs. | /// Allows for viewing of audit logs. | ||||
/// </summary> | /// </summary> | ||||
ViewAuditLog = 0x00_00_00_80, | ViewAuditLog = 0x00_00_00_80, | ||||
[Obsolete("Use ViewChannel instead.")] | |||||
ReadMessages = ViewChannel, | |||||
ViewChannel = 0x00_00_04_00, | ViewChannel = 0x00_00_04_00, | ||||
SendMessages = 0x00_00_08_00, | SendMessages = 0x00_00_08_00, | ||||
/// <summary> | /// <summary> | ||||
@@ -37,9 +37,6 @@ namespace Discord | |||||
/// <summary> If <c>true</c>, a user may view the guild insights. </summary> | /// <summary> If <c>true</c>, a user may view the guild insights. </summary> | ||||
public bool ViewGuildInsights => Permissions.GetValue(RawValue, GuildPermission.ViewGuildInsights); | public bool ViewGuildInsights => Permissions.GetValue(RawValue, GuildPermission.ViewGuildInsights); | ||||
/// <summary> If True, a user may join channels. </summary> | |||||
[Obsolete("Use ViewChannel instead.")] | |||||
public bool ReadMessages => ViewChannel; | |||||
/// <summary> If True, a user may view channels. </summary> | /// <summary> If True, a user may view channels. </summary> | ||||
public bool ViewChannel => Permissions.GetValue(RawValue, GuildPermission.ViewChannel); | public bool ViewChannel => Permissions.GetValue(RawValue, GuildPermission.ViewChannel); | ||||
/// <summary> If True, a user may send messages. </summary> | /// <summary> If True, a user may send messages. </summary> | ||||
@@ -90,6 +87,9 @@ namespace Discord | |||||
/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary> | /// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary> | ||||
public GuildPermissions(ulong rawValue) { RawValue = rawValue; } | 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, | private GuildPermissions(ulong initialValue, | ||||
bool? createInstantInvite = null, | bool? createInstantInvite = null, | ||||
bool? kickMembers = null, | bool? kickMembers = null, | ||||
@@ -43,9 +43,6 @@ namespace Discord | |||||
/// <summary> If Allowed, a user may add reactions. </summary> | /// <summary> If Allowed, a user may add reactions. </summary> | ||||
public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); | public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); | ||||
/// <summary> If Allowed, a user may join channels. </summary> | /// <summary> If Allowed, a user may join channels. </summary> | ||||
[Obsolete("Use ViewChannel instead.")] | |||||
public PermValue ReadMessages => ViewChannel; | |||||
/// <summary> If Allowed, a user may join channels. </summary> | |||||
public PermValue ViewChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ViewChannel); | public PermValue ViewChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ViewChannel); | ||||
/// <summary> If Allowed, a user may send messages. </summary> | /// <summary> If Allowed, a user may send messages. </summary> | ||||
public PermValue SendMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessages); | public PermValue SendMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessages); | ||||
@@ -93,6 +90,13 @@ namespace Discord | |||||
DenyValue = denyValue; | 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, | private OverwritePermissions(ulong allowValue, ulong denyValue, | ||||
PermValue? createInstantInvite = null, | PermValue? createInstantInvite = null, | ||||
PermValue? manageChannel = null, | PermValue? manageChannel = null, | ||||
@@ -7,10 +7,6 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
public interface IPresence | public interface IPresence | ||||
{ | { | ||||
/// <summary> | |||||
/// Gets the activity this user is currently doing. | |||||
/// </summary> | |||||
IActivity Activity { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the current status of this user. | /// Gets the current status of this user. | ||||
/// </summary> | /// </summary> | ||||
@@ -87,7 +87,7 @@ namespace Discord | |||||
UserProperties? PublicFlags { get; } | UserProperties? PublicFlags { get; } | ||||
/// <summary> | /// <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> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// This method is used to obtain or create a channel used to send a direct message. | /// This method is used to obtain or create a channel used to send a direct message. | ||||
@@ -102,7 +102,7 @@ namespace Discord | |||||
/// <example> | /// <example> | ||||
/// <para>The following example attempts to send a direct message to the target user and logs the incident should | /// <para>The following example attempts to send a direct message to the target user and logs the incident should | ||||
/// it fail.</para> | /// it fail.</para> | ||||
/// <code region="GetOrCreateDMChannelAsync" language="cs" | |||||
/// <code region="CreateDMChannelAsync" language="cs" | |||||
/// source="../../../Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs"/> | /// source="../../../Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs"/> | ||||
/// </example> | /// </example> | ||||
/// <param name="options">The options to be used when sending the request.</param> | /// <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 | /// 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. | /// contains the DM channel associated with this user. | ||||
/// </returns> | /// </returns> | ||||
Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null); | |||||
Task<IDMChannel> CreateDMChannelAsync(RequestOptions options = null); | |||||
} | } | ||||
} | } |
@@ -22,12 +22,6 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
HypeSquadEvents = 1 << 2, | HypeSquadEvents = 1 << 2, | ||||
/// <summary> | /// <summary> | ||||
/// Flag given to users who have participated in the bug report program. | |||||
/// This flag is obsolete, use <see cref="BugHunterLevel1"/> instead. | |||||
/// </summary> | |||||
[Obsolete("Use BugHunterLevel1 instead.")] | |||||
BugHunter = 1 << 3, | |||||
/// <summary> | |||||
/// Flag given to users who have participated in the bug report program and are level 1. | /// Flag given to users who have participated in the bug report program and are level 1. | ||||
/// </summary> | /// </summary> | ||||
BugHunterLevel1 = 1 << 3, | BugHunterLevel1 = 1 << 3, | ||||
@@ -67,5 +61,9 @@ namespace Discord | |||||
/// Flag given to users that developed bots and early verified their accounts. | /// Flag given to users that developed bots and early verified their accounts. | ||||
/// </summary> | /// </summary> | ||||
EarlyVerifiedBotDeveloper = 1 << 17, | EarlyVerifiedBotDeveloper = 1 << 17, | ||||
/// <summary> | |||||
/// Flag given to users that are discord certified moderators who has give discord's exam. | |||||
/// </summary> | |||||
DiscordCertifiedModerator = 1 << 18, | |||||
} | } | ||||
} | } |
@@ -1,10 +0,0 @@ | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
internal static class StringExtensions | |||||
{ | |||||
public static bool IsNullOrUri(this string url) => | |||||
string.IsNullOrEmpty(url) || Uri.IsWellFormedUriString(url, UriKind.Absolute); | |||||
} | |||||
} |
@@ -42,7 +42,7 @@ namespace Discord | |||||
RequestOptions options = null, | RequestOptions options = null, | ||||
AllowedMentions allowedMentions = 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> | /// <summary> | ||||
@@ -94,7 +94,7 @@ namespace Discord | |||||
RequestOptions options = null | 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> | /// <summary> | ||||
@@ -149,7 +149,7 @@ namespace Discord | |||||
Embed embed = null, | Embed embed = null, | ||||
RequestOptions options = 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> | /// <summary> | ||||
@@ -39,5 +39,16 @@ namespace Discord | |||||
DirectMessageReactions = 1 << 13, | DirectMessageReactions = 1 << 13, | ||||
/// <summary> This intent includes TYPING_START </summary> | /// <summary> This intent includes TYPING_START </summary> | ||||
DirectMessageTyping = 1 << 14, | 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 | |||||
} | |||||
} |
@@ -5,8 +5,6 @@ namespace Discord | |||||
/// <summary> Specifies the type of token to use with the client. </summary> | /// <summary> Specifies the type of token to use with the client. </summary> | ||||
public enum TokenType | public enum TokenType | ||||
{ | { | ||||
[Obsolete("User logins are deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827", error: true)] | |||||
User, | |||||
/// <summary> | /// <summary> | ||||
/// An OAuth2 token type. | /// An OAuth2 token type. | ||||
/// </summary> | /// </summary> | ||||
@@ -18,11 +18,11 @@ namespace Discord.Net.Examples.Core.Entities.Users | |||||
#endregion | #endregion | ||||
#region GetOrCreateDMChannelAsync | |||||
#region CreateDMChannelAsync | |||||
public async Task MessageUserAsync(IUser user) | public async Task MessageUserAsync(IUser user) | ||||
{ | { | ||||
var channel = await user.GetOrCreateDMChannelAsync(); | |||||
var channel = await user.CreateDMChannelAsync(); | |||||
try | try | ||||
{ | { | ||||
await channel.SendMessageAsync("Awesome stuff!"); | await channel.SendMessageAsync("Awesome stuff!"); | ||||
@@ -15,7 +15,7 @@ namespace Discord.Net.Examples.WebSocket | |||||
=> client.ReactionAdded += HandleReactionAddedAsync; | => client.ReactionAdded += HandleReactionAddedAsync; | ||||
public async Task HandleReactionAddedAsync(Cacheable<IUserMessage, ulong> cachedMessage, | public async Task HandleReactionAddedAsync(Cacheable<IUserMessage, ulong> cachedMessage, | ||||
ISocketMessageChannel originChannel, SocketReaction reaction) | |||||
Cacheable<IMessageChannel, ulong> originChannel, SocketReaction reaction) | |||||
{ | { | ||||
var message = await cachedMessage.GetOrDownloadAsync(); | var message = await cachedMessage.GetOrDownloadAsync(); | ||||
if (message != null && reaction.User.IsSpecified) | if (message != null && reaction.User.IsSpecified) | ||||
@@ -100,16 +100,17 @@ namespace Discord.Net.Examples.WebSocket | |||||
public void HookMessageDeleted(BaseSocketClient client) | public void HookMessageDeleted(BaseSocketClient client) | ||||
=> client.MessageDeleted += HandleMessageDelete; | => client.MessageDeleted += HandleMessageDelete; | ||||
public Task HandleMessageDelete(Cacheable<IMessage, ulong> cachedMessage, ISocketMessageChannel channel) | |||||
public async Task HandleMessageDelete(Cacheable<IMessage, ulong> cachedMessage, Cacheable<IMessageChannel, ulong> cachedChannel) | |||||
{ | { | ||||
// check if the message exists in cache; if not, we cannot report what was removed | // check if the message exists in cache; if not, we cannot report what was removed | ||||
if (!cachedMessage.HasValue) return Task.CompletedTask; | |||||
if (!cachedMessage.HasValue) return; | |||||
// gets or downloads the channel if it's not in the cache | |||||
IMessageChannel channel = await cachedChannel.GetOrDownloadAsync(); | |||||
var message = cachedMessage.Value; | var message = cachedMessage.Value; | ||||
Console.WriteLine( | Console.WriteLine( | ||||
$"A message ({message.Id}) from {message.Author} was removed from the channel {channel.Name} ({channel.Id}):" | $"A message ({message.Id}) from {message.Author} was removed from the channel {channel.Name} ({channel.Id}):" | ||||
+ Environment.NewLine | + Environment.NewLine | ||||
+ message.Content); | + message.Content); | ||||
return Task.CompletedTask; | |||||
} | } | ||||
#endregion | #endregion | ||||
@@ -23,10 +23,6 @@ namespace Discord.API | |||||
public ulong? AFKChannelId { get; set; } | public ulong? AFKChannelId { get; set; } | ||||
[JsonProperty("afk_timeout")] | [JsonProperty("afk_timeout")] | ||||
public int AFKTimeout { get; set; } | public int AFKTimeout { get; set; } | ||||
[JsonProperty("embed_enabled")] | |||||
public Optional<bool> EmbedEnabled { get; set; } | |||||
[JsonProperty("embed_channel_id")] | |||||
public Optional<ulong?> EmbedChannelId { get; set; } | |||||
[JsonProperty("verification_level")] | [JsonProperty("verification_level")] | ||||
public VerificationLevel VerificationLevel { get; set; } | public VerificationLevel VerificationLevel { get; set; } | ||||
[JsonProperty("default_message_notifications")] | [JsonProperty("default_message_notifications")] | ||||
@@ -1,13 +0,0 @@ | |||||
#pragma warning disable CS1591 | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API | |||||
{ | |||||
internal class GuildEmbed | |||||
{ | |||||
[JsonProperty("enabled")] | |||||
public bool Enabled { get; set; } | |||||
[JsonProperty("channel_id")] | |||||
public ulong? ChannelId { get; set; } | |||||
} | |||||
} |
@@ -6,5 +6,7 @@ namespace Discord.API | |||||
{ | { | ||||
[JsonProperty("code")] | [JsonProperty("code")] | ||||
public string Code { get; set; } | public string Code { get; set; } | ||||
[JsonProperty("uses")] | |||||
public int Uses { get; set; } | |||||
} | } | ||||
} | } |
@@ -10,8 +10,8 @@ namespace Discord.API | |||||
[JsonProperty("type")] | [JsonProperty("type")] | ||||
public PermissionTarget TargetType { get; set; } | public PermissionTarget TargetType { get; set; } | ||||
[JsonProperty("deny"), Int53] | [JsonProperty("deny"), Int53] | ||||
public ulong Deny { get; set; } | |||||
public string Deny { get; set; } | |||||
[JsonProperty("allow"), Int53] | [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; } | public Optional<ulong> GuildId { get; set; } | ||||
[JsonProperty("status")] | [JsonProperty("status")] | ||||
public UserStatus Status { get; set; } | public UserStatus Status { get; set; } | ||||
[JsonProperty("game")] | |||||
public Game Game { get; set; } | |||||
[JsonProperty("roles")] | [JsonProperty("roles")] | ||||
public Optional<ulong[]> Roles { get; set; } | public Optional<ulong[]> Roles { get; set; } | ||||
@@ -18,7 +18,7 @@ namespace Discord.API | |||||
[JsonProperty("position")] | [JsonProperty("position")] | ||||
public int Position { get; set; } | public int Position { get; set; } | ||||
[JsonProperty("permissions"), Int53] | [JsonProperty("permissions"), Int53] | ||||
public ulong Permissions { get; set; } | |||||
public string Permissions { get; set; } | |||||
[JsonProperty("managed")] | [JsonProperty("managed")] | ||||
public bool Managed { get; set; } | public bool Managed { get; set; } | ||||
[JsonProperty("tags")] | [JsonProperty("tags")] | ||||
@@ -1,4 +1,4 @@ | |||||
#pragma warning disable CS1591 | |||||
#pragma warning disable CS1591 | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
namespace Discord.API | namespace Discord.API | ||||
@@ -14,6 +14,6 @@ namespace Discord.API | |||||
[JsonProperty("owner")] | [JsonProperty("owner")] | ||||
public bool Owner { get; set; } | public bool Owner { get; set; } | ||||
[JsonProperty("permissions"), Int53] | [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; | using Newtonsoft.Json; | ||||
namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
@@ -7,13 +7,13 @@ namespace Discord.API.Rest | |||||
internal class ModifyChannelPermissionsParams | internal class ModifyChannelPermissionsParams | ||||
{ | { | ||||
[JsonProperty("type")] | [JsonProperty("type")] | ||||
public string Type { get; } | |||||
public int Type { get; } | |||||
[JsonProperty("allow")] | [JsonProperty("allow")] | ||||
public ulong Allow { get; } | |||||
public string Allow { get; } | |||||
[JsonProperty("deny")] | [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; | Type = type; | ||||
Allow = allow; | Allow = allow; | ||||
@@ -1,4 +1,4 @@ | |||||
#pragma warning disable CS1591 | |||||
#pragma warning disable CS1591 | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
@@ -9,7 +9,7 @@ namespace Discord.API.Rest | |||||
[JsonProperty("name")] | [JsonProperty("name")] | ||||
public Optional<string> Name { get; set; } | public Optional<string> Name { get; set; } | ||||
[JsonProperty("permissions")] | [JsonProperty("permissions")] | ||||
public Optional<ulong> Permissions { get; set; } | |||||
public Optional<string> Permissions { get; set; } | |||||
[JsonProperty("color")] | [JsonProperty("color")] | ||||
public Optional<uint> Color { get; set; } | public Optional<uint> Color { get; set; } | ||||
[JsonProperty("hoist")] | [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; } | |||||
} | |||||
} |
@@ -69,14 +69,6 @@ namespace Discord.Rest | |||||
return RestGuild.Create(client, model); | return RestGuild.Create(client, model); | ||||
return null; | return null; | ||||
} | } | ||||
public static async Task<RestGuildEmbed?> GetGuildEmbedAsync(BaseDiscordClient client, | |||||
ulong id, RequestOptions options) | |||||
{ | |||||
var model = await client.ApiClient.GetGuildEmbedAsync(id, options).ConfigureAwait(false); | |||||
if (model != null) | |||||
return RestGuildEmbed.Create(model); | |||||
return null; | |||||
} | |||||
public static async Task<RestGuildWidget?> GetGuildWidgetAsync(BaseDiscordClient client, | public static async Task<RestGuildWidget?> GetGuildWidgetAsync(BaseDiscordClient client, | ||||
ulong id, RequestOptions options) | ulong id, RequestOptions options) | ||||
{ | { | ||||
@@ -45,20 +45,18 @@ namespace Discord.API | |||||
internal string AuthToken { get; private set; } | internal string AuthToken { get; private set; } | ||||
internal IRestClient RestClient { get; private set; } | internal IRestClient RestClient { get; private set; } | ||||
internal ulong? CurrentUserId { get; set; } | internal ulong? CurrentUserId { get; set; } | ||||
public RateLimitPrecision RateLimitPrecision { get; private set; } | |||||
internal bool UseSystemClock { get; set; } | internal bool UseSystemClock { get; set; } | ||||
internal JsonSerializer Serializer => _serializer; | internal JsonSerializer Serializer => _serializer; | ||||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | ||||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | 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; | _restClientProvider = restClientProvider; | ||||
UserAgent = userAgent; | UserAgent = userAgent; | ||||
DefaultRetryMode = defaultRetryMode; | DefaultRetryMode = defaultRetryMode; | ||||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
RateLimitPrecision = rateLimitPrecision; | |||||
UseSystemClock = useSystemClock; | UseSystemClock = useSystemClock; | ||||
RequestQueue = new RequestQueue(); | RequestQueue = new RequestQueue(); | ||||
@@ -75,14 +73,12 @@ namespace Discord.API | |||||
RestClient.SetHeader("accept", "*/*"); | RestClient.SetHeader("accept", "*/*"); | ||||
RestClient.SetHeader("user-agent", UserAgent); | RestClient.SetHeader("user-agent", UserAgent); | ||||
RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); | RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); | ||||
RestClient.SetHeader("X-RateLimit-Precision", RateLimitPrecision.ToString().ToLower()); | |||||
} | } | ||||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | ||||
internal static string GetPrefixedToken(TokenType tokenType, string token) | internal static string GetPrefixedToken(TokenType tokenType, string token) | ||||
{ | { | ||||
return tokenType switch | return tokenType switch | ||||
{ | { | ||||
default(TokenType) => token, | |||||
TokenType.Bot => $"Bot {token}", | TokenType.Bot => $"Bot {token}", | ||||
TokenType.Bearer => $"Bearer {token}", | TokenType.Bearer => $"Bearer {token}", | ||||
_ => throw new ArgumentException(message: "Unknown OAuth token type.", paramName: nameof(tokenType)), | _ => throw new ArgumentException(message: "Unknown OAuth token type.", paramName: nameof(tokenType)), | ||||
@@ -645,16 +641,6 @@ namespace Discord.API | |||||
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | 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) | public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) | ||||
{ | { | ||||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | Preconditions.NotEqual(channelId, 0, nameof(channelId)); | ||||
@@ -936,7 +922,7 @@ namespace Discord.API | |||||
var ids = new BucketIds(guildId: guildId); | var ids = new BucketIds(guildId: guildId); | ||||
string reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={Uri.EscapeDataString(args.Reason)}"; | 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> | /// <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) | public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null) | ||||
@@ -949,32 +935,6 @@ namespace Discord.API | |||||
await SendAsync("DELETE", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); | await SendAsync("DELETE", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); | ||||
} | } | ||||
//Guild Embeds | |||||
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception> | |||||
public async Task<GuildEmbed> GetGuildEmbedAsync(ulong guildId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
try | |||||
{ | |||||
var ids = new BucketIds(guildId: guildId); | |||||
return await SendAsync<GuildEmbed>("GET", () => $"guilds/{guildId}/embed", ids, options: options).ConfigureAwait(false); | |||||
} | |||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } | |||||
} | |||||
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception> | |||||
/// <exception cref="ArgumentNullException"><paramref name="args"/> must not be <see langword="null"/>.</exception> | |||||
public async Task<GuildEmbed> ModifyGuildEmbedAsync(ulong guildId, Rest.ModifyGuildEmbedParams args, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotNull(args, nameof(args)); | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
var ids = new BucketIds(guildId: guildId); | |||||
return await SendJsonAsync<GuildEmbed>("PATCH", () => $"guilds/{guildId}/embed", args, ids, options: options).ConfigureAwait(false); | |||||
} | |||||
//Guild Widget | //Guild Widget | ||||
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception> | /// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception> | ||||
public async Task<GuildWidget> GetGuildWidgetAsync(ulong guildId, RequestOptions options = null) | public async Task<GuildWidget> GetGuildWidgetAsync(ulong guildId, RequestOptions options = null) | ||||
@@ -29,10 +29,7 @@ namespace Discord.Rest | |||||
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } | internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } | ||||
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | 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) | internal override void Dispose(bool disposing) | ||||
{ | { | ||||
@@ -80,9 +77,6 @@ namespace Discord.Rest | |||||
=> ClientHelper.GetGuildAsync(this, id, false, options); | => ClientHelper.GetGuildAsync(this, id, false, options); | ||||
public Task<RestGuild> GetGuildAsync(ulong id, bool withCounts, RequestOptions options = null) | public Task<RestGuild> GetGuildAsync(ulong id, bool withCounts, RequestOptions options = null) | ||||
=> ClientHelper.GetGuildAsync(this, id, withCounts, options); | => ClientHelper.GetGuildAsync(this, id, withCounts, options); | ||||
[Obsolete("This endpoint is deprecated, use GetGuildWidgetAsync instead.")] | |||||
public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id, RequestOptions options = null) | |||||
=> ClientHelper.GetGuildEmbedAsync(this, id, options); | |||||
public Task<RestGuildWidget?> GetGuildWidgetAsync(ulong id, RequestOptions options = null) | public Task<RestGuildWidget?> GetGuildWidgetAsync(ulong id, RequestOptions options = null) | ||||
=> ClientHelper.GetGuildWidgetAsync(this, id, options); | => ClientHelper.GetGuildWidgetAsync(this, id, options); | ||||
public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(RequestOptions options = null) | public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(RequestOptions options = null) | ||||
@@ -33,8 +33,8 @@ namespace Discord.Rest | |||||
{ | { | ||||
TargetId = overwrite.TargetId, | TargetId = overwrite.TargetId, | ||||
TargetType = overwrite.TargetType, | TargetType = overwrite.TargetType, | ||||
Allow = overwrite.Permissions.AllowValue, | |||||
Deny = overwrite.Permissions.DenyValue | |||||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||||
Deny = overwrite.Permissions.DenyValue.ToString() | |||||
}).ToArray() | }).ToArray() | ||||
: Optional.Create<API.Overwrite[]>(), | : Optional.Create<API.Overwrite[]>(), | ||||
}; | }; | ||||
@@ -59,8 +59,8 @@ namespace Discord.Rest | |||||
{ | { | ||||
TargetId = overwrite.TargetId, | TargetId = overwrite.TargetId, | ||||
TargetType = overwrite.TargetType, | TargetType = overwrite.TargetType, | ||||
Allow = overwrite.Permissions.AllowValue, | |||||
Deny = overwrite.Permissions.DenyValue | |||||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||||
Deny = overwrite.Permissions.DenyValue.ToString() | |||||
}).ToArray() | }).ToArray() | ||||
: Optional.Create<API.Overwrite[]>(), | : Optional.Create<API.Overwrite[]>(), | ||||
}; | }; | ||||
@@ -84,8 +84,8 @@ namespace Discord.Rest | |||||
{ | { | ||||
TargetId = overwrite.TargetId, | TargetId = overwrite.TargetId, | ||||
TargetType = overwrite.TargetType, | TargetType = overwrite.TargetType, | ||||
Allow = overwrite.Permissions.AllowValue, | |||||
Deny = overwrite.Permissions.DenyValue | |||||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||||
Deny = overwrite.Permissions.DenyValue.ToString() | |||||
}).ToArray() | }).ToArray() | ||||
: Optional.Create<API.Overwrite[]>(), | : Optional.Create<API.Overwrite[]>(), | ||||
}; | }; | ||||
@@ -328,13 +328,13 @@ namespace Discord.Rest | |||||
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | ||||
IUser user, OverwritePermissions perms, RequestOptions options) | 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); | await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args, options).ConfigureAwait(false); | ||||
} | } | ||||
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | ||||
IRole role, OverwritePermissions perms, RequestOptions options) | 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); | await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args, options).ConfigureAwait(false); | ||||
} | } | ||||
public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | ||||
@@ -450,8 +450,8 @@ namespace Discord.Rest | |||||
{ | { | ||||
TargetId = overwrite.TargetId, | TargetId = overwrite.TargetId, | ||||
TargetType = overwrite.TargetType, | TargetType = overwrite.TargetType, | ||||
Allow = overwrite.Permissions.AllowValue, | |||||
Deny = overwrite.Permissions.DenyValue | |||||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||||
Deny = overwrite.Permissions.DenyValue.ToString() | |||||
}).ToArray() | }).ToArray() | ||||
}; | }; | ||||
await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | ||||
@@ -4,7 +4,6 @@ using System.Collections.Generic; | |||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using EmbedModel = Discord.API.GuildEmbed; | |||||
using WidgetModel = Discord.API.GuildWidget; | using WidgetModel = Discord.API.GuildWidget; | ||||
using Model = Discord.API.Guild; | using Model = Discord.API.Guild; | ||||
using RoleModel = Discord.API.Role; | using RoleModel = Discord.API.Role; | ||||
@@ -81,26 +80,6 @@ namespace Discord.Rest | |||||
return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); | return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); | ||||
} | } | ||||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception> | /// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception> | ||||
public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, | |||||
Action<GuildEmbedProperties> func, RequestOptions options) | |||||
{ | |||||
if (func == null) throw new ArgumentNullException(nameof(func)); | |||||
var args = new GuildEmbedProperties(); | |||||
func(args); | |||||
var apiArgs = new API.Rest.ModifyGuildEmbedParams | |||||
{ | |||||
Enabled = args.Enabled | |||||
}; | |||||
if (args.Channel.IsSpecified) | |||||
apiArgs.ChannelId = args.Channel.Value?.Id; | |||||
else if (args.ChannelId.IsSpecified) | |||||
apiArgs.ChannelId = args.ChannelId.Value; | |||||
return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, apiArgs, options).ConfigureAwait(false); | |||||
} | |||||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception> | |||||
public static async Task<WidgetModel> ModifyWidgetAsync(IGuild guild, BaseDiscordClient client, | public static async Task<WidgetModel> ModifyWidgetAsync(IGuild guild, BaseDiscordClient client, | ||||
Action<GuildWidgetProperties> func, RequestOptions options) | Action<GuildWidgetProperties> func, RequestOptions options) | ||||
{ | { | ||||
@@ -205,8 +184,8 @@ namespace Discord.Rest | |||||
{ | { | ||||
TargetId = overwrite.TargetId, | TargetId = overwrite.TargetId, | ||||
TargetType = overwrite.TargetType, | TargetType = overwrite.TargetType, | ||||
Allow = overwrite.Permissions.AllowValue, | |||||
Deny = overwrite.Permissions.DenyValue | |||||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||||
Deny = overwrite.Permissions.DenyValue.ToString() | |||||
}).ToArray() | }).ToArray() | ||||
: Optional.Create<API.Overwrite[]>(), | : Optional.Create<API.Overwrite[]>(), | ||||
}; | }; | ||||
@@ -233,8 +212,8 @@ namespace Discord.Rest | |||||
{ | { | ||||
TargetId = overwrite.TargetId, | TargetId = overwrite.TargetId, | ||||
TargetType = overwrite.TargetType, | TargetType = overwrite.TargetType, | ||||
Allow = overwrite.Permissions.AllowValue, | |||||
Deny = overwrite.Permissions.DenyValue | |||||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||||
Deny = overwrite.Permissions.DenyValue.ToString() | |||||
}).ToArray() | }).ToArray() | ||||
: Optional.Create<API.Overwrite[]>(), | : Optional.Create<API.Overwrite[]>(), | ||||
}; | }; | ||||
@@ -258,8 +237,8 @@ namespace Discord.Rest | |||||
{ | { | ||||
TargetId = overwrite.TargetId, | TargetId = overwrite.TargetId, | ||||
TargetType = overwrite.TargetType, | TargetType = overwrite.TargetType, | ||||
Allow = overwrite.Permissions.AllowValue, | |||||
Deny = overwrite.Permissions.DenyValue | |||||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||||
Deny = overwrite.Permissions.DenyValue.ToString() | |||||
}).ToArray() | }).ToArray() | ||||
: Optional.Create<API.Overwrite[]>(), | : Optional.Create<API.Overwrite[]>(), | ||||
}; | }; | ||||
@@ -304,6 +283,7 @@ namespace Discord.Rest | |||||
var vanityModel = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); | var vanityModel = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); | ||||
if (vanityModel == null) throw new InvalidOperationException("This guild does not have a vanity URL."); | if (vanityModel == null) throw new InvalidOperationException("This guild does not have a vanity URL."); | ||||
var inviteModel = await client.ApiClient.GetInviteAsync(vanityModel.Code, options).ConfigureAwait(false); | var inviteModel = await client.ApiClient.GetInviteAsync(vanityModel.Code, options).ConfigureAwait(false); | ||||
inviteModel.Uses = vanityModel.Uses; | |||||
return RestInviteMetadata.Create(client, guild, null, inviteModel); | return RestInviteMetadata.Create(client, guild, null, inviteModel); | ||||
} | } | ||||
@@ -320,7 +300,7 @@ namespace Discord.Rest | |||||
Hoist = isHoisted, | Hoist = isHoisted, | ||||
Mentionable = isMentionable, | Mentionable = isMentionable, | ||||
Name = name, | 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); | var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, createGuildRoleParams, options).ConfigureAwait(false); | ||||
@@ -6,7 +6,6 @@ using System.Diagnostics; | |||||
using System.Globalization; | using System.Globalization; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using EmbedModel = Discord.API.GuildEmbed; | |||||
using WidgetModel = Discord.API.GuildWidget; | using WidgetModel = Discord.API.GuildWidget; | ||||
using Model = Discord.API.Guild; | using Model = Discord.API.Guild; | ||||
@@ -27,8 +26,6 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public int AFKTimeout { get; private set; } | public int AFKTimeout { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public bool IsEmbeddable { get; private set; } | |||||
/// <inheritdoc /> | |||||
public bool IsWidgetEnabled { get; private set; } | public bool IsWidgetEnabled { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public VerificationLevel VerificationLevel { get; private set; } | public VerificationLevel VerificationLevel { get; private set; } | ||||
@@ -42,8 +39,6 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public ulong? AFKChannelId { get; private set; } | public ulong? AFKChannelId { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public ulong? EmbedChannelId { get; private set; } | |||||
/// <inheritdoc /> | |||||
public ulong? WidgetChannelId { get; private set; } | public ulong? WidgetChannelId { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public ulong? SystemChannelId { get; private set; } | public ulong? SystemChannelId { get; private set; } | ||||
@@ -95,8 +90,6 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | ||||
[Obsolete("DefaultChannelId is deprecated, use GetDefaultChannelAsync")] | |||||
public ulong DefaultChannelId => Id; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); | public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -133,16 +126,12 @@ namespace Discord.Rest | |||||
internal void Update(Model model) | internal void Update(Model model) | ||||
{ | { | ||||
AFKChannelId = model.AFKChannelId; | AFKChannelId = model.AFKChannelId; | ||||
if (model.EmbedChannelId.IsSpecified) | |||||
EmbedChannelId = model.EmbedChannelId.Value; | |||||
if (model.WidgetChannelId.IsSpecified) | if (model.WidgetChannelId.IsSpecified) | ||||
WidgetChannelId = model.WidgetChannelId.Value; | WidgetChannelId = model.WidgetChannelId.Value; | ||||
SystemChannelId = model.SystemChannelId; | SystemChannelId = model.SystemChannelId; | ||||
RulesChannelId = model.RulesChannelId; | RulesChannelId = model.RulesChannelId; | ||||
PublicUpdatesChannelId = model.PublicUpdatesChannelId; | PublicUpdatesChannelId = model.PublicUpdatesChannelId; | ||||
AFKTimeout = model.AFKTimeout; | AFKTimeout = model.AFKTimeout; | ||||
if (model.EmbedEnabled.IsSpecified) | |||||
IsEmbeddable = model.EmbedEnabled.Value; | |||||
if (model.WidgetEnabled.IsSpecified) | if (model.WidgetEnabled.IsSpecified) | ||||
IsWidgetEnabled = model.WidgetEnabled.Value; | IsWidgetEnabled = model.WidgetEnabled.Value; | ||||
IconId = model.Icon; | IconId = model.Icon; | ||||
@@ -200,11 +189,6 @@ namespace Discord.Rest | |||||
Available = true; | Available = true; | ||||
} | } | ||||
internal void Update(EmbedModel model) | |||||
{ | |||||
EmbedChannelId = model.ChannelId; | |||||
IsEmbeddable = model.Enabled; | |||||
} | |||||
internal void Update(WidgetModel model) | internal void Update(WidgetModel model) | ||||
{ | { | ||||
WidgetChannelId = model.ChannelId; | WidgetChannelId = model.ChannelId; | ||||
@@ -241,15 +225,6 @@ namespace Discord.Rest | |||||
Update(model); | Update(model); | ||||
} | } | ||||
/// <inheritdoc /> | |||||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||||
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")] | |||||
public async Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null) | |||||
{ | |||||
var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); | |||||
Update(model); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | /// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | ||||
public async Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null) | public async Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null) | ||||
@@ -463,23 +438,6 @@ namespace Discord.Rest | |||||
.FirstOrDefault(); | .FirstOrDefault(); | ||||
} | } | ||||
/// <summary> | |||||
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. | |||||
/// </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 the embed channel set | |||||
/// within the server's widget settings; <see langword="null"/> if none is set. | |||||
/// </returns> | |||||
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")] | |||||
public async Task<RestGuildChannel> GetEmbedChannelAsync(RequestOptions options = null) | |||||
{ | |||||
var embedId = EmbedChannelId; | |||||
if (embedId.HasValue) | |||||
return await GuildHelper.GetChannelAsync(this, Discord, embedId.Value, options).ConfigureAwait(false); | |||||
return null; | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. | /// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. | ||||
/// </summary> | /// </summary> | ||||
@@ -937,15 +895,6 @@ namespace Discord.Rest | |||||
return null; | return null; | ||||
} | } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")] | |||||
async Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) | |||||
{ | |||||
if (mode == CacheMode.AllowDownload) | |||||
return await GetEmbedChannelAsync(options).ConfigureAwait(false); | |||||
else | |||||
return null; | |||||
} | |||||
/// <inheritdoc /> | |||||
async Task<IGuildChannel> IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options) | async Task<IGuildChannel> IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options) | ||||
{ | { | ||||
if (mode == CacheMode.AllowDownload) | if (mode == CacheMode.AllowDownload) | ||||
@@ -1,25 +0,0 @@ | |||||
using System.Diagnostics; | |||||
using Model = Discord.API.GuildEmbed; | |||||
namespace Discord.Rest | |||||
{ | |||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
public struct RestGuildEmbed | |||||
{ | |||||
public bool IsEnabled { get; private set; } | |||||
public ulong? ChannelId { get; private set; } | |||||
internal RestGuildEmbed(bool isEnabled, ulong? channelId) | |||||
{ | |||||
ChannelId = channelId; | |||||
IsEnabled = isEnabled; | |||||
} | |||||
internal static RestGuildEmbed Create(Model model) | |||||
{ | |||||
return new RestGuildEmbed(model.Enabled, model.ChannelId); | |||||
} | |||||
public override string ToString() => ChannelId?.ToString() ?? "Unknown"; | |||||
private string DebuggerDisplay => $"{ChannelId} ({(IsEnabled ? "Enabled" : "Disabled")})"; | |||||
} | |||||
} |
@@ -8,9 +8,6 @@ namespace Discord.Rest | |||||
{ | { | ||||
private long _createdAtTicks; | private long _createdAtTicks; | ||||
/// <inheritdoc /> | |||||
[Obsolete("This property doesn't exist anymore and shouldn't be used.")] | |||||
public bool IsRevoked { get; private set; } | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public bool IsTemporary { get; private set; } | public bool IsTemporary { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -122,13 +122,9 @@ namespace Discord.Rest | |||||
await client.ApiClient.DeleteMessageAsync(channelId, msgId, options).ConfigureAwait(false); | await client.ApiClient.DeleteMessageAsync(channelId, msgId, options).ConfigureAwait(false); | ||||
} | } | ||||
public static async Task SuppressEmbedsAsync(IMessage msg, BaseDiscordClient client, bool suppress, RequestOptions options) | |||||
public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) | |||||
{ | { | ||||
var apiArgs = new API.Rest.SuppressEmbedParams | |||||
{ | |||||
Suppressed = suppress | |||||
}; | |||||
await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | |||||
await client.ApiClient.AddReactionAsync(channelId, messageId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); | |||||
} | } | ||||
public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) | public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) | ||||
@@ -164,9 +164,6 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task UnpinAsync(RequestOptions options = null) | public Task UnpinAsync(RequestOptions options = null) | ||||
=> MessageHelper.UnpinAsync(this, Discord, options); | => 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, | 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) | 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 System.Threading.Tasks; | ||||
using Model = Discord.API.Role; | using Model = Discord.API.Role; | ||||
using BulkParams = Discord.API.Rest.ModifyGuildRolesParams; | using BulkParams = Discord.API.Rest.ModifyGuildRolesParams; | ||||
@@ -24,7 +24,7 @@ namespace Discord.Rest | |||||
Hoist = args.Hoist, | Hoist = args.Hoist, | ||||
Mentionable = args.Mentionable, | Mentionable = args.Mentionable, | ||||
Name = args.Name, | 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); | var model = await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, apiArgs, options).ConfigureAwait(false); | ||||
@@ -79,13 +79,13 @@ namespace Discord.Rest | |||||
} | } | ||||
/// <summary> | /// <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> | /// </summary> | ||||
/// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
/// <returns> | /// <returns> | ||||
/// A task that represents the asynchronous get operation. The task result contains a rest DM channel where the user is the recipient. | /// A task that represents the asynchronous get operation. The task result contains a rest DM channel where the user is the recipient. | ||||
/// </returns> | /// </returns> | ||||
public Task<RestDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null) | |||||
public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | |||||
=> UserHelper.CreateDMChannelAsync(this, Discord, options); | => UserHelper.CreateDMChannelAsync(this, Discord, options); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -107,7 +107,7 @@ namespace Discord.Rest | |||||
//IUser | //IUser | ||||
/// <inheritdoc /> | /// <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 | //Enums | ||||
if (type == typeof(PermissionTarget)) | |||||
return PermissionTargetConverter.Instance; | |||||
if (type == typeof(UserStatus)) | if (type == typeof(UserStatus)) | ||||
return UserStatusConverter.Instance; | return UserStatusConverter.Instance; | ||||
if (type == typeof(EmbedType)) | 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; } | public Optional<int[]> ShardingParams { get; set; } | ||||
[JsonProperty("presence")] | [JsonProperty("presence")] | ||||
public Optional<StatusUpdateParams> Presence { get; set; } | public Optional<StatusUpdateParams> Presence { get; set; } | ||||
[JsonProperty("guild_subscriptions")] | |||||
public Optional<bool> GuildSubscriptions { get; set; } | |||||
[JsonProperty("intents")] | [JsonProperty("intents")] | ||||
public Optional<int> Intents { get; set; } | public Optional<int> Intents { get; set; } | ||||
} | } | ||||
@@ -10,6 +10,8 @@ namespace Discord.API.Gateway | |||||
public ulong MessageId { get; set; } | public ulong MessageId { get; set; } | ||||
[JsonProperty("channel_id")] | [JsonProperty("channel_id")] | ||||
public ulong ChannelId { get; set; } | public ulong ChannelId { get; set; } | ||||
[JsonProperty("guild_id")] | |||||
public Optional<ulong> GuildId { get; set; } | |||||
[JsonProperty("emoji")] | [JsonProperty("emoji")] | ||||
public Emoji Emoji { get; set; } | public Emoji Emoji { get; set; } | ||||
[JsonProperty("member")] | [JsonProperty("member")] | ||||
@@ -124,11 +124,11 @@ namespace Discord.WebSocket | |||||
/// <code language="cs" region="MessageDeleted" | /// <code language="cs" region="MessageDeleted" | ||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs" /> | /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs" /> | ||||
/// </example> | /// </example> | ||||
public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted { | |||||
public event Func<Cacheable<IMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task> MessageDeleted { | |||||
add { _messageDeletedEvent.Add(value); } | add { _messageDeletedEvent.Add(value); } | ||||
remove { _messageDeletedEvent.Remove(value); } | remove { _messageDeletedEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>(); | |||||
internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task>>(); | |||||
/// <summary> Fired when multiple messages are bulk deleted. </summary> | /// <summary> Fired when multiple messages are bulk deleted. </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// <note> | /// <note> | ||||
@@ -155,12 +155,12 @@ namespace Discord.WebSocket | |||||
/// <see cref="ISocketMessageChannel"/> parameter. | /// <see cref="ISocketMessageChannel"/> parameter. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
public event Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, ISocketMessageChannel, Task> MessagesBulkDeleted | |||||
public event Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, Cacheable<IMessageChannel, ulong>, Task> MessagesBulkDeleted | |||||
{ | { | ||||
add { _messagesBulkDeletedEvent.Add(value); } | add { _messagesBulkDeletedEvent.Add(value); } | ||||
remove { _messagesBulkDeletedEvent.Remove(value); } | remove { _messagesBulkDeletedEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, ISocketMessageChannel, Task>> _messagesBulkDeletedEvent = new AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, ISocketMessageChannel, Task>>(); | |||||
internal readonly AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, Cacheable<IMessageChannel, ulong>, Task>> _messagesBulkDeletedEvent = new AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, Cacheable<IMessageChannel, ulong>, Task>>(); | |||||
/// <summary> Fired when a message is updated. </summary> | /// <summary> Fired when a message is updated. </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// <para> | /// <para> | ||||
@@ -217,23 +217,23 @@ namespace Discord.WebSocket | |||||
/// <code language="cs" region="ReactionAdded" | /// <code language="cs" region="ReactionAdded" | ||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | /// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | ||||
/// </example> | /// </example> | ||||
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { | |||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task> ReactionAdded { | |||||
add { _reactionAddedEvent.Add(value); } | add { _reactionAddedEvent.Add(value); } | ||||
remove { _reactionAddedEvent.Remove(value); } | remove { _reactionAddedEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>>(); | |||||
/// <summary> Fired when a reaction is removed from a message. </summary> | /// <summary> Fired when a reaction is removed from a message. </summary> | ||||
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { | |||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task> ReactionRemoved { | |||||
add { _reactionRemovedEvent.Add(value); } | add { _reactionRemovedEvent.Add(value); } | ||||
remove { _reactionRemovedEvent.Remove(value); } | remove { _reactionRemovedEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>>(); | |||||
/// <summary> Fired when all reactions to a message are cleared. </summary> | /// <summary> Fired when all reactions to a message are cleared. </summary> | ||||
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared { | |||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task> ReactionsCleared { | |||||
add { _reactionsClearedEvent.Add(value); } | add { _reactionsClearedEvent.Add(value); } | ||||
remove { _reactionsClearedEvent.Remove(value); } | remove { _reactionsClearedEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>(); | |||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task>>(); | |||||
/// <summary> | /// <summary> | ||||
/// Fired when all reactions to a message with a specific emote are removed. | /// Fired when all reactions to a message with a specific emote are removed. | ||||
/// </summary> | /// </summary> | ||||
@@ -250,12 +250,12 @@ namespace Discord.WebSocket | |||||
/// The emoji that all reactions had and were removed will be passed into the <see cref="IEmote"/> parameter. | /// The emoji that all reactions had and were removed will be passed into the <see cref="IEmote"/> parameter. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, IEmote, Task> ReactionsRemovedForEmote | |||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, IEmote, Task> ReactionsRemovedForEmote | |||||
{ | { | ||||
add { _reactionsRemovedForEmoteEvent.Add(value); } | add { _reactionsRemovedForEmoteEvent.Add(value); } | ||||
remove { _reactionsRemovedForEmoteEvent.Remove(value); } | remove { _reactionsRemovedForEmoteEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, IEmote, Task>>(); | |||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, IEmote, Task>>(); | |||||
//Roles | //Roles | ||||
/// <summary> Fired when a role is created. </summary> | /// <summary> Fired when a role is created. </summary> | ||||
@@ -347,11 +347,11 @@ namespace Discord.WebSocket | |||||
} | } | ||||
internal readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>(); | internal readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>(); | ||||
/// <summary> Fired when a guild member is updated, or a member presence is updated. </summary> | /// <summary> Fired when a guild member is updated, or a member presence is updated. </summary> | ||||
public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated { | |||||
public event Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task> GuildMemberUpdated { | |||||
add { _guildMemberUpdatedEvent.Add(value); } | add { _guildMemberUpdatedEvent.Add(value); } | ||||
remove { _guildMemberUpdatedEvent.Remove(value); } | remove { _guildMemberUpdatedEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>(); | |||||
internal readonly AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task>>(); | |||||
/// <summary> Fired when a user joins, leaves, or moves voice channels. </summary> | /// <summary> Fired when a user joins, leaves, or moves voice channels. </summary> | ||||
public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated { | public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated { | ||||
add { _userVoiceStateUpdatedEvent.Add(value); } | add { _userVoiceStateUpdatedEvent.Add(value); } | ||||
@@ -372,11 +372,11 @@ namespace Discord.WebSocket | |||||
} | } | ||||
internal readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>(); | internal readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>(); | ||||
/// <summary> Fired when a user starts typing. </summary> | /// <summary> Fired when a user starts typing. </summary> | ||||
public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping { | |||||
public event Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task> UserIsTyping { | |||||
add { _userIsTypingEvent.Add(value); } | add { _userIsTypingEvent.Add(value); } | ||||
remove { _userIsTypingEvent.Remove(value); } | remove { _userIsTypingEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>(); | |||||
internal readonly AsyncEvent<Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task>> _userIsTypingEvent = new AsyncEvent<Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task>>(); | |||||
/// <summary> Fired when a user joins a group channel. </summary> | /// <summary> Fired when a user joins a group channel. </summary> | ||||
public event Func<SocketGroupUser, Task> RecipientAdded { | public event Func<SocketGroupUser, Task> RecipientAdded { | ||||
add { _recipientAddedEvent.Add(value); } | add { _recipientAddedEvent.Add(value); } | ||||
@@ -47,7 +47,7 @@ namespace Discord.WebSocket | |||||
/// <summary> | /// <summary> | ||||
/// Gets the current logged-in user. | /// Gets the current logged-in user. | ||||
/// </summary> | /// </summary> | ||||
public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } | |||||
public virtual new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets a collection of guilds that the user is currently in. | /// Gets a collection of guilds that the user is currently in. | ||||
/// </summary> | /// </summary> | ||||
@@ -70,20 +70,11 @@ namespace Discord.WebSocket | |||||
/// A read-only collection of private channels that the user currently partakes in. | /// A read-only collection of private channels that the user currently partakes in. | ||||
/// </returns> | /// </returns> | ||||
public abstract IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels { get; } | 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) | internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | ||||
: base(config, client) => BaseConfig = config; | : base(config, client) => BaseConfig = config; | ||||
private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
=> new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | ||||
rateLimitPrecision: config.RateLimitPrecision, | |||||
useSystemClock: config.UseSystemClock); | useSystemClock: config.UseSystemClock); | ||||
/// <summary> | /// <summary> | ||||
@@ -164,16 +155,6 @@ namespace Discord.WebSocket | |||||
/// </returns> | /// </returns> | ||||
public abstract SocketGuild GetGuild(ulong id); | public abstract SocketGuild GetGuild(ulong id); | ||||
/// <summary> | /// <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. | /// Gets all voice regions. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="options">The options to be used when sending the request.</param> | /// <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)); | => Task.FromResult<IUser>(GetUser(username, discriminator)); | ||||
/// <inheritdoc /> | /// <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 /> | /// <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,14 +36,13 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); | public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); | ||||
public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | 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> | /// <summary> | ||||
/// Provides access to a REST-only client with a shared state from this client. | /// Provides access to a REST-only client with a shared state from this client. | ||||
/// </summary> | /// </summary> | ||||
public override DiscordSocketRestClient Rest => _shards[0].Rest; | |||||
public override DiscordSocketRestClient Rest => _shards?[0].Rest; | |||||
public override SocketSelfUser CurrentUser { get => _shards?.FirstOrDefault(x => x.CurrentUser != null)?.CurrentUser; protected set => throw new InvalidOperationException(); } | |||||
/// <summary> Creates a new REST/WebSocket Discord client. </summary> | /// <summary> Creates a new REST/WebSocket Discord client. </summary> | ||||
public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } | public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } | ||||
@@ -91,8 +90,7 @@ namespace Discord.WebSocket | |||||
} | } | ||||
} | } | ||||
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | 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) | internal async Task AcquireIdentifyLockAsync(int shardId, CancellationToken token) | ||||
{ | { | ||||
@@ -264,11 +262,6 @@ namespace Discord.WebSocket | |||||
return null; | return null; | ||||
} | } | ||||
/// <inheritdoc /> | |||||
[Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||||
public override RestVoiceRegion GetVoiceRegion(string id) | |||||
=> _shards[0].GetVoiceRegion(id); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | ||||
{ | { | ||||
@@ -339,14 +332,6 @@ namespace Discord.WebSocket | |||||
} | } | ||||
return Task.Delay(0); | return Task.Delay(0); | ||||
}; | }; | ||||
if (isPrimary) | |||||
{ | |||||
client.Ready += () => | |||||
{ | |||||
CurrentUser = client.CurrentUser; | |||||
return Task.Delay(0); | |||||
}; | |||||
} | |||||
client.Connected += () => _shardConnectedEvent.InvokeAsync(client); | client.Connected += () => _shardConnectedEvent.InvokeAsync(client); | ||||
client.Disconnected += (exception) => _shardDisconnectedEvent.InvokeAsync(exception, client); | client.Disconnected += (exception) => _shardDisconnectedEvent.InvokeAsync(exception, client); | ||||
@@ -432,11 +417,15 @@ namespace Discord.WebSocket | |||||
=> Task.FromResult<IUser>(GetUser(username, discriminator)); | => Task.FromResult<IUser>(GetUser(username, discriminator)); | ||||
/// <inheritdoc /> | /// <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 /> | /// <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) | internal override void Dispose(bool disposing) | ||||
{ | { | ||||
@@ -40,9 +40,8 @@ namespace Discord.API | |||||
public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | ||||
string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, | string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, | ||||
RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, | |||||
bool useSystemClock = true) | bool useSystemClock = true) | ||||
: base(restClientProvider, userAgent, defaultRetryMode, serializer, rateLimitPrecision, useSystemClock) | |||||
: base(restClientProvider, userAgent, defaultRetryMode, serializer, useSystemClock) | |||||
{ | { | ||||
_gatewayUrl = url; | _gatewayUrl = url; | ||||
if (url != null) | if (url != null) | ||||
@@ -216,7 +215,7 @@ namespace Discord.API | |||||
await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | 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); | options = RequestOptions.CreateOrClone(options); | ||||
var props = new Dictionary<string, string> | var props = new Dictionary<string, string> | ||||
@@ -234,10 +233,7 @@ namespace Discord.API | |||||
options.BucketId = GatewayBucket.Get(GatewayBucketType.Identify).Id; | 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) | if (presence.HasValue) | ||||
{ | { | ||||
@@ -43,8 +43,7 @@ namespace Discord.WebSocket | |||||
private DateTimeOffset? _statusSince; | private DateTimeOffset? _statusSince; | ||||
private RestApplication _applicationInfo; | private RestApplication _applicationInfo; | ||||
private bool _isDisposed; | private bool _isDisposed; | ||||
private bool _guildSubscriptions; | |||||
private GatewayIntents? _gatewayIntents; | |||||
private GatewayIntents _gatewayIntents; | |||||
/// <summary> | /// <summary> | ||||
/// Provides access to a REST-only client with a shared state from this client. | /// Provides access to a REST-only client with a shared state from this client. | ||||
@@ -72,7 +71,6 @@ namespace Discord.WebSocket | |||||
internal WebSocketProvider WebSocketProvider { get; private set; } | internal WebSocketProvider WebSocketProvider { get; private set; } | ||||
internal bool AlwaysDownloadUsers { get; private set; } | internal bool AlwaysDownloadUsers { get; private set; } | ||||
internal int? HandlerTimeout { get; private set; } | internal int? HandlerTimeout { get; private set; } | ||||
internal bool? ExclusiveBulkDelete { get; private set; } | |||||
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -109,9 +107,6 @@ namespace Discord.WebSocket | |||||
/// </returns> | /// </returns> | ||||
public IReadOnlyCollection<SocketGroupChannel> GroupChannels | public IReadOnlyCollection<SocketGroupChannel> GroupChannels | ||||
=> State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); | => 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> | /// <summary> | ||||
/// Initializes a new REST/WebSocket-based Discord client. | /// Initializes a new REST/WebSocket-based Discord client. | ||||
@@ -136,11 +131,9 @@ namespace Discord.WebSocket | |||||
WebSocketProvider = config.WebSocketProvider; | WebSocketProvider = config.WebSocketProvider; | ||||
AlwaysDownloadUsers = config.AlwaysDownloadUsers; | AlwaysDownloadUsers = config.AlwaysDownloadUsers; | ||||
HandlerTimeout = config.HandlerTimeout; | HandlerTimeout = config.HandlerTimeout; | ||||
ExclusiveBulkDelete = config.ExclusiveBulkDelete; | |||||
State = new ClientState(0, 0); | State = new ClientState(0, 0); | ||||
Rest = new DiscordSocketRestClient(config, ApiClient); | Rest = new DiscordSocketRestClient(config, ApiClient); | ||||
_heartbeatTimes = new ConcurrentQueue<long>(); | _heartbeatTimes = new ConcurrentQueue<long>(); | ||||
_guildSubscriptions = config.GuildSubscriptions; | |||||
_gatewayIntents = config.GatewayIntents; | _gatewayIntents = config.GatewayIntents; | ||||
_stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
@@ -182,8 +175,7 @@ namespace Discord.WebSocket | |||||
_largeGuilds = new ConcurrentQueue<ulong>(); | _largeGuilds = new ConcurrentQueue<ulong>(); | ||||
} | } | ||||
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | 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 /> | /// <inheritdoc /> | ||||
internal override void Dispose(bool disposing) | internal override void Dispose(bool disposing) | ||||
{ | { | ||||
@@ -201,11 +193,6 @@ namespace Discord.WebSocket | |||||
base.Dispose(disposing); | base.Dispose(disposing); | ||||
} | } | ||||
/// <inheritdoc /> | |||||
internal override async Task OnLoginAsync(TokenType tokenType, string token) | |||||
{ | |||||
await Rest.OnLoginAsync(tokenType, token); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
internal override async Task OnLogoutAsync() | internal override async Task OnLogoutAsync() | ||||
{ | { | ||||
@@ -243,7 +230,7 @@ namespace Discord.WebSocket | |||||
else | else | ||||
{ | { | ||||
await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); | 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 | finally | ||||
@@ -302,13 +289,51 @@ namespace Discord.WebSocket | |||||
public override SocketChannel GetChannel(ulong id) | public override SocketChannel GetChannel(ulong id) | ||||
=> State.GetChannel(id); | => State.GetChannel(id); | ||||
/// <summary> | /// <summary> | ||||
/// Gets a generic channel from the cache or does a rest request if unavailable. | |||||
/// </summary> | |||||
/// <example> | |||||
/// <code language="cs" title="Example method"> | |||||
/// var channel = await _client.GetChannelAsync(381889909113225237); | |||||
/// if (channel != null && channel is IMessageChannel msgChannel) | |||||
/// { | |||||
/// await msgChannel.SendMessageAsync($"{msgChannel} is created at {msgChannel.CreatedAt}"); | |||||
/// } | |||||
/// </code> | |||||
/// </example> | |||||
/// <param name="id">The snowflake identifier of the channel (e.g. `381889909113225237`).</param> | |||||
/// <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 the channel associated | |||||
/// with the snowflake identifier; <c>null</c> when the channel cannot be found. | |||||
/// </returns> | |||||
public async ValueTask<IChannel> GetChannelAsync(ulong id, RequestOptions options = null) | |||||
=> GetChannel(id) ?? (IChannel)await ClientHelper.GetChannelAsync(this, id, options).ConfigureAwait(false); | |||||
/// <summary> | |||||
/// Gets a user from the cache or does a rest request if unavailable. | |||||
/// </summary> | |||||
/// <example> | |||||
/// <code language="cs" title="Example method"> | |||||
/// var user = await _client.GetUserAsync(168693960628371456); | |||||
/// if (user != null) | |||||
/// Console.WriteLine($"{user} is created at {user.CreatedAt}."; | |||||
/// </code> | |||||
/// </example> | |||||
/// <param name="id">The snowflake identifier of the user (e.g. `168693960628371456`).</param> | |||||
/// <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 the user associated with | |||||
/// the snowflake identifier; <c>null</c> if the user is not found. | |||||
/// </returns> | |||||
public async ValueTask<IUser> GetUserAsync(ulong id, RequestOptions options = null) | |||||
=> await ClientHelper.GetUserAsync(this, id, options).ConfigureAwait(false); | |||||
/// <summary> | |||||
/// Clears all cached channels from the client. | /// Clears all cached channels from the client. | ||||
/// </summary> | /// </summary> | ||||
public void PurgeChannelCache() => State.PurgeAllChannels(); | public void PurgeChannelCache() => State.PurgeAllChannels(); | ||||
/// <summary> | /// <summary> | ||||
/// Clears cached DM channels from the client. | /// Clears cached DM channels from the client. | ||||
/// </summary> | /// </summary> | ||||
public void PurgeDMChannelCache() => State.PurgeDMChannels(); | |||||
public void PurgeDMChannelCache() => RemoveDMChannels(); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override SocketUser GetUser(ulong id) | public override SocketUser GetUser(ulong id) | ||||
@@ -322,12 +347,11 @@ namespace Discord.WebSocket | |||||
public void PurgeUserCache() => State.PurgeUsers(); | public void PurgeUserCache() => State.PurgeUsers(); | ||||
internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) | internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) | ||||
{ | { | ||||
return state.GetOrAddUser(model.Id, x => | |||||
{ | |||||
var user = SocketGlobalUser.Create(this, state, model); | |||||
user.GlobalUser.AddRef(); | |||||
return user; | |||||
}); | |||||
return state.GetOrAddUser(model.Id, x => SocketGlobalUser.Create(this, state, model)); | |||||
} | |||||
internal SocketUser GetOrCreateTemporaryUser(ClientState state, Discord.API.User model) | |||||
{ | |||||
return state.GetUser(model.Id) ?? (SocketUser)SocketUnknownUser.Create(this, state, model); | |||||
} | } | ||||
internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, Discord.API.User model) | internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, Discord.API.User model) | ||||
{ | { | ||||
@@ -335,18 +359,13 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
var user = SocketGlobalUser.Create(this, state, model); | var user = SocketGlobalUser.Create(this, state, model); | ||||
user.GlobalUser.AddRef(); | user.GlobalUser.AddRef(); | ||||
user.Presence = new SocketPresence(UserStatus.Online, null, null, null); | |||||
user.Presence = new SocketPresence(UserStatus.Online, null, null); | |||||
return user; | return user; | ||||
}); | }); | ||||
} | } | ||||
internal void RemoveUser(ulong id) | internal void RemoveUser(ulong id) | ||||
=> State.RemoveUser(id); | => State.RemoveUser(id); | ||||
/// <inheritdoc /> | |||||
[Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||||
public override RestVoiceRegion GetVoiceRegion(string id) | |||||
=> GetVoiceRegionAsync(id).GetAwaiter().GetResult(); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | ||||
{ | { | ||||
@@ -469,7 +488,8 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
if (CurrentUser == null) | if (CurrentUser == null) | ||||
return; | 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); | var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null); | ||||
@@ -564,7 +584,7 @@ namespace Discord.WebSocket | |||||
await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false); | await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false); | ||||
try | 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 | finally | ||||
{ | { | ||||
@@ -572,7 +592,7 @@ namespace Discord.WebSocket | |||||
} | } | ||||
} | } | ||||
else | 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; | break; | ||||
case GatewayOpCode.Reconnect: | case GatewayOpCode.Reconnect: | ||||
@@ -595,7 +615,8 @@ namespace Discord.WebSocket | |||||
var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | ||||
var currentUser = SocketSelfUser.Create(this, state, data.User); | 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; | ApiClient.CurrentUserId = currentUser.Id; | ||||
int unavailableGuilds = 0; | int unavailableGuilds = 0; | ||||
for (int i = 0; i < data.Guilds.Length; i++) | for (int i = 0; i < data.Guilds.Length; i++) | ||||
@@ -749,7 +770,8 @@ namespace Discord.WebSocket | |||||
break; | break; | ||||
case "GUILD_SYNC": | case "GUILD_SYNC": | ||||
{ | { | ||||
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); | |||||
await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_SYNC)").ConfigureAwait(false); | |||||
/*await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); //TODO remove? userbot related | |||||
var data = (payload as JToken).ToObject<GuildSyncEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildSyncEvent>(_serializer); | ||||
var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
if (guild != null) | if (guild != null) | ||||
@@ -766,7 +788,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); | await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); | ||||
return; | return; | ||||
} | |||||
}*/ | |||||
} | } | ||||
break; | break; | ||||
case "GUILD_DELETE": | case "GUILD_DELETE": | ||||
@@ -966,15 +988,15 @@ namespace Discord.WebSocket | |||||
var before = user.Clone(); | var before = user.Clone(); | ||||
user.Update(State, data); | user.Update(State, data); | ||||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); | |||||
var cacheableBefore = new Cacheable<SocketGuildUser, ulong>(before, user.Id, true, () => Task.FromResult((SocketGuildUser)null)); | |||||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
if (!guild.HasAllMembers) | |||||
await IncompleteGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); | |||||
else | |||||
await UnknownGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); | |||||
return; | |||||
user = guild.AddOrUpdateUser(data); | |||||
var cacheableBefore = new Cacheable<SocketGuildUser, ulong>(null, user.Id, false, () => Task.FromResult((SocketGuildUser)null)); | |||||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
else | else | ||||
@@ -1237,56 +1259,63 @@ namespace Discord.WebSocket | |||||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | ||||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||||
var channel = 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 | 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 | 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; | break; | ||||
case "MESSAGE_UPDATE": | case "MESSAGE_UPDATE": | ||||
@@ -1294,52 +1323,85 @@ namespace Discord.WebSocket | |||||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | ||||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||||
var channel = 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; | |||||
} | |||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
SocketMessage before = null, after = null; | |||||
SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); | |||||
bool isCached = cachedMsg != null; | |||||
if (isCached) | |||||
SocketMessage before = null, after = null; | |||||
SocketMessage cachedMsg = channel?.GetCachedMessage(data.Id); | |||||
bool isCached = cachedMsg != null; | |||||
if (isCached) | |||||
{ | |||||
before = cachedMsg.Clone(); | |||||
cachedMsg.Update(State, data); | |||||
after = cachedMsg; | |||||
} | |||||
else | |||||
{ | |||||
//Edited message isnt in cache, create a detached one | |||||
SocketUser author; | |||||
if (data.Author.IsSpecified) | |||||
{ | { | ||||
before = cachedMsg.Clone(); | |||||
cachedMsg.Update(State, data); | |||||
after = cachedMsg; | |||||
if (guild != null) | |||||
{ | |||||
if (data.WebhookId.IsSpecified) | |||||
author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); | |||||
else | |||||
author = guild.GetUser(data.Author.Value.Id); | |||||
} | |||||
else | |||||
author = (channel as SocketChannel)?.GetUser(data.Author.Value.Id); | |||||
if (author == null) | |||||
{ | |||||
if (guild != null) | |||||
{ | |||||
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 | |||||
} | |||||
else if (channel is SocketGroupChannel groupChannel) | |||||
author = groupChannel.GetOrAddUser(data.Author.Value); | |||||
} | |||||
} | } | ||||
else | else | ||||
// Message author wasn't specified in the payload, so create a completely anonymous unknown user | |||||
author = new SocketUnknownUser(this, id: 0); | |||||
if (channel == null) | |||||
{ | { | ||||
//Edited message isnt in cache, create a detached one | |||||
SocketUser author; | |||||
if (data.Author.IsSpecified) | |||||
if (!data.GuildId.IsSpecified) // assume it is a DM | |||||
{ | { | ||||
if (guild != null) | |||||
author = guild.GetUser(data.Author.Value.Id); | |||||
if (data.Author.IsSpecified) | |||||
{ | |||||
var dmChannel = CreateDMChannel(data.ChannelId, data.Author.Value, State); | |||||
channel = dmChannel; | |||||
author = dmChannel.Recipient; | |||||
} | |||||
else | else | ||||
author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||||
if (author == null) | |||||
author = SocketUnknownUser.Create(this, State, data.Author.Value); | |||||
channel = CreateDMChannel(data.ChannelId, author, State); | |||||
} | } | ||||
else | else | ||||
// Message author wasn't specified in the payload, so create a completely anonymous unknown user | |||||
author = new SocketUnknownUser(this, id: 0); | |||||
after = SocketMessage.Create(this, State, author, channel, data); | |||||
{ | |||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
} | } | ||||
var cacheableBefore = new Cacheable<IMessage, ulong>(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); | |||||
await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); | |||||
} | |||||
else | |||||
{ | |||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
return; | |||||
after = SocketMessage.Create(this, State, author, channel, data); | |||||
} | } | ||||
var cacheableBefore = new Cacheable<IMessage, ulong>(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); | |||||
await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); | |||||
} | } | ||||
break; | break; | ||||
case "MESSAGE_DELETE": | case "MESSAGE_DELETE": | ||||
@@ -1347,26 +1409,22 @@ namespace Discord.WebSocket | |||||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | ||||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||||
{ | |||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
if (!(guild?.IsSynced ?? true)) | |||||
{ | |||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); | |||||
bool isCached = msg != null; | |||||
var cacheable = new Cacheable<IMessage, ulong>(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); | |||||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); | |||||
} | |||||
else | |||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
if (!(guild?.IsSynced ?? true)) | |||||
{ | { | ||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
return; | return; | ||||
} | } | ||||
SocketMessage msg = null; | |||||
if (channel != null) | |||||
msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); | |||||
var cacheableMsg = new Cacheable<IMessage, ulong>(msg, data.Id, msg != null, () => Task.FromResult((IMessage)null)); | |||||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||||
await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheableMsg, cacheableChannel).ConfigureAwait(false); | |||||
} | } | ||||
break; | break; | ||||
case "MESSAGE_REACTION_ADD": | case "MESSAGE_REACTION_ADD": | ||||
@@ -1374,40 +1432,43 @@ namespace Discord.WebSocket | |||||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | ||||
var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | ||||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||||
{ | |||||
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||||
bool isCached = cachedMsg != null; | |||||
var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); | |||||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
var optionalMsg = !isCached | |||||
? Optional.Create<SocketUserMessage>() | |||||
: Optional.Create(cachedMsg); | |||||
if (data.Member.IsSpecified) | |||||
{ | |||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
if (guild != null) | |||||
user = guild.AddOrUpdateUser(data.Member.Value); | |||||
} | |||||
var optionalUser = user is null | |||||
? Optional.Create<IUser>() | |||||
: Optional.Create(user); | |||||
var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||||
bool isMsgCached = cachedMsg != null; | |||||
IUser user = null; | |||||
if (channel != null) | |||||
user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); | |||||
var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); | |||||
var cacheable = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); | |||||
var optionalMsg = !isMsgCached | |||||
? Optional.Create<SocketUserMessage>() | |||||
: Optional.Create(cachedMsg); | |||||
cachedMsg?.AddReaction(reaction); | |||||
if (data.Member.IsSpecified) | |||||
{ | |||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheable, channel, reaction).ConfigureAwait(false); | |||||
if (guild != null) | |||||
user = guild.AddOrUpdateUser(data.Member.Value); | |||||
} | } | ||||
else | else | ||||
user = GetUser(data.UserId); | |||||
var optionalUser = user is null | |||||
? Optional.Create<IUser>() | |||||
: Optional.Create(user); | |||||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||||
var cacheableMsg = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isMsgCached, async () => | |||||
{ | { | ||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); | |||||
return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; | |||||
}); | |||||
var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); | |||||
cachedMsg?.AddReaction(reaction); | |||||
await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); | |||||
} | } | ||||
break; | break; | ||||
case "MESSAGE_REACTION_REMOVE": | case "MESSAGE_REACTION_REMOVE": | ||||
@@ -1415,32 +1476,35 @@ namespace Discord.WebSocket | |||||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); | ||||
var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | ||||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||||
{ | |||||
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||||
bool isCached = cachedMsg != null; | |||||
var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); | |||||
var optionalMsg = !isCached | |||||
? Optional.Create<SocketUserMessage>() | |||||
: Optional.Create(cachedMsg); | |||||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
var optionalUser = user is null | |||||
? Optional.Create<IUser>() | |||||
: Optional.Create(user); | |||||
var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||||
bool isMsgCached = cachedMsg != null; | |||||
IUser user = null; | |||||
if (channel != null) | |||||
user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); | |||||
else if (!data.GuildId.IsSpecified) | |||||
user = GetUser(data.UserId); | |||||
var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); | |||||
var cacheable = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); | |||||
var optionalMsg = !isMsgCached | |||||
? Optional.Create<SocketUserMessage>() | |||||
: Optional.Create(cachedMsg); | |||||
cachedMsg?.RemoveReaction(reaction); | |||||
var optionalUser = user is null | |||||
? Optional.Create<IUser>() | |||||
: Optional.Create(user); | |||||
await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheable, channel, reaction).ConfigureAwait(false); | |||||
} | |||||
else | |||||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||||
var cacheableMsg = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isMsgCached, async () => | |||||
{ | { | ||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); | |||||
return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; | |||||
}); | |||||
var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); | |||||
cachedMsg?.RemoveReaction(reaction); | |||||
await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); | |||||
} | } | ||||
break; | break; | ||||
case "MESSAGE_REACTION_REMOVE_ALL": | case "MESSAGE_REACTION_REMOVE_ALL": | ||||
@@ -1448,21 +1512,20 @@ namespace Discord.WebSocket | |||||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); | ||||
var data = (payload as JToken).ToObject<RemoveAllReactionsEvent>(_serializer); | var data = (payload as JToken).ToObject<RemoveAllReactionsEvent>(_serializer); | ||||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||||
var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||||
bool isMsgCached = cachedMsg != null; | |||||
var cacheableMsg = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isMsgCached, async () => | |||||
{ | { | ||||
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||||
bool isCached = cachedMsg != null; | |||||
var cacheable = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isCached, async () => (await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false)) as IUserMessage); | |||||
var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); | |||||
return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; | |||||
}); | |||||
cachedMsg?.ClearReactions(); | |||||
cachedMsg?.ClearReactions(); | |||||
await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheable, channel).ConfigureAwait(false); | |||||
} | |||||
else | |||||
{ | |||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheableMsg, cacheableChannel).ConfigureAwait(false); | |||||
} | } | ||||
break; | break; | ||||
case "MESSAGE_REACTION_REMOVE_EMOJI": | case "MESSAGE_REACTION_REMOVE_EMOJI": | ||||
@@ -1470,70 +1533,55 @@ namespace Discord.WebSocket | |||||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false); | ||||
var data = (payload as JToken).ToObject<API.Gateway.RemoveAllReactionsForEmoteEvent>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.RemoveAllReactionsForEmoteEvent>(_serializer); | ||||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||||
{ | |||||
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||||
bool isCached = cachedMsg != null; | |||||
var optionalMsg = !isCached | |||||
? Optional.Create<SocketUserMessage>() | |||||
: Optional.Create(cachedMsg); | |||||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
var cacheable = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); | |||||
var emote = data.Emoji.ToIEmote(); | |||||
var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||||
bool isMsgCached = cachedMsg != null; | |||||
cachedMsg?.RemoveReactionsForEmote(emote); | |||||
var optionalMsg = !isMsgCached | |||||
? Optional.Create<SocketUserMessage>() | |||||
: Optional.Create(cachedMsg); | |||||
await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false); | |||||
} | |||||
else | |||||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||||
var cacheableMsg = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isMsgCached, async () => | |||||
{ | { | ||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); | |||||
return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; | |||||
}); | |||||
var emote = data.Emoji.ToIEmote(); | |||||
cachedMsg?.RemoveReactionsForEmote(emote); | |||||
await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheableMsg, cacheableChannel, emote).ConfigureAwait(false); | |||||
} | } | ||||
break; | break; | ||||
case "MESSAGE_DELETE_BULK": | case "MESSAGE_DELETE_BULK": | ||||
{ | { | ||||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | ||||
if (!ExclusiveBulkDelete.HasValue) | |||||
{ | |||||
await _gatewayLogger.WarningAsync("A bulk delete event has been received, but the event handling behavior has not been set. " + | |||||
"To suppress this message, set the ExclusiveBulkDelete configuration property. " + | |||||
"This message will appear only once."); | |||||
ExclusiveBulkDelete = false; | |||||
} | |||||
var data = (payload as JToken).ToObject<MessageDeleteBulkEvent>(_serializer); | var data = (payload as JToken).ToObject<MessageDeleteBulkEvent>(_serializer); | ||||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||||
{ | |||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
if (!(guild?.IsSynced ?? true)) | |||||
{ | |||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var cacheableList = new List<Cacheable<IMessage, ulong>>(data.Ids.Length); | |||||
foreach (ulong id in data.Ids) | |||||
{ | |||||
var msg = SocketChannelHelper.RemoveMessage(channel, this, id); | |||||
bool isCached = msg != null; | |||||
var cacheable = new Cacheable<IMessage, ulong>(msg, id, isCached, async () => await channel.GetMessageAsync(id).ConfigureAwait(false)); | |||||
cacheableList.Add(cacheable); | |||||
if (!ExclusiveBulkDelete ?? false) // this shouldn't happen, but we'll play it safe anyways | |||||
await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); | |||||
} | |||||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, channel).ConfigureAwait(false); | |||||
} | |||||
else | |||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
if (!(guild?.IsSynced ?? true)) | |||||
{ | { | ||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
return; | return; | ||||
} | } | ||||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||||
var cacheableList = new List<Cacheable<IMessage, ulong>>(data.Ids.Length); | |||||
foreach (ulong id in data.Ids) | |||||
{ | |||||
SocketMessage msg = null; | |||||
if (channel != null) | |||||
msg = SocketChannelHelper.RemoveMessage(channel, this, id); | |||||
bool isMsgCached = msg != null; | |||||
var cacheableMsg = new Cacheable<IMessage, ulong>(msg, id, isMsgCached, () => Task.FromResult((IMessage)null)); | |||||
cacheableList.Add(cacheableMsg); | |||||
} | |||||
await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, cacheableChannel).ConfigureAwait(false); | |||||
} | } | ||||
break; | break; | ||||
@@ -1579,7 +1627,8 @@ namespace Discord.WebSocket | |||||
var before = user.Clone(); | var before = user.Clone(); | ||||
user.Update(State, data, true); | user.Update(State, data, true); | ||||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); | |||||
var cacheableBefore = new Cacheable<SocketGuildUser, ulong>(before, user.Id, true, () => Task.FromResult(user)); | |||||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -1602,24 +1651,26 @@ namespace Discord.WebSocket | |||||
await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | ||||
var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | ||||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
if (!(guild?.IsSynced ?? true)) | |||||
{ | { | ||||
var guild = (channel as SocketGuildChannel)?.Guild; | |||||
if (!(guild?.IsSynced ?? true)) | |||||
{ | |||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var user = (channel as SocketChannel).GetUser(data.UserId); | |||||
if (user == null) | |||||
{ | |||||
if (guild != null) | |||||
user = guild.AddOrUpdateUser(data.Member); | |||||
} | |||||
if (user != null) | |||||
await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), user, channel).ConfigureAwait(false); | |||||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||||
var user = (channel as SocketChannel)?.GetUser(data.UserId); | |||||
if (user == null) | |||||
{ | |||||
if (guild != null) | |||||
user = guild.AddOrUpdateUser(data.Member); | |||||
} | } | ||||
var cacheableUser = new Cacheable<IUser, ulong>(user, data.UserId, user != null, async () => await GetUserAsync(data.UserId).ConfigureAwait(false)); | |||||
await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), cacheableUser, cacheableChannel).ConfigureAwait(false); | |||||
} | } | ||||
break; | break; | ||||
@@ -1691,7 +1742,7 @@ namespace Discord.WebSocket | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
var groupChannel = State.GetChannel(data.ChannelId.Value) as SocketGroupChannel; | |||||
var groupChannel = GetChannel(data.ChannelId.Value) as SocketGroupChannel; | |||||
if (groupChannel == null) | if (groupChannel == null) | ||||
{ | { | ||||
await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); | await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); | ||||
@@ -1938,24 +1989,33 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
var channel = SocketChannel.CreatePrivate(this, state, model); | var channel = SocketChannel.CreatePrivate(this, state, model); | ||||
state.AddChannel(channel as SocketChannel); | state.AddChannel(channel as SocketChannel); | ||||
if (channel is SocketDMChannel dm) | |||||
dm.Recipient.GlobalUser.DMChannel = dm; | |||||
return channel; | return channel; | ||||
} | } | ||||
internal SocketDMChannel CreateDMChannel(ulong channelId, API.User model, ClientState state) | |||||
{ | |||||
return SocketDMChannel.Create(this, state, channelId, model); | |||||
} | |||||
internal SocketDMChannel CreateDMChannel(ulong channelId, SocketUser user, ClientState state) | |||||
{ | |||||
return new SocketDMChannel(this, channelId, user); | |||||
} | |||||
internal ISocketPrivateChannel RemovePrivateChannel(ulong id) | internal ISocketPrivateChannel RemovePrivateChannel(ulong id) | ||||
{ | { | ||||
var channel = State.RemoveChannel(id) as ISocketPrivateChannel; | var channel = State.RemoveChannel(id) as ISocketPrivateChannel; | ||||
if (channel != null) | if (channel != null) | ||||
{ | { | ||||
if (channel is SocketDMChannel dmChannel) | |||||
dmChannel.Recipient.GlobalUser.DMChannel = null; | |||||
foreach (var recipient in channel.Recipients) | foreach (var recipient in channel.Recipients) | ||||
recipient.GlobalUser.RemoveRef(this); | recipient.GlobalUser.RemoveRef(this); | ||||
} | } | ||||
return channel; | 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) | private async Task GuildAvailableAsync(SocketGuild guild) | ||||
{ | { | ||||
@@ -2111,8 +2171,8 @@ namespace Discord.WebSocket | |||||
=> await GetApplicationInfoAsync().ConfigureAwait(false); | => await GetApplicationInfoAsync().ConfigureAwait(false); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
=> Task.FromResult<IChannel>(GetChannel(id)); | |||||
async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
=> mode == CacheMode.AllowDownload ? await GetChannelAsync(id, options).ConfigureAwait(false) : GetChannel(id); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | ||||
=> Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | ||||
@@ -2142,8 +2202,8 @@ namespace Discord.WebSocket | |||||
=> await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
=> Task.FromResult<IUser>(GetUser(id)); | |||||
async Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||||
=> mode == CacheMode.AllowDownload ? await GetUserAsync(id, options).ConfigureAwait(false) : GetUser(id); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | ||||
=> Task.FromResult<IUser>(GetUser(username, discriminator)); | => Task.FromResult<IUser>(GetUser(username, discriminator)); | ||||
@@ -111,28 +111,6 @@ namespace Discord.WebSocket | |||||
/// </summary> | /// </summary> | ||||
public int? HandlerTimeout { get; set; } = 3000; | public int? HandlerTimeout { get; set; } = 3000; | ||||
/// <summary> | |||||
/// Gets or sets the behavior for <see cref="BaseSocketClient.MessageDeleted"/> on bulk deletes. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// <para> | |||||
/// If <c>true</c>, the <see cref="BaseSocketClient.MessageDeleted"/> event will not be raised for bulk | |||||
/// deletes, and only the <see cref="BaseSocketClient.MessagesBulkDeleted"/> will be raised. If <c>false</c> | |||||
/// , both events will be raised. | |||||
/// </para> | |||||
/// <para> | |||||
/// If unset, both events will be raised, but a warning will be raised the first time a bulk delete event is | |||||
/// received. | |||||
/// </para> | |||||
/// </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> | /// <summary> | ||||
/// Gets or sets the maximum identify concurrency. | /// Gets or sets the maximum identify concurrency. | ||||
/// </summary> | /// </summary> | ||||
@@ -172,14 +150,15 @@ namespace Discord.WebSocket | |||||
private int maxWaitForGuildAvailable = 10000; | private int maxWaitForGuildAvailable = 10000; | ||||
/// <summary> | /// <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> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// For more information, please see | /// For more information, please see | ||||
/// <see href="https://discord.com/developers/docs/topics/gateway#gateway-intents">GatewayIntents</see> | /// <see href="https://discord.com/developers/docs/topics/gateway#gateway-intents">GatewayIntents</see> | ||||
/// on the official Discord API documentation. | /// on the official Discord API documentation. | ||||
/// </remarks> | /// </remarks> | ||||
public GatewayIntents? GatewayIntents { get; set; } | |||||
public GatewayIntents GatewayIntents { get; set; } = GatewayIntents.AllUnprivileged; | |||||
/// <summary> | /// <summary> | ||||
/// Initializes a new instance of the <see cref="DiscordSocketConfig"/> class with the default configuration. | /// Initializes a new instance of the <see cref="DiscordSocketConfig"/> class with the default configuration. | ||||
@@ -16,32 +16,27 @@ namespace Discord.WebSocket | |||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel | public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel | ||||
{ | { | ||||
private readonly MessageCache _messages; | |||||
/// <summary> | /// <summary> | ||||
/// Gets the recipient of the channel. | /// Gets the recipient of the channel. | ||||
/// </summary> | /// </summary> | ||||
public SocketUser Recipient { get; } | public SocketUser Recipient { get; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | |||||
public IReadOnlyCollection<SocketMessage> CachedMessages => ImmutableArray.Create<SocketMessage>(); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a collection that is the current logged-in user and the recipient. | /// Gets a collection that is the current logged-in user and the recipient. | ||||
/// </summary> | /// </summary> | ||||
public new IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | public new IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | ||||
internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketGlobalUser recipient) | |||||
internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketUser recipient) | |||||
: base(discord, id) | : base(discord, id) | ||||
{ | { | ||||
Recipient = recipient; | Recipient = recipient; | ||||
recipient.GlobalUser.AddRef(); | |||||
if (Discord.MessageCacheSize > 0) | |||||
_messages = new MessageCache(Discord); | |||||
} | } | ||||
internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) | 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.GetOrCreateTemporaryUser(state, model.Recipients.Value[0])); | |||||
entity.Update(state, model); | entity.Update(state, model); | ||||
return entity; | return entity; | ||||
} | } | ||||
@@ -49,6 +44,16 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
Recipient.Update(state, model.Recipients.Value[0]); | 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.GetOrCreateTemporaryUser(state, recipient)); | |||||
entity.Update(state, recipient); | |||||
return entity; | |||||
} | |||||
internal void Update(ClientState state, API.User recipient) | |||||
{ | |||||
Recipient.Update(state, recipient); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task CloseAsync(RequestOptions options = null) | public Task CloseAsync(RequestOptions options = null) | ||||
@@ -57,7 +62,7 @@ namespace Discord.WebSocket | |||||
//Messages | //Messages | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public SocketMessage GetCachedMessage(ulong id) | public SocketMessage GetCachedMessage(ulong id) | ||||
=> _messages?.Get(id); | |||||
=> null; | |||||
/// <summary> | /// <summary> | ||||
/// Gets the message associated with the given <paramref name="id"/>. | /// Gets the message associated with the given <paramref name="id"/>. | ||||
/// </summary> | /// </summary> | ||||
@@ -68,10 +73,7 @@ namespace Discord.WebSocket | |||||
/// </returns> | /// </returns> | ||||
public async Task<IMessage> GetMessageAsync(ulong id, RequestOptions options = null) | 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> | /// <summary> | ||||
@@ -87,7 +89,7 @@ namespace Discord.WebSocket | |||||
/// Paged collection of messages. | /// Paged collection of messages. | ||||
/// </returns> | /// </returns> | ||||
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | 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> | /// <summary> | ||||
/// Gets a collection of messages in this channel. | /// Gets a collection of messages in this channel. | ||||
/// </summary> | /// </summary> | ||||
@@ -103,7 +105,7 @@ namespace Discord.WebSocket | |||||
/// Paged collection of messages. | /// Paged collection of messages. | ||||
/// </returns> | /// </returns> | ||||
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | 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> | /// <summary> | ||||
/// Gets a collection of messages in this channel. | /// Gets a collection of messages in this channel. | ||||
/// </summary> | /// </summary> | ||||
@@ -119,16 +121,16 @@ namespace Discord.WebSocket | |||||
/// Paged collection of messages. | /// Paged collection of messages. | ||||
/// </returns> | /// </returns> | ||||
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | 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 /> | /// <inheritdoc /> | ||||
public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | |||||
=> ImmutableArray.Create<SocketMessage>(); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | ||||
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | |||||
=> ImmutableArray.Create<SocketMessage>(); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | 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 /> | /// <inheritdoc /> | ||||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | ||||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | ||||
@@ -164,9 +166,12 @@ namespace Discord.WebSocket | |||||
=> ChannelHelper.EnterTypingState(this, Discord, options); | => ChannelHelper.EnterTypingState(this, Discord, options); | ||||
internal void AddMessage(SocketMessage msg) | internal void AddMessage(SocketMessage msg) | ||||
=> _messages?.Add(msg); | |||||
{ | |||||
} | |||||
internal SocketMessage RemoveMessage(ulong id) | internal SocketMessage RemoveMessage(ulong id) | ||||
=> _messages?.Remove(id); | |||||
{ | |||||
return null; | |||||
} | |||||
//Users | //Users | ||||
/// <summary> | /// <summary> | ||||
@@ -222,13 +227,13 @@ namespace Discord.WebSocket | |||||
} | } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) | 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 /> | /// <inheritdoc /> | ||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) | 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 /> | /// <inheritdoc /> | ||||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) | 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 /> | /// <inheritdoc /> | ||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | ||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
@@ -46,8 +46,6 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public int AFKTimeout { get; private set; } | public int AFKTimeout { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public bool IsEmbeddable { get; private set; } | |||||
/// <inheritdoc /> | |||||
public bool IsWidgetEnabled { get; private set; } | public bool IsWidgetEnabled { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public VerificationLevel VerificationLevel { get; private set; } | public VerificationLevel VerificationLevel { get; private set; } | ||||
@@ -84,7 +82,6 @@ namespace Discord.WebSocket | |||||
public ulong? ApplicationId { get; internal set; } | public ulong? ApplicationId { get; internal set; } | ||||
internal ulong? AFKChannelId { get; private set; } | internal ulong? AFKChannelId { get; private set; } | ||||
internal ulong? EmbedChannelId { get; private set; } | |||||
internal ulong? WidgetChannelId { get; private set; } | internal ulong? WidgetChannelId { get; private set; } | ||||
internal ulong? SystemChannelId { get; private set; } | internal ulong? SystemChannelId { get; private set; } | ||||
internal ulong? RulesChannelId { get; private set; } | internal ulong? RulesChannelId { get; private set; } | ||||
@@ -198,21 +195,6 @@ namespace Discord.WebSocket | |||||
} | } | ||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// A channel set within the server's widget settings; <see langword="null"/> if none is set. | |||||
/// </returns> | |||||
[Obsolete("This property is deprecated, use WidgetChannel instead.")] | |||||
public SocketGuildChannel EmbedChannel | |||||
{ | |||||
get | |||||
{ | |||||
var id = EmbedChannelId; | |||||
return id.HasValue ? GetChannel(id.Value) : null; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. | /// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. | ||||
/// </summary> | /// </summary> | ||||
/// <returns> | /// <returns> | ||||
@@ -405,7 +387,8 @@ namespace Discord.WebSocket | |||||
for (int i = 0; i < model.Members.Length; i++) | for (int i = 0; i < model.Members.Length; i++) | ||||
{ | { | ||||
var member = SocketGuildUser.Create(this, state, model.Members[i]); | var member = SocketGuildUser.Create(this, state, model.Members[i]); | ||||
members.TryAdd(member.Id, member); | |||||
if (members.TryAdd(member.Id, member)) | |||||
member.GlobalUser.AddRef(); | |||||
} | } | ||||
DownloadedMemberCount = members.Count; | DownloadedMemberCount = members.Count; | ||||
@@ -440,16 +423,12 @@ namespace Discord.WebSocket | |||||
internal void Update(ClientState state, Model model) | internal void Update(ClientState state, Model model) | ||||
{ | { | ||||
AFKChannelId = model.AFKChannelId; | AFKChannelId = model.AFKChannelId; | ||||
if (model.EmbedChannelId.IsSpecified) | |||||
EmbedChannelId = model.EmbedChannelId.Value; | |||||
if (model.WidgetChannelId.IsSpecified) | if (model.WidgetChannelId.IsSpecified) | ||||
WidgetChannelId = model.WidgetChannelId.Value; | WidgetChannelId = model.WidgetChannelId.Value; | ||||
SystemChannelId = model.SystemChannelId; | SystemChannelId = model.SystemChannelId; | ||||
RulesChannelId = model.RulesChannelId; | RulesChannelId = model.RulesChannelId; | ||||
PublicUpdatesChannelId = model.PublicUpdatesChannelId; | PublicUpdatesChannelId = model.PublicUpdatesChannelId; | ||||
AFKTimeout = model.AFKTimeout; | AFKTimeout = model.AFKTimeout; | ||||
if (model.EmbedEnabled.IsSpecified) | |||||
IsEmbeddable = model.EmbedEnabled.Value; | |||||
if (model.WidgetEnabled.IsSpecified) | if (model.WidgetEnabled.IsSpecified) | ||||
IsWidgetEnabled = model.WidgetEnabled.Value; | IsWidgetEnabled = model.WidgetEnabled.Value; | ||||
IconId = model.Icon; | IconId = model.Icon; | ||||
@@ -504,7 +483,7 @@ namespace Discord.WebSocket | |||||
} | } | ||||
_roles = roles; | _roles = roles; | ||||
} | } | ||||
internal void Update(ClientState state, GuildSyncModel model) | |||||
/*internal void Update(ClientState state, GuildSyncModel model) //TODO remove? userbot related | |||||
{ | { | ||||
var members = new ConcurrentDictionary<ulong, SocketGuildUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); | var members = new ConcurrentDictionary<ulong, SocketGuildUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); | ||||
{ | { | ||||
@@ -524,9 +503,9 @@ namespace Discord.WebSocket | |||||
_members = members; | _members = members; | ||||
var _ = _syncPromise.TrySetResultAsync(true); | var _ = _syncPromise.TrySetResultAsync(true); | ||||
/*if (!model.Large) | |||||
_ = _downloaderPromise.TrySetResultAsync(true);*/ | |||||
} | |||||
//if (!model.Large) | |||||
// _ = _downloaderPromise.TrySetResultAsync(true); | |||||
}*/ | |||||
internal void Update(ClientState state, EmojiUpdateModel model) | internal void Update(ClientState state, EmojiUpdateModel model) | ||||
{ | { | ||||
@@ -548,11 +527,6 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | /// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | ||||
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")] | |||||
public Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null) | |||||
=> GuildHelper.ModifyEmbedAsync(this, Discord, func, options); | |||||
/// <inheritdoc /> | |||||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||||
public Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null) | public Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null) | ||||
=> GuildHelper.ModifyWidgetAsync(this, Discord, func, options); | => GuildHelper.ModifyWidgetAsync(this, Discord, func, options); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -869,16 +843,10 @@ namespace Discord.WebSocket | |||||
else | else | ||||
{ | { | ||||
member = SocketGuildUser.Create(this, Discord.State, model); | member = SocketGuildUser.Create(this, Discord.State, model); | ||||
if (member == null) | |||||
throw new InvalidOperationException("SocketGuildUser.Create failed to produce a member"); // TODO 2.2rel: delete this | |||||
if (member.GlobalUser == null) | |||||
throw new InvalidOperationException("Member was created without global user"); // TODO 2.2rel: delete this | |||||
member.GlobalUser.AddRef(); | member.GlobalUser.AddRef(); | ||||
_members[member.Id] = member; | _members[member.Id] = member; | ||||
DownloadedMemberCount++; | DownloadedMemberCount++; | ||||
} | } | ||||
if (member == null) | |||||
throw new InvalidOperationException("AddOrUpdateUser failed to produce a user"); // TODO 2.2rel: delete this | |||||
return member; | return member; | ||||
} | } | ||||
internal SocketGuildUser AddOrUpdateUser(PresenceModel model) | internal SocketGuildUser AddOrUpdateUser(PresenceModel model) | ||||
@@ -912,6 +880,7 @@ namespace Discord.WebSocket | |||||
if (self != null) | if (self != null) | ||||
_members.TryAdd(self.Id, self); | _members.TryAdd(self.Id, self); | ||||
_downloaderPromise = new TaskCompletionSource<bool>(); | |||||
DownloadedMemberCount = _members.Count; | DownloadedMemberCount = _members.Count; | ||||
foreach (var member in members) | foreach (var member in members) | ||||
@@ -1232,10 +1201,6 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
bool IGuild.Available => true; | bool IGuild.Available => true; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; | |||||
/// <inheritdoc /> | |||||
ulong? IGuild.EmbedChannelId => EmbedChannelId; | |||||
/// <inheritdoc /> | |||||
ulong? IGuild.WidgetChannelId => WidgetChannelId; | ulong? IGuild.WidgetChannelId => WidgetChannelId; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
ulong? IGuild.SystemChannelId => SystemChannelId; | ulong? IGuild.SystemChannelId => SystemChannelId; | ||||
@@ -1290,10 +1255,6 @@ namespace Discord.WebSocket | |||||
Task<ITextChannel> IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) | Task<ITextChannel> IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) | ||||
=> Task.FromResult<ITextChannel>(DefaultChannel); | => Task.FromResult<ITextChannel>(DefaultChannel); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
[Obsolete("This method is deprecated, use GetWidgetChannelAsync instead.")] | |||||
Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) | |||||
=> Task.FromResult<IGuildChannel>(EmbedChannel); | |||||
/// <inheritdoc /> | |||||
Task<IGuildChannel> IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options) | Task<IGuildChannel> IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options) | ||||
=> Task.FromResult<IGuildChannel>(WidgetChannel); | => Task.FromResult<IGuildChannel>(WidgetChannel); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -49,9 +49,6 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
int? IInvite.MemberCount => throw new NotImplementedException(); | int? IInvite.MemberCount => throw new NotImplementedException(); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
[Obsolete("This property doesn't exist anymore and shouldn't be used.")] | |||||
bool IInviteMetadata.IsRevoked => throw new NotImplementedException(); | |||||
/// <inheritdoc /> | |||||
public bool IsTemporary { get; private set; } | public bool IsTemporary { get; private set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
int? IInviteMetadata.MaxAge { get => MaxAge; } | int? IInviteMetadata.MaxAge { get => MaxAge; } | ||||
@@ -189,9 +189,6 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task UnpinAsync(RequestOptions options = null) | public Task UnpinAsync(RequestOptions options = null) | ||||
=> MessageHelper.UnpinAsync(this, Discord, options); | => 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, | 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) | 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 string Username { get; internal set; } | ||||
public override ushort DiscriminatorValue { get; internal set; } | public override ushort DiscriminatorValue { get; internal set; } | ||||
public override string AvatarId { get; internal set; } | public override string AvatarId { get; internal set; } | ||||
public SocketDMChannel DMChannel { get; internal set; } | |||||
internal override SocketPresence Presence { get; set; } | internal override SocketPresence Presence { get; set; } | ||||
public override bool IsWebhook => false; | public override bool IsWebhook => false; | ||||
@@ -52,7 +51,6 @@ namespace Discord.WebSocket | |||||
internal void Update(ClientState state, PresenceModel model) | internal void Update(ClientState state, PresenceModel model) | ||||
{ | { | ||||
Presence = SocketPresence.Create(model); | Presence = SocketPresence.Create(model); | ||||
DMChannel = state.DMChannels.FirstOrDefault(x => x.Recipient.Id == Id); | |||||
} | } | ||||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)"; | private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)"; | ||||
@@ -2,6 +2,7 @@ using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Linq; | |||||
using Model = Discord.API.Presence; | using Model = Discord.API.Presence; | ||||
namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
@@ -15,15 +16,12 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public UserStatus Status { get; } | public UserStatus Status { get; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IActivity Activity { get; } | |||||
/// <inheritdoc /> | |||||
public IImmutableSet<ClientType> ActiveClients { get; } | public IImmutableSet<ClientType> ActiveClients { get; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IImmutableList<IActivity> Activities { get; } | 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; | Status = status; | ||||
Activity = activity; | |||||
ActiveClients = activeClients ?? ImmutableHashSet<ClientType>.Empty; | ActiveClients = activeClients ?? ImmutableHashSet<ClientType>.Empty; | ||||
Activities = activities ?? ImmutableList<IActivity>.Empty; | Activities = activities ?? ImmutableList<IActivity>.Empty; | ||||
} | } | ||||
@@ -31,7 +29,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
var clients = ConvertClientTypesDict(model.ClientStatus.GetValueOrDefault()); | var clients = ConvertClientTypesDict(model.ClientStatus.GetValueOrDefault()); | ||||
var activities = ConvertActivitiesList(model.Activities); | var activities = ConvertActivitiesList(model.Activities); | ||||
return new SocketPresence(model.Status, model.Game?.ToEntity(), clients, activities); | |||||
return new SocketPresence(model.Status, clients, activities); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// Creates a new <see cref="IReadOnlyCollection{T}"/> containing all of the client types | /// 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" />. | /// A string that resolves to <see cref="Discord.WebSocket.SocketPresence.Status" />. | ||||
/// </returns> | /// </returns> | ||||
public override string ToString() => Status.ToString(); | 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; | internal SocketPresence Clone() => this; | ||||
} | } | ||||
@@ -25,7 +25,7 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override bool IsWebhook => false; | public override bool IsWebhook => false; | ||||
/// <inheritdoc /> | /// <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 /> | /// <inheritdoc /> | ||||
/// <exception cref="NotSupportedException">This field is not supported for an unknown user.</exception> | /// <exception cref="NotSupportedException">This field is not supported for an unknown user.</exception> | ||||
internal override SocketGlobalUser GlobalUser => | internal override SocketGlobalUser GlobalUser => | ||||
@@ -38,8 +38,6 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public string Mention => MentionUtils.MentionUser(Id); | public string Mention => MentionUtils.MentionUser(Id); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IActivity Activity => Presence.Activity; | |||||
/// <inheritdoc /> | |||||
public UserStatus Status => Presence.Status; | public UserStatus Status => Presence.Status; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IImmutableSet<ClientType> ActiveClients => Presence.ActiveClients ?? ImmutableHashSet<ClientType>.Empty; | public IImmutableSet<ClientType> ActiveClients => Presence.ActiveClients ?? ImmutableHashSet<ClientType>.Empty; | ||||
@@ -94,8 +92,8 @@ namespace Discord.WebSocket | |||||
} | } | ||||
/// <inheritdoc /> | /// <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 /> | /// <inheritdoc /> | ||||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | ||||
@@ -30,7 +30,7 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override bool IsWebhook => true; | public override bool IsWebhook => true; | ||||
/// <inheritdoc /> | /// <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 => | internal override SocketGlobalUser GlobalUser => | ||||
throw new NotSupportedException(); | throw new NotSupportedException(); | ||||
@@ -2,7 +2,7 @@ | |||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | ||||
<metadata> | <metadata> | ||||
<id>Discord.Net</id> | <id>Discord.Net</id> | ||||
<version>2.4.0$suffix$</version> | |||||
<version>3.0.0-dev$suffix$</version> | |||||
<title>Discord.Net</title> | <title>Discord.Net</title> | ||||
<authors>Discord.Net Contributors</authors> | <authors>Discord.Net Contributors</authors> | ||||
<owners>foxbot</owners> | <owners>foxbot</owners> | ||||
@@ -14,25 +14,25 @@ | |||||
<iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl> | <iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl> | ||||
<dependencies> | <dependencies> | ||||
<group targetFramework="net461"> | <group targetFramework="net461"> | ||||
<dependency id="Discord.Net.Core" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Rest" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.WebSocket" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Commands" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Webhook" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Core" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.Rest" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.WebSocket" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.Commands" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.Webhook" version="3.0.0-dev$suffix$" /> | |||||
</group> | </group> | ||||
<group targetFramework="netstandard2.0"> | <group targetFramework="netstandard2.0"> | ||||
<dependency id="Discord.Net.Core" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Rest" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.WebSocket" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Commands" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Webhook" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Core" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.Rest" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.WebSocket" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.Commands" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.Webhook" version="3.0.0-dev$suffix$" /> | |||||
</group> | </group> | ||||
<group targetFramework="netstandard2.1"> | <group targetFramework="netstandard2.1"> | ||||
<dependency id="Discord.Net.Core" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Rest" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.WebSocket" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Commands" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Webhook" version="2.4.0$suffix$" /> | |||||
<dependency id="Discord.Net.Core" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.Rest" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.WebSocket" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.Commands" version="3.0.0-dev$suffix$" /> | |||||
<dependency id="Discord.Net.Webhook" version="3.0.0-dev$suffix$" /> | |||||
</group> | </group> | ||||
</dependencies> | </dependencies> | ||||
</metadata> | </metadata> | ||||
@@ -190,42 +190,6 @@ namespace Discord | |||||
Assert.Equal(result.ThumbnailUrl, url); | Assert.Equal(result.ThumbnailUrl, url); | ||||
} | } | ||||
/// <summary> | |||||
/// Tests that invalid urls throw an <see cref="ArgumentException"/>. | |||||
/// </summary> | |||||
/// <param name="url">The url to set.</param> | |||||
[Theory] | |||||
[InlineData(" ")] | |||||
[InlineData("not a url")] | |||||
public void Url_Invalid(string url) | |||||
{ | |||||
Assert.Throws<ArgumentException>(() | |||||
=> new EmbedBuilder() | |||||
.WithUrl(url)); | |||||
Assert.Throws<ArgumentException>(() | |||||
=> new EmbedBuilder() | |||||
.WithImageUrl(url)); | |||||
Assert.Throws<ArgumentException>(() | |||||
=> new EmbedBuilder() | |||||
.WithThumbnailUrl(url)); | |||||
Assert.Throws<ArgumentException>(() => | |||||
{ | |||||
var b = new EmbedBuilder(); | |||||
b.Url = url; | |||||
}); | |||||
Assert.Throws<ArgumentException>(() => | |||||
{ | |||||
var b = new EmbedBuilder(); | |||||
b.ImageUrl = url; | |||||
}); | |||||
Assert.Throws<ArgumentException>(() => | |||||
{ | |||||
var b = new EmbedBuilder(); | |||||
b.ThumbnailUrl = url; | |||||
}); | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// Tests the value of the <see cref="EmbedBuilder.Length"/> property when there are no fields set. | /// Tests the value of the <see cref="EmbedBuilder.Length"/> property when there are no fields set. | ||||
/// </summary> | /// </summary> | ||||
@@ -343,24 +307,6 @@ namespace Discord | |||||
Assert.Equal(name, footer.Text); | Assert.Equal(name, footer.Text); | ||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// Tests that invalid URLs throw an <see cref="ArgumentException"/>. | |||||
/// </summary> | |||||
[Fact] | |||||
public void EmbedFooterBuilder_InvalidURL() | |||||
{ | |||||
IEnumerable<string> InvalidUrls() | |||||
{ | |||||
yield return "not a url"; | |||||
} | |||||
foreach (var url in InvalidUrls()) | |||||
{ | |||||
Assert.Throws<ArgumentException>(() => | |||||
{ | |||||
new EmbedFooterBuilder().WithIconUrl(url); | |||||
}); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Tests that invalid text throws an <see cref="ArgumentException"/>. | /// Tests that invalid text throws an <see cref="ArgumentException"/>. | ||||
/// </summary> | /// </summary> | ||||
[Fact] | [Fact] | ||||
@@ -33,6 +33,11 @@ namespace Discord | |||||
throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
} | } | ||||
public Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | |||||
{ | |||||
throw new NotImplementedException(); | |||||
} | |||||
public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
{ | { | ||||
throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
@@ -31,6 +31,11 @@ namespace Discord | |||||
throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
} | } | ||||
public Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | |||||
{ | |||||
throw new NotImplementedException(); | |||||
} | |||||
public Task DisconnectAsync() | public Task DisconnectAsync() | ||||
{ | { | ||||
throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
@@ -77,6 +77,11 @@ namespace Discord | |||||
throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
} | } | ||||
public Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | |||||
{ | |||||
throw new NotImplementedException(); | |||||
} | |||||
public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
{ | { | ||||
throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
@@ -127,8 +127,6 @@ namespace Discord | |||||
/// The <see cref="TokenType.User"/> type is treated as an invalid <see cref="TokenType"/>. | /// The <see cref="TokenType.User"/> type is treated as an invalid <see cref="TokenType"/>. | ||||
/// </remarks> | /// </remarks> | ||||
[Theory] | [Theory] | ||||
// TokenType.User | |||||
[InlineData(0)] | |||||
// out of range TokenType | // out of range TokenType | ||||
[InlineData(-1)] | [InlineData(-1)] | ||||
[InlineData(4)] | [InlineData(4)] | ||||