| @@ -1,5 +1,108 @@ | |||||
| # Changelog | # Changelog | ||||
| ## [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 | |||||
| ### Added | |||||
| - #1491 Add INVITE_CREATE and INVITE_DELETE events (1ab670b) | |||||
| - #1520 Support reading multiple activities (421a0c1) | |||||
| - #1521 Allow for inherited commands in modules (a51cdf6) | |||||
| - #1526 Add Direction.Around to GetMessagesAsync (f2130f8) | |||||
| - #1537 Implement gateway ratelimit (ec673e1) | |||||
| - #1544 Add MESSAGE_REACTION_REMOVE_EMOJI and RemoveAllReactionsForEmoteAsync (a89f076) | |||||
| - #1549 Add GetUsersAsync to SocketGuild (30b5a83) | |||||
| - #1566 Support Gateway Intents (d5d10d3) | |||||
| - #1573 Add missing properties to Guild and deprecate GuildEmbed (ec212b1) | |||||
| - #1581 Add includeRoleIds to PruneUsersAsync (a80e5ff) | |||||
| - #1588 Add GetStreams to AudioClient (1e012ac) | |||||
| - #1596 Add missing channel properties (2d80037) | |||||
| - #1604 Add missing application properties (including Teams) (10fcde0) | |||||
| - #1619 Add "View Guild Insights" to GuildPermission (2592264) | |||||
| - #1637 Added CultureInvariant RegexOption to WebhookUrlRegex (e3925a7) | |||||
| - #1659 Add inline replies (e3850e1) | |||||
| - #1688 Send presence on Identify payload (25d5d36) | |||||
| - #1721 Add role tags (6a62c47) | |||||
| - #1722 Add user public flags (c683b29) | |||||
| - #1724 Add MessageFlags and AllowedMentions to message modify (225550d) | |||||
| - #1731 Add GuildUser IsPending property (8b25c9b) | |||||
| - #1690 Add max bitrate value to SocketGuild (aacfea0) | |||||
| ### Fixed | |||||
| - #1244 Missing AddReactions permission for DM channels. (e40ca4a) | |||||
| - #1469 unsupported property causes an exception (468f826) | |||||
| - #1525 AllowedMentions and AllowedMentionTypes (3325031) | |||||
| - #1531 Add AllowedMentions to SendFileAsync (ab32607) | |||||
| - #1532 GuildEmbed.ChannelId as nullable per API documentation (971d519) | |||||
| - #1546 Different ratelimits for the same route (implement discord buckets) (2f6c017) | |||||
| - #1548 Incomplete Ready, DownloadUsersAsync, and optimize AlwaysDownloadUsers (dc8c959) | |||||
| - #1555 InvalidOperationException at MESSAGE_CREATE (bd4672a) | |||||
| - #1557 Sending 2 requests instead of 1 to create a Guild role. (5430cc8) | |||||
| - #1571 Not using the new domain name. (df8a0f7) | |||||
| - #1578 Trim token before passing it to the authorization header (42ba372) | |||||
| - #1580 Stop TaskCanceledException from bubbling up (b8fa464) | |||||
| - #1599 Invite audit log without inviter (b95b95b) | |||||
| - #1602 Add AllowedMentions to webhooks (bd4516b) | |||||
| - #1603 Cancel reconnection when 4014 (f396cd9) | |||||
| - #1608 Voice overwrites and CategoryId remarks (43c8fc0) | |||||
| - #1614 Check error 404 and return null for GetBanAsync (ae9fff6) | |||||
| - #1621 Parse mentions from message payload (366ca9a) | |||||
| - #1622 Do not update overwrite cache locally (3860da0) | |||||
| - #1623 Invoke UserUpdated from GuildMemberUpdated if needed (3085e88) | |||||
| - #1624 Handle null PreferredLocale in rare cases (c1d04b4) | |||||
| - #1639 Invite and InviteMetadata properties (dd2e524) | |||||
| - #1642 Add missing permissions (4b389f3) | |||||
| - #1647 handicap member downloading for verified bots (fa5ef5e) | |||||
| - #1652 Update README.MD to reflect new discord domain (03b831e) | |||||
| - #1667 Audio stream dispose (a2af985) | |||||
| - #1671 Crosspost throwing InvalidOperationException (9134443) | |||||
| - #1672 Team is nullable, not optional (be60d81) | |||||
| - #1681 Emoji url encode (04389a4) | |||||
| - #1683 SocketGuild.HasAllMembers is false if a user left a guild (47f571e) | |||||
| - #1686 Revert PremiumSubscriptionCount type (97e71cd) | |||||
| - #1695 Possible NullReferenceException when receiving InvalidSession (5213916) | |||||
| - #1702 Rollback Activities to Game (9d7cb39) | |||||
| - #1727 Move and fix internal AllowedMentions object (4a7f8fe) | |||||
| - limit request members batch size (084db25) | |||||
| - UserMentions throwing NullRef (5ed01a3) | |||||
| - Wrong author for SocketUserMessage.ReferencedMessage (1e9b252) | |||||
| - Discord sends null when there's no team (05a1f0a) | |||||
| - IMessage.Embeds docs remarks (a4d32d3) | |||||
| - Missing MessageReference when sending files (2095701) | |||||
| ### Misc | |||||
| - #1545 MutualGuilds optimization (323a677) | |||||
| - #1551 Update webhook regex to support discord.com (7585789) | |||||
| - #1556 Add SearchUsersAsync (57880de) | |||||
| - #1561 Minor refactor to switch expression (42826df) | |||||
| - #1576 Updating comments for privileged intents (c42bfa6) | |||||
| - #1678 Change ratelimit messages (47ed806) | |||||
| - #1714 Update summary of SocketVoiceChannel.Users (e385c40) | |||||
| - #1720 VoiceRegions and related changes (5934c79) | |||||
| - Add updated libraries for LastModified (d761846) | |||||
| - Add alternative documentation link (accd351) | |||||
| - Temporarily disable StyleCops until all the fixes are impl'd (36de7b2) | |||||
| - Remove redundant CreateGuildRoleParams (3df0539) | |||||
| - Add minor tweaks to DiscordSocketConfig docs strings (2cd1880) | |||||
| - Fix MaxWaitBetweenGuildAvailablesBeforeReady docs string (e31cdc7) | |||||
| - Missing summary tag for GatewayIntents (3a10018) | |||||
| - Add new method of role ID copy (857ef77) | |||||
| - Resolve inheritdocs for IAttachment (9ea3291) | |||||
| - Mark null as a specific langword in summary (13a41f8) | |||||
| - Cleanup GatewayReconnectException docs (833ee42) | |||||
| - Update Docfx.Plugins.LastModified to v1.2.4 (28a6f97) | |||||
| - Update framework version for tests to Core 3.1 to comply with LTS (4988a07) | |||||
| - Move bulk deletes remarks from <summary> to <remarks> (62539f0) | |||||
| ## [2.2.0] - 2020-04-16 | ## [2.2.0] - 2020-04-16 | ||||
| ### Added | ### Added | ||||
| - #1247 Implement Client Status Support (9da11b4) | - #1247 Implement Client Status Support (9da11b4) | ||||
| @@ -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.0</VersionPrefix> | |||||
| <VersionPrefix>2.4.0</VersionPrefix> | |||||
| <VersionSuffix>dev</VersionSuffix> | <VersionSuffix>dev</VersionSuffix> | ||||
| <LangVersion>latest</LangVersion> | <LangVersion>latest</LangVersion> | ||||
| <Authors>Discord.Net Contributors</Authors> | <Authors>Discord.Net Contributors</Authors> | ||||
| @@ -8,8 +8,6 @@ An unofficial .NET API Wrapper for the Discord client (https://discord.com). | |||||
| ## Documentation | ## Documentation | ||||
| - [Stable](https://discord.foxbot.me/) | |||||
| - Hosted by @foxbot | |||||
| - [Nightly](https://docs.stillu.cc/) | - [Nightly](https://docs.stillu.cc/) | ||||
| - [Latest CI repo](https://github.com/discord-net/docs-static) | - [Latest CI repo](https://github.com/discord-net/docs-static) | ||||
| @@ -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); | ||||
| } | } | ||||
| @@ -136,7 +136,7 @@ namespace Discord.Commands | |||||
| builder.Name = typeInfo.Name; | builder.Name = typeInfo.Name; | ||||
| // Get all methods (including from inherited members), that are valid commands | // Get all methods (including from inherited members), that are valid commands | ||||
| var validCommands = typeInfo.GetMethods().Where(IsValidCommandDefinition); | |||||
| var validCommands = typeInfo.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(IsValidCommandDefinition); | |||||
| foreach (var method in validCommands) | foreach (var method in validCommands) | ||||
| { | { | ||||
| @@ -92,10 +92,10 @@ namespace Discord | |||||
| /// Gets all embeds included in this message. | /// Gets all embeds included in this message. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// </remarks> | |||||
| /// This property gets a read-only collection of embeds associated with this message. Depending on the | /// This property gets a read-only collection of embeds associated with this message. Depending on the | ||||
| /// message, a sent message may contain one or more embeds. This is usually true when multiple link previews | /// message, a sent message may contain one or more embeds. This is usually true when multiple link previews | ||||
| /// are generated; however, only one <see cref="EmbedType.Rich"/> <see cref="Embed"/> can be featured. | /// are generated; however, only one <see cref="EmbedType.Rich"/> <see cref="Embed"/> can be featured. | ||||
| /// </remarks> | |||||
| /// <returns> | /// <returns> | ||||
| /// A read-only collection of embed objects. | /// A read-only collection of embed objects. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -171,6 +171,17 @@ namespace Discord | |||||
| /// A read-only collection of sticker objects. | /// A read-only collection of sticker objects. | ||||
| /// </returns> | /// </returns> | ||||
| IReadOnlyCollection<ISticker> Stickers { get; } | IReadOnlyCollection<ISticker> Stickers { get; } | ||||
| /// <summary> | |||||
| /// Gets the flags related to this message. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This value is determined by bitwise OR-ing <see cref="MessageFlags"/> values together. | |||||
| /// </remarks> | |||||
| /// <returns> | |||||
| /// A message's flags, if any is associated. | |||||
| /// </returns> | |||||
| MessageFlags? Flags { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Adds a reaction to this message. | /// Adds a reaction to this message. | ||||
| @@ -0,0 +1,36 @@ | |||||
| using System; | |||||
| namespace Discord | |||||
| { | |||||
| [Flags] | |||||
| public enum MessageFlags | |||||
| { | |||||
| /// <summary> | |||||
| /// Default value for flags, when none are given to a message. | |||||
| /// </summary> | |||||
| None = 0, | |||||
| /// <summary> | |||||
| /// Flag given to messages that have been published to subscribed | |||||
| /// channels (via Channel Following). | |||||
| /// </summary> | |||||
| Crossposted = 1 << 0, | |||||
| /// <summary> | |||||
| /// Flag given to messages that originated from a message in another | |||||
| /// channel (via Channel Following). | |||||
| /// </summary> | |||||
| IsCrosspost = 1 << 1, | |||||
| /// <summary> | |||||
| /// Flag given to messages that do not display any embeds. | |||||
| /// </summary> | |||||
| SuppressEmbeds = 1 << 2, | |||||
| /// <summary> | |||||
| /// Flag given to messages that the source message for this crosspost | |||||
| /// has been deleted (via Channel Following). | |||||
| /// </summary> | |||||
| SourceMessageDeleted = 1 << 3, | |||||
| /// <summary> | |||||
| /// Flag given to messages that came from the urgent message system. | |||||
| /// </summary> | |||||
| Urgent = 1 << 4, | |||||
| } | |||||
| } | |||||
| @@ -21,5 +21,17 @@ namespace Discord | |||||
| /// Gets or sets the embed the message should display. | /// Gets or sets the embed the message should display. | ||||
| /// </summary> | /// </summary> | ||||
| public Optional<Embed> Embed { get; set; } | public Optional<Embed> Embed { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets the flags of the message. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Only <see cref="MessageFlags.SuppressEmbeds"/> can be set/unset and you need to be | |||||
| /// the author of the message. | |||||
| /// </remarks> | |||||
| public Optional<MessageFlags?> Flags { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the allowed mentions of the message. | |||||
| /// </summary> | |||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -65,6 +65,13 @@ namespace Discord | |||||
| /// An <see cref="int"/> representing the position of the role in the role list of the guild. | /// An <see cref="int"/> representing the position of the role in the role list of the guild. | ||||
| /// </returns> | /// </returns> | ||||
| int Position { get; } | int Position { get; } | ||||
| /// <summary> | |||||
| /// Gets the tags related to this role. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="RoleTags"/> object containing all tags related to this role. | |||||
| /// </returns> | |||||
| RoleTags Tags { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Modifies this role. | /// Modifies this role. | ||||
| @@ -0,0 +1,40 @@ | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Provides tags related to a discord role. | |||||
| /// </summary> | |||||
| public class RoleTags | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the identifier of the bot that this role belongs to, if it does. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see langword="ulong"/> if this role belongs to a bot; otherwise | |||||
| /// <see langword="null"/>. | |||||
| /// </returns> | |||||
| public ulong? BotId { get; } | |||||
| /// <summary> | |||||
| /// Gets the identifier of the integration that this role belongs to, if it does. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see langword="ulong"/> if this role belongs to an integration; otherwise | |||||
| /// <see langword="null"/>. | |||||
| /// </returns> | |||||
| public ulong? IntegrationId { get; } | |||||
| /// <summary> | |||||
| /// Gets if this role is the guild's premium subscriber (booster) role. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// <see langword="true"/> if this role is the guild's premium subscriber role; | |||||
| /// otherwise <see langword="false"/>. | |||||
| /// </returns> | |||||
| public bool IsPremiumSubscriberRole { get; } | |||||
| internal RoleTags(ulong? botId, ulong? integrationId, bool isPremiumSubscriber) | |||||
| { | |||||
| BotId = botId; | |||||
| IntegrationId = integrationId; | |||||
| IsPremiumSubscriberRole = isPremiumSubscriber; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -68,6 +68,11 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| IReadOnlyCollection<ulong> RoleIds { get; } | IReadOnlyCollection<ulong> RoleIds { get; } | ||||
| /// <summary> | |||||
| /// Whether the user has passed the guild's Membership Screening requirements. | |||||
| /// </summary> | |||||
| bool? IsPending { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the level permissions granted to this user to a given channel. | /// Gets the level permissions granted to this user to a given channel. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -75,6 +75,16 @@ namespace Discord | |||||
| /// Gets the username for this user. | /// Gets the username for this user. | ||||
| /// </summary> | /// </summary> | ||||
| string Username { get; } | string Username { get; } | ||||
| /// <summary> | |||||
| /// Gets the public flags that are applied to this user's account. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This value is determined by bitwise OR-ing <see cref="UserProperties"/> values together. | |||||
| /// </remarks> | |||||
| /// <returns> | |||||
| /// The value of public flags for this user. | |||||
| /// </returns> | |||||
| UserProperties? PublicFlags { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the direct message channel of this user, or create one if it does not already exist. | /// Gets the direct message channel of this user, or create one if it does not already exist. | ||||
| @@ -10,32 +10,62 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| None = 0, | None = 0, | ||||
| /// <summary> | /// <summary> | ||||
| /// Flag given to Discord staff. | |||||
| /// Flag given to users who are a Discord employee. | |||||
| /// </summary> | /// </summary> | ||||
| Staff = 0b1, | |||||
| Staff = 1 << 0, | |||||
| /// <summary> | /// <summary> | ||||
| /// Flag given to Discord partners. | |||||
| /// Flag given to users who are owners of a partnered Discord server. | |||||
| /// </summary> | /// </summary> | ||||
| Partner = 0b10, | |||||
| Partner = 1 << 1, | |||||
| /// <summary> | |||||
| /// Flag given to users in HypeSquad events. | |||||
| /// </summary> | |||||
| HypeSquadEvents = 1 << 2, | |||||
| /// <summary> | /// <summary> | ||||
| /// Flag given to users who have participated in the bug report program. | /// 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. | |||||
| /// </summary> | /// </summary> | ||||
| BugHunter = 0b1000, | |||||
| BugHunterLevel1 = 1 << 3, | |||||
| /// <summary> | /// <summary> | ||||
| /// Flag given to users who are in the HypeSquad House of Bravery. | /// Flag given to users who are in the HypeSquad House of Bravery. | ||||
| /// </summary> | /// </summary> | ||||
| HypeSquadBravery = 0b100_0000, | |||||
| HypeSquadBravery = 1 << 6, | |||||
| /// <summary> | /// <summary> | ||||
| /// Flag given to users who are in the HypeSquad House of Brilliance. | /// Flag given to users who are in the HypeSquad House of Brilliance. | ||||
| /// </summary> | /// </summary> | ||||
| HypeSquadBrilliance = 0b1000_0000, | |||||
| HypeSquadBrilliance = 1 << 7, | |||||
| /// <summary> | /// <summary> | ||||
| /// Flag given to users who are in the HypeSquad House of Balance. | /// Flag given to users who are in the HypeSquad House of Balance. | ||||
| /// </summary> | /// </summary> | ||||
| HypeSquadBalance = 0b1_0000_0000, | |||||
| HypeSquadBalance = 1 << 8, | |||||
| /// <summary> | /// <summary> | ||||
| /// Flag given to users who subscribed to Nitro before games were added. | /// Flag given to users who subscribed to Nitro before games were added. | ||||
| /// </summary> | /// </summary> | ||||
| EarlySupporter = 0b10_0000_0000, | |||||
| EarlySupporter = 1 << 9, | |||||
| /// <summary> | |||||
| /// Flag given to users who are part of a team. | |||||
| /// </summary> | |||||
| TeamUser = 1 << 10, | |||||
| /// <summary> | |||||
| /// Flag given to users who represent Discord (System). | |||||
| /// </summary> | |||||
| System = 1 << 12, | |||||
| /// <summary> | |||||
| /// Flag given to users who have participated in the bug report program and are level 2. | |||||
| /// </summary> | |||||
| BugHunterLevel2 = 1 << 14, | |||||
| /// <summary> | |||||
| /// Flag given to users who are verified bots. | |||||
| /// </summary> | |||||
| VerifiedBot = 1 << 16, | |||||
| /// <summary> | |||||
| /// Flag given to users that developed bots and early verified their accounts. | |||||
| /// </summary> | |||||
| EarlyVerifiedBotDeveloper = 1 << 17, | |||||
| } | } | ||||
| } | } | ||||
| @@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API | namespace Discord.API | ||||
| { | { | ||||
| public class AllowedMentions | |||||
| internal class AllowedMentions | |||||
| { | { | ||||
| [JsonProperty("parse")] | [JsonProperty("parse")] | ||||
| public Optional<string[]> Parse { get; set; } | public Optional<string[]> Parse { get; set; } | ||||
| @@ -7,7 +7,7 @@ namespace Discord.API | |||||
| [JsonProperty("target_id")] | [JsonProperty("target_id")] | ||||
| public ulong? TargetId { get; set; } | public ulong? TargetId { get; set; } | ||||
| [JsonProperty("user_id")] | [JsonProperty("user_id")] | ||||
| public ulong UserId { get; set; } | |||||
| public ulong? UserId { get; set; } | |||||
| [JsonProperty("changes")] | [JsonProperty("changes")] | ||||
| public AuditLogChange[] Changes { get; set; } | public AuditLogChange[] Changes { get; set; } | ||||
| @@ -18,6 +18,8 @@ namespace Discord.API | |||||
| public Optional<bool> Deaf { get; set; } | public Optional<bool> Deaf { get; set; } | ||||
| [JsonProperty("mute")] | [JsonProperty("mute")] | ||||
| public Optional<bool> Mute { get; set; } | public Optional<bool> Mute { get; set; } | ||||
| [JsonProperty("pending")] | |||||
| public Optional<bool> Pending { get; set; } | |||||
| [JsonProperty("premium_since")] | [JsonProperty("premium_since")] | ||||
| public Optional<DateTimeOffset?> PremiumSince { get; set; } | public Optional<DateTimeOffset?> PremiumSince { get; set; } | ||||
| } | } | ||||
| @@ -1,10 +0,0 @@ | |||||
| using System; | |||||
| namespace Discord.API | |||||
| { | |||||
| [Flags] | |||||
| internal enum MessageFlags : byte // probably safe to constrain this to 8 values, if not, it's internal so who cares | |||||
| { | |||||
| Suppressed = 0x04, | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| #pragma warning disable CS1591 | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| namespace Discord.API | namespace Discord.API | ||||
| @@ -21,5 +21,7 @@ namespace Discord.API | |||||
| public ulong Permissions { get; set; } | public ulong Permissions { get; set; } | ||||
| [JsonProperty("managed")] | [JsonProperty("managed")] | ||||
| public bool Managed { get; set; } | public bool Managed { get; set; } | ||||
| [JsonProperty("tags")] | |||||
| public Optional<RoleTags> Tags { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,15 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class RoleTags | |||||
| { | |||||
| [JsonProperty("bot_id")] | |||||
| public Optional<ulong> BotId { get; set; } | |||||
| [JsonProperty("integration_id")] | |||||
| public Optional<ulong> IntegrationId { get; set; } | |||||
| [JsonProperty("premium_subscriber")] | |||||
| public Optional<bool?> IsPremiumSubscriber { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -29,5 +29,7 @@ namespace Discord.API | |||||
| public Optional<PremiumType> PremiumType { get; set; } | public Optional<PremiumType> PremiumType { get; set; } | ||||
| [JsonProperty("locale")] | [JsonProperty("locale")] | ||||
| public Optional<string> Locale { get; set; } | public Optional<string> Locale { get; set; } | ||||
| [JsonProperty("public_flags")] | |||||
| public Optional<UserProperties> PublicFlags { 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 | ||||
| @@ -10,5 +10,9 @@ namespace Discord.API.Rest | |||||
| public Optional<string> Content { get; set; } | public Optional<string> Content { get; set; } | ||||
| [JsonProperty("embed")] | [JsonProperty("embed")] | ||||
| public Optional<Embed> Embed { get; set; } | public Optional<Embed> Embed { get; set; } | ||||
| [JsonProperty("flags")] | |||||
| public Optional<MessageFlags?> Flags { get; set; } | |||||
| [JsonProperty("allowed_mentions")] | |||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -49,6 +49,8 @@ namespace Discord.API.Rest | |||||
| payload["allowed_mentions"] = AllowedMentions.Value; | payload["allowed_mentions"] = AllowedMentions.Value; | ||||
| if (IsSpoiler) | if (IsSpoiler) | ||||
| payload["hasSpoiler"] = IsSpoiler.ToString(); | payload["hasSpoiler"] = IsSpoiler.ToString(); | ||||
| if (MessageReference.IsSpecified) | |||||
| payload["message_reference"] = MessageReference.Value; | |||||
| var json = new StringBuilder(); | var json = new StringBuilder(); | ||||
| using (var text = new StringWriter(json)) | using (var text = new StringWriter(json)) | ||||
| @@ -5,13 +5,14 @@ namespace Discord.Rest | |||||
| /// </summary> | /// </summary> | ||||
| public struct ChannelInfo | public struct ChannelInfo | ||||
| { | { | ||||
| internal ChannelInfo(string name, string topic, int? rateLimit, bool? nsfw, int? bitrate) | |||||
| internal ChannelInfo(string name, string topic, int? rateLimit, bool? nsfw, int? bitrate, ChannelType? type) | |||||
| { | { | ||||
| Name = name; | Name = name; | ||||
| Topic = topic; | Topic = topic; | ||||
| SlowModeInterval = rateLimit; | SlowModeInterval = rateLimit; | ||||
| IsNsfw = nsfw; | IsNsfw = nsfw; | ||||
| Bitrate = bitrate; | Bitrate = bitrate; | ||||
| ChannelType = type; | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -53,5 +54,12 @@ namespace Discord.Rest | |||||
| /// <c>null</c> if this is not mentioned in this entry. | /// <c>null</c> if this is not mentioned in this entry. | ||||
| /// </returns> | /// </returns> | ||||
| public int? Bitrate { get; } | public int? Bitrate { get; } | ||||
| /// <summary> | |||||
| /// Gets the type of this channel. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The channel type of this channel; <c>null</c> if not applicable. | |||||
| /// </returns> | |||||
| public ChannelType? ChannelType { get; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -26,6 +26,7 @@ namespace Discord.Rest | |||||
| var rateLimitPerUserModel = changes.FirstOrDefault(x => x.ChangedProperty == "rate_limit_per_user"); | var rateLimitPerUserModel = changes.FirstOrDefault(x => x.ChangedProperty == "rate_limit_per_user"); | ||||
| var nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw"); | var nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw"); | ||||
| var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); | var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); | ||||
| var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); | |||||
| string oldName = nameModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | string oldName = nameModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | ||||
| newName = nameModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | newName = nameModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | ||||
| @@ -37,9 +38,11 @@ namespace Discord.Rest | |||||
| newNsfw = nsfwModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | newNsfw = nsfwModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | ||||
| int? oldBitrate = bitrateModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer), | int? oldBitrate = bitrateModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer), | ||||
| newBitrate = bitrateModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | newBitrate = bitrateModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | ||||
| ChannelType? oldType = typeModel?.OldValue?.ToObject<ChannelType>(discord.ApiClient.Serializer), | |||||
| newType = typeModel?.NewValue?.ToObject<ChannelType>(discord.ApiClient.Serializer); | |||||
| var before = new ChannelInfo(oldName, oldTopic, oldRateLimitPerUser, oldNsfw, oldBitrate); | |||||
| var after = new ChannelInfo(newName, newTopic, newRateLimitPerUser, newNsfw, newBitrate); | |||||
| var before = new ChannelInfo(oldName, oldTopic, oldRateLimitPerUser, oldNsfw, oldBitrate, oldType); | |||||
| var after = new ChannelInfo(newName, newTopic, newRateLimitPerUser, newNsfw, newBitrate, newType); | |||||
| return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after); | return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after); | ||||
| } | } | ||||
| @@ -19,8 +19,14 @@ namespace Discord.Rest | |||||
| internal static MessagePinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | internal static MessagePinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | ||||
| { | { | ||||
| var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||||
| return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo)); | |||||
| RestUser user = null; | |||||
| if (entry.TargetId.HasValue) | |||||
| { | |||||
| var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||||
| user = RestUser.Create(discord, userInfo); | |||||
| } | |||||
| return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -38,10 +44,10 @@ namespace Discord.Rest | |||||
| /// </returns> | /// </returns> | ||||
| public ulong ChannelId { get; } | public ulong ChannelId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the user of the message that was pinned. | |||||
| /// Gets the user of the message that was pinned if available. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// A user object representing the user that created the pinned message. | |||||
| /// A user object representing the user that created the pinned message or <see langword="null"/>. | |||||
| /// </returns> | /// </returns> | ||||
| public IUser Target { get; } | public IUser Target { get; } | ||||
| } | } | ||||
| @@ -19,8 +19,14 @@ namespace Discord.Rest | |||||
| internal static MessageUnpinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | internal static MessageUnpinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) | ||||
| { | { | ||||
| var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||||
| return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo)); | |||||
| RestUser user = null; | |||||
| if (entry.TargetId.HasValue) | |||||
| { | |||||
| var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||||
| user = RestUser.Create(discord, userInfo); | |||||
| } | |||||
| return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -38,10 +44,10 @@ namespace Discord.Rest | |||||
| /// </returns> | /// </returns> | ||||
| public ulong ChannelId { get; } | public ulong ChannelId { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the user of the message that was unpinned. | |||||
| /// Gets the user of the message that was unpinned if available. | |||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| /// A user object representing the user that created the unpinned message. | |||||
| /// A user object representing the user that created the unpinned message or <see langword="null"/>. | |||||
| /// </returns> | /// </returns> | ||||
| public IUser Target { get; } | public IUser Target { get; } | ||||
| } | } | ||||
| @@ -22,7 +22,7 @@ namespace Discord.Rest | |||||
| internal static RestAuditLogEntry Create(BaseDiscordClient discord, Model fullLog, EntryModel model) | internal static RestAuditLogEntry Create(BaseDiscordClient discord, Model fullLog, EntryModel model) | ||||
| { | { | ||||
| var userInfo = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId); | |||||
| var userInfo = model.UserId != null ? fullLog.Users.FirstOrDefault(x => x.Id == model.UserId) : null; | |||||
| IUser user = null; | IUser user = null; | ||||
| if (userInfo != null) | if (userInfo != null) | ||||
| user = RestUser.Create(discord, userInfo); | user = RestUser.Create(discord, userInfo); | ||||
| @@ -27,21 +27,46 @@ namespace Discord.Rest | |||||
| public static async Task<Model> ModifyAsync(IMessage msg, BaseDiscordClient client, Action<MessageProperties> func, | public static async Task<Model> ModifyAsync(IMessage msg, BaseDiscordClient client, Action<MessageProperties> func, | ||||
| RequestOptions options) | RequestOptions options) | ||||
| { | { | ||||
| if (msg.Author.Id != client.CurrentUser.Id) | |||||
| throw new InvalidOperationException("Only the author of a message may modify the message."); | |||||
| var args = new MessageProperties(); | var args = new MessageProperties(); | ||||
| func(args); | func(args); | ||||
| if (msg.Author.Id != client.CurrentUser.Id && (args.Content.IsSpecified || args.Embed.IsSpecified || args.AllowedMentions.IsSpecified)) | |||||
| throw new InvalidOperationException("Only the author of a message may modify the message content, embed, or allowed mentions."); | |||||
| bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content); | bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content); | ||||
| bool hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : msg.Embeds.Any(); | bool hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : msg.Embeds.Any(); | ||||
| if (!hasText && !hasEmbed) | if (!hasText && !hasEmbed) | ||||
| Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | ||||
| if (args.AllowedMentions.IsSpecified) | |||||
| { | |||||
| AllowedMentions allowedMentions = args.AllowedMentions.Value; | |||||
| Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | |||||
| Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); | |||||
| // check that user flag and user Id list are exclusive, same with role flag and role Id list | |||||
| if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) | |||||
| { | |||||
| if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && | |||||
| allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) | |||||
| { | |||||
| throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); | |||||
| } | |||||
| if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && | |||||
| allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) | |||||
| { | |||||
| throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); | |||||
| } | |||||
| } | |||||
| } | |||||
| var apiArgs = new API.Rest.ModifyMessageParams | var apiArgs = new API.Rest.ModifyMessageParams | ||||
| { | { | ||||
| Content = args.Content, | Content = args.Content, | ||||
| Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create<API.Embed>() | |||||
| Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create<API.Embed>(), | |||||
| Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create<MessageFlags?>(), | |||||
| AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(), | |||||
| }; | }; | ||||
| return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -69,6 +69,8 @@ namespace Discord.Rest | |||||
| public MessageApplication Application { get; private set; } | public MessageApplication Application { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public MessageReference Reference { get; private set; } | public MessageReference Reference { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public MessageFlags? Flags { get; private set; } | |||||
| internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | ||||
| : base(discord, id) | : base(discord, id) | ||||
| @@ -126,6 +128,9 @@ namespace Discord.Rest | |||||
| }; | }; | ||||
| } | } | ||||
| if (model.Flags.IsSpecified) | |||||
| Flags = model.Flags.Value; | |||||
| if (model.Reactions.IsSpecified) | if (model.Reactions.IsSpecified) | ||||
| { | { | ||||
| var value = model.Reactions.Value; | var value = model.Reactions.Value; | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Rest | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class RestUserMessage : RestMessage, IUserMessage | public class RestUserMessage : RestMessage, IUserMessage | ||||
| { | { | ||||
| private bool _isMentioningEveryone, _isTTS, _isPinned, _isSuppressed; | |||||
| private bool _isMentioningEveryone, _isTTS, _isPinned; | |||||
| private long? _editedTimestampTicks; | private long? _editedTimestampTicks; | ||||
| private IUserMessage _referencedMessage; | private IUserMessage _referencedMessage; | ||||
| private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | ||||
| @@ -28,7 +28,7 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override bool IsPinned => _isPinned; | public override bool IsPinned => _isPinned; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override bool IsSuppressed => _isSuppressed; | |||||
| public override bool IsSuppressed => Flags.HasValue && Flags.Value.HasFlag(MessageFlags.SuppressEmbeds); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -73,10 +73,6 @@ namespace Discord.Rest | |||||
| _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | ||||
| if (model.MentionEveryone.IsSpecified) | if (model.MentionEveryone.IsSpecified) | ||||
| _isMentioningEveryone = model.MentionEveryone.Value; | _isMentioningEveryone = model.MentionEveryone.Value; | ||||
| if (model.Flags.IsSpecified) | |||||
| { | |||||
| _isSuppressed = model.Flags.Value.HasFlag(API.MessageFlags.Suppressed); | |||||
| } | |||||
| if (model.RoleMentions.IsSpecified) | if (model.RoleMentions.IsSpecified) | ||||
| _roleMentionIds = model.RoleMentions.Value.ToImmutableArray(); | _roleMentionIds = model.RoleMentions.Value.ToImmutableArray(); | ||||
| @@ -26,6 +26,8 @@ namespace Discord.Rest | |||||
| public GuildPermissions Permissions { get; private set; } | public GuildPermissions Permissions { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public int Position { get; private set; } | public int Position { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public RoleTags Tags { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | ||||
| @@ -56,6 +58,8 @@ namespace Discord.Rest | |||||
| Position = model.Position; | Position = model.Position; | ||||
| Color = new Color(model.Color); | Color = new Color(model.Color); | ||||
| Permissions = new GuildPermissions(model.Permissions); | Permissions = new GuildPermissions(model.Permissions); | ||||
| if (model.Tags.IsSpecified) | |||||
| Tags = model.Tags.Value.ToEntity(); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -29,6 +29,8 @@ namespace Discord.Rest | |||||
| public DateTimeOffset? PremiumSince => DateTimeUtils.FromTicks(_premiumSinceTicks); | public DateTimeOffset? PremiumSince => DateTimeUtils.FromTicks(_premiumSinceTicks); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public ulong GuildId => Guild.Id; | public ulong GuildId => Guild.Id; | ||||
| /// <inheritdoc /> | |||||
| public bool? IsPending { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="InvalidOperationException" accessor="get">Resolving permissions requires the parent guild to be downloaded.</exception> | /// <exception cref="InvalidOperationException" accessor="get">Resolving permissions requires the parent guild to be downloaded.</exception> | ||||
| @@ -73,6 +75,8 @@ namespace Discord.Rest | |||||
| UpdateRoles(model.Roles.Value); | UpdateRoles(model.Roles.Value); | ||||
| if (model.PremiumSince.IsSpecified) | if (model.PremiumSince.IsSpecified) | ||||
| _premiumSinceTicks = model.PremiumSince.Value?.UtcTicks; | _premiumSinceTicks = model.PremiumSince.Value?.UtcTicks; | ||||
| if (model.Pending.IsSpecified) | |||||
| IsPending = model.Pending.Value; | |||||
| } | } | ||||
| private void UpdateRoles(ulong[] roleIds) | private void UpdateRoles(ulong[] roleIds) | ||||
| { | { | ||||
| @@ -21,6 +21,8 @@ namespace Discord.Rest | |||||
| public ushort DiscriminatorValue { get; private set; } | public ushort DiscriminatorValue { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string AvatarId { get; private set; } | public string AvatarId { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public UserProperties? PublicFlags { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | ||||
| @@ -65,6 +67,8 @@ namespace Discord.Rest | |||||
| IsBot = model.Bot.Value; | IsBot = model.Bot.Value; | ||||
| if (model.Username.IsSpecified) | if (model.Username.IsSpecified) | ||||
| Username = model.Username.Value; | Username = model.Username.Value; | ||||
| if (model.PublicFlags.IsSpecified) | |||||
| PublicFlags = model.PublicFlags.Value; | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -52,6 +52,8 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| string IGuildUser.Nickname => null; | string IGuildUser.Nickname => null; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| bool? IGuildUser.IsPending => null; | |||||
| /// <inheritdoc /> | |||||
| GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; | GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -11,11 +11,11 @@ namespace Discord.Rest | |||||
| internal IGuild Guild { get; private set; } | internal IGuild Guild { get; private set; } | ||||
| internal ITextChannel Channel { get; private set; } | internal ITextChannel Channel { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public ulong ChannelId { get; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string Token { get; } | public string Token { get; } | ||||
| /// <inheritdoc /> | |||||
| public ulong ChannelId { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -56,6 +56,8 @@ namespace Discord.Rest | |||||
| internal void Update(Model model) | internal void Update(Model model) | ||||
| { | { | ||||
| if (ChannelId != model.ChannelId) | |||||
| ChannelId = model.ChannelId; | |||||
| if (model.Avatar.IsSpecified) | if (model.Avatar.IsSpecified) | ||||
| AvatarId = model.Avatar.Value; | AvatarId = model.Avatar.Value; | ||||
| if (model.Creator.IsSpecified) | if (model.Creator.IsSpecified) | ||||
| @@ -34,6 +34,13 @@ namespace Discord.Rest | |||||
| model.Thumbnail.IsSpecified ? model.Thumbnail.Value.ToEntity() : (EmbedThumbnail?)null, | model.Thumbnail.IsSpecified ? model.Thumbnail.Value.ToEntity() : (EmbedThumbnail?)null, | ||||
| model.Fields.IsSpecified ? model.Fields.Value.Select(x => x.ToEntity()).ToImmutableArray() : ImmutableArray.Create<EmbedField>()); | model.Fields.IsSpecified ? model.Fields.Value.Select(x => x.ToEntity()).ToImmutableArray() : ImmutableArray.Create<EmbedField>()); | ||||
| } | } | ||||
| public static RoleTags ToEntity(this API.RoleTags model) | |||||
| { | |||||
| return new RoleTags( | |||||
| model.BotId.IsSpecified ? model.BotId.Value : null, | |||||
| model.IntegrationId.IsSpecified ? model.IntegrationId.Value : null, | |||||
| model.IsPremiumSubscriber.IsSpecified ? true : false); | |||||
| } | |||||
| public static API.Embed ToModel(this Embed entity) | public static API.Embed ToModel(this Embed entity) | ||||
| { | { | ||||
| if (entity == null) return null; | if (entity == null) return null; | ||||
| @@ -1,3 +1,4 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.IO; | using System.IO; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -75,6 +76,7 @@ namespace Discord.WebSocket | |||||
| /// <returns> | /// <returns> | ||||
| /// A read-only collection of voice regions that the user has access to. | /// A read-only collection of voice regions that the user has access to. | ||||
| /// </returns> | /// </returns> | ||||
| [Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] | |||||
| public abstract IReadOnlyCollection<RestVoiceRegion> VoiceRegions { get; } | public abstract IReadOnlyCollection<RestVoiceRegion> VoiceRegions { get; } | ||||
| internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | ||||
| @@ -169,7 +171,26 @@ namespace Discord.WebSocket | |||||
| /// A REST-based voice region associated with the identifier; <c>null</c> if the voice region is not | /// A REST-based voice region associated with the identifier; <c>null</c> if the voice region is not | ||||
| /// found. | /// found. | ||||
| /// </returns> | /// </returns> | ||||
| [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||||
| public abstract RestVoiceRegion GetVoiceRegion(string id); | public abstract RestVoiceRegion GetVoiceRegion(string id); | ||||
| /// <summary> | |||||
| /// Gets all voice regions. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that contains a read-only collection of REST-based voice regions. | |||||
| /// </returns> | |||||
| public abstract ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null); | |||||
| /// <summary> | |||||
| /// Gets a voice region. | |||||
| /// </summary> | |||||
| /// <param name="id">The identifier of the voice region (e.g. <c>eu-central</c> ).</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that contains a REST-based voice region associated with the identifier; <c>null</c> if the | |||||
| /// voice region is not found. | |||||
| /// </returns> | |||||
| public abstract ValueTask<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public abstract Task StartAsync(); | public abstract Task StartAsync(); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -188,6 +209,12 @@ namespace Discord.WebSocket | |||||
| /// <param name="name">The name of the game.</param> | /// <param name="name">The name of the game.</param> | ||||
| /// <param name="streamUrl">If streaming, the URL of the stream. Must be a valid Twitch URL.</param> | /// <param name="streamUrl">If streaming, the URL of the stream. Must be a valid Twitch URL.</param> | ||||
| /// <param name="type">The type of the game.</param> | /// <param name="type">The type of the game.</param> | ||||
| /// <remarks> | |||||
| /// <note type="warning"> | |||||
| /// Bot accounts cannot set <see cref="ActivityType.CustomStatus"/> as their activity | |||||
| /// type and it will have no effect. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous set operation. | /// A task that represents the asynchronous set operation. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -201,6 +228,10 @@ namespace Discord.WebSocket | |||||
| /// Discord will only accept setting of name and the type of activity. | /// Discord will only accept setting of name and the type of activity. | ||||
| /// </note> | /// </note> | ||||
| /// <note type="warning"> | /// <note type="warning"> | ||||
| /// Bot accounts cannot set <see cref="ActivityType.CustomStatus"/> as their activity | |||||
| /// type and it will have no effect. | |||||
| /// </note> | |||||
| /// <note type="warning"> | |||||
| /// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC | /// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC | ||||
| /// clients only. | /// clients only. | ||||
| /// </note> | /// </note> | ||||
| @@ -37,6 +37,7 @@ namespace Discord.WebSocket | |||||
| 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 /> | /// <inheritdoc /> | ||||
| [Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] | |||||
| public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | ||||
| /// <summary> | /// <summary> | ||||
| @@ -264,9 +265,22 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||||
| public override RestVoiceRegion GetVoiceRegion(string id) | public override RestVoiceRegion GetVoiceRegion(string id) | ||||
| => _shards[0].GetVoiceRegion(id); | => _shards[0].GetVoiceRegion(id); | ||||
| /// <inheritdoc /> | |||||
| public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | |||||
| { | |||||
| return await _shards[0].GetVoiceRegionsAsync().ConfigureAwait(false); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public override async ValueTask<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null) | |||||
| { | |||||
| return await _shards[0].GetVoiceRegionAsync(id, options).ConfigureAwait(false); | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| /// <exception cref="ArgumentNullException"><paramref name="guilds"/> is <see langword="null"/></exception> | /// <exception cref="ArgumentNullException"><paramref name="guilds"/> is <see langword="null"/></exception> | ||||
| public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | ||||
| @@ -110,7 +110,8 @@ namespace Discord.WebSocket | |||||
| public IReadOnlyCollection<SocketGroupChannel> GroupChannels | public IReadOnlyCollection<SocketGroupChannel> GroupChannels | ||||
| => State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); | => State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | |||||
| [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. | ||||
| @@ -178,7 +179,6 @@ namespace Discord.WebSocket | |||||
| return Task.Delay(0); | return Task.Delay(0); | ||||
| }; | }; | ||||
| _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||||
| _largeGuilds = new ConcurrentQueue<ulong>(); | _largeGuilds = new ConcurrentQueue<ulong>(); | ||||
| } | } | ||||
| private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
| @@ -204,13 +204,6 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| internal override async Task OnLoginAsync(TokenType tokenType, string token) | internal override async Task OnLoginAsync(TokenType tokenType, string token) | ||||
| { | { | ||||
| if (_parentClient == null) | |||||
| { | |||||
| var voiceRegions = await ApiClient.GetVoiceRegionsAsync(new RequestOptions { IgnoreState = true, RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false); | |||||
| _voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id); | |||||
| } | |||||
| else | |||||
| _voiceRegions = _parentClient._voiceRegions; | |||||
| await Rest.OnLoginAsync(tokenType, token); | await Rest.OnLoginAsync(tokenType, token); | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -218,7 +211,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await StopAsync().ConfigureAwait(false); | await StopAsync().ConfigureAwait(false); | ||||
| _applicationInfo = null; | _applicationInfo = null; | ||||
| _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||||
| _voiceRegions = null; | |||||
| await Rest.OnLogoutAsync(); | await Rest.OnLogoutAsync(); | ||||
| } | } | ||||
| @@ -252,15 +245,15 @@ namespace Discord.WebSocket | |||||
| 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, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | ||||
| } | } | ||||
| //Wait for READY | |||||
| await _connection.WaitAsync().ConfigureAwait(false); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| if (locked) | if (locked) | ||||
| _shardedClient.ReleaseIdentifyLock(); | _shardedClient.ReleaseIdentifyLock(); | ||||
| } | } | ||||
| //Wait for READY | |||||
| await _connection.WaitAsync().ConfigureAwait(false); | |||||
| } | } | ||||
| private async Task OnDisconnectingAsync(Exception ex) | private async Task OnDisconnectingAsync(Exception ex) | ||||
| { | { | ||||
| @@ -350,11 +343,39 @@ namespace Discord.WebSocket | |||||
| => State.RemoveUser(id); | => State.RemoveUser(id); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||||
| public override RestVoiceRegion GetVoiceRegion(string id) | public override RestVoiceRegion GetVoiceRegion(string id) | ||||
| => GetVoiceRegionAsync(id).GetAwaiter().GetResult(); | |||||
| /// <inheritdoc /> | |||||
| public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | |||||
| { | |||||
| if (_parentClient == null) | |||||
| { | |||||
| if (_voiceRegions == null) | |||||
| { | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| options.IgnoreState = true; | |||||
| var voiceRegions = await ApiClient.GetVoiceRegionsAsync(options).ConfigureAwait(false); | |||||
| _voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id); | |||||
| } | |||||
| return _voiceRegions.ToReadOnlyCollection(); | |||||
| } | |||||
| return await _parentClient.GetVoiceRegionsAsync().ConfigureAwait(false); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public override async ValueTask<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null) | |||||
| { | { | ||||
| if (_voiceRegions.TryGetValue(id, out RestVoiceRegion region)) | |||||
| return region; | |||||
| return null; | |||||
| if (_parentClient == null) | |||||
| { | |||||
| if (_voiceRegions == null) | |||||
| await GetVoiceRegionsAsync().ConfigureAwait(false); | |||||
| if (_voiceRegions.TryGetValue(id, out RestVoiceRegion region)) | |||||
| return region; | |||||
| return null; | |||||
| } | |||||
| return await _parentClient.GetVoiceRegionAsync(id, options).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -611,7 +632,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| else if (_connection.CancelToken.IsCancellationRequested) | else if (_connection.CancelToken.IsCancellationRequested) | ||||
| return; | return; | ||||
| if (BaseConfig.AlwaysDownloadUsers) | if (BaseConfig.AlwaysDownloadUsers) | ||||
| _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); | _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); | ||||
| @@ -2120,11 +2141,11 @@ 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) | |||||
| => await GetVoiceRegionsAsync(options).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) | |||||
| => await GetVoiceRegionAsync(id, options).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task IDiscordClient.StartAsync() | async Task IDiscordClient.StartAsync() | ||||
| @@ -58,6 +58,9 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public MessageReference Reference { get; private set; } | public MessageReference Reference { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public MessageFlags? Flags { get; private set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Returns all attachments included in this message. | /// Returns all attachments included in this message. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -158,6 +161,9 @@ namespace Discord.WebSocket | |||||
| MessageId = model.Reference.Value.MessageId | MessageId = model.Reference.Value.MessageId | ||||
| }; | }; | ||||
| } | } | ||||
| if (model.Flags.IsSpecified) | |||||
| Flags = model.Flags.Value; | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -15,7 +15,7 @@ namespace Discord.WebSocket | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class SocketUserMessage : SocketMessage, IUserMessage | public class SocketUserMessage : SocketMessage, IUserMessage | ||||
| { | { | ||||
| private bool _isMentioningEveryone, _isTTS, _isPinned, _isSuppressed; | |||||
| private bool _isMentioningEveryone, _isTTS, _isPinned; | |||||
| private long? _editedTimestampTicks; | private long? _editedTimestampTicks; | ||||
| private IUserMessage _referencedMessage; | private IUserMessage _referencedMessage; | ||||
| private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | ||||
| @@ -30,7 +30,7 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override bool IsPinned => _isPinned; | public override bool IsPinned => _isPinned; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override bool IsSuppressed => _isSuppressed; | |||||
| public override bool IsSuppressed => Flags.HasValue && Flags.Value.HasFlag(MessageFlags.SuppressEmbeds); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -77,10 +77,6 @@ namespace Discord.WebSocket | |||||
| _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | ||||
| if (model.MentionEveryone.IsSpecified) | if (model.MentionEveryone.IsSpecified) | ||||
| _isMentioningEveryone = model.MentionEveryone.Value; | _isMentioningEveryone = model.MentionEveryone.Value; | ||||
| if (model.Flags.IsSpecified) | |||||
| { | |||||
| _isSuppressed = model.Flags.Value.HasFlag(API.MessageFlags.Suppressed); | |||||
| } | |||||
| if (model.RoleMentions.IsSpecified) | if (model.RoleMentions.IsSpecified) | ||||
| _roleMentions = model.RoleMentions.Value.Select(x => guild.GetRole(x)).ToImmutableArray(); | _roleMentions = model.RoleMentions.Value.Select(x => guild.GetRole(x)).ToImmutableArray(); | ||||
| @@ -36,6 +36,8 @@ namespace Discord.WebSocket | |||||
| public GuildPermissions Permissions { get; private set; } | public GuildPermissions Permissions { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public int Position { get; private set; } | public int Position { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public RoleTags Tags { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | ||||
| @@ -71,6 +73,8 @@ namespace Discord.WebSocket | |||||
| Position = model.Position; | Position = model.Position; | ||||
| Color = new Color(model.Color); | Color = new Color(model.Color); | ||||
| Permissions = new GuildPermissions(model.Permissions); | Permissions = new GuildPermissions(model.Permissions); | ||||
| if (model.Tags.IsSpecified) | |||||
| Tags = model.Tags.Value.ToEntity(); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -57,6 +57,8 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public bool IsStreaming => VoiceState?.IsStreaming ?? false; | public bool IsStreaming => VoiceState?.IsStreaming ?? false; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public bool? IsPending { get; private set; } | |||||
| /// <inheritdoc /> | |||||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns a collection of roles that the user possesses. | /// Returns a collection of roles that the user possesses. | ||||
| @@ -142,6 +144,8 @@ namespace Discord.WebSocket | |||||
| UpdateRoles(model.Roles.Value); | UpdateRoles(model.Roles.Value); | ||||
| if (model.PremiumSince.IsSpecified) | if (model.PremiumSince.IsSpecified) | ||||
| _premiumSinceTicks = model.PremiumSince.Value?.UtcTicks; | _premiumSinceTicks = model.PremiumSince.Value?.UtcTicks; | ||||
| if (model.Pending.IsSpecified) | |||||
| IsPending = model.Pending.Value; | |||||
| } | } | ||||
| internal void Update(ClientState state, PresenceModel model, bool updatePresence) | internal void Update(ClientState state, PresenceModel model, bool updatePresence) | ||||
| { | { | ||||
| @@ -26,6 +26,8 @@ namespace Discord.WebSocket | |||||
| public abstract string AvatarId { get; internal set; } | public abstract string AvatarId { get; internal set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public abstract bool IsWebhook { get; } | public abstract bool IsWebhook { get; } | ||||
| /// <inheritdoc /> | |||||
| public UserProperties? PublicFlags { get; private set; } | |||||
| internal abstract SocketGlobalUser GlobalUser { get; } | internal abstract SocketGlobalUser GlobalUser { get; } | ||||
| internal abstract SocketPresence Presence { get; set; } | internal abstract SocketPresence Presence { get; set; } | ||||
| @@ -83,6 +85,11 @@ namespace Discord.WebSocket | |||||
| Username = model.Username.Value; | Username = model.Username.Value; | ||||
| hasChanges = true; | hasChanges = true; | ||||
| } | } | ||||
| if (model.PublicFlags.IsSpecified && model.PublicFlags.Value != PublicFlags) | |||||
| { | |||||
| PublicFlags = model.PublicFlags.Value; | |||||
| hasChanges = true; | |||||
| } | |||||
| return hasChanges; | return hasChanges; | ||||
| } | } | ||||
| @@ -65,6 +65,8 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| DateTimeOffset? IGuildUser.PremiumSince => null; | DateTimeOffset? IGuildUser.PremiumSince => null; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| bool? IGuildUser.IsPending => null; | |||||
| /// <inheritdoc /> | |||||
| GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; | GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Webhook; | using Model = Discord.API.Webhook; | ||||
| @@ -11,9 +11,9 @@ namespace Discord.Webhook | |||||
| private DiscordWebhookClient _client; | private DiscordWebhookClient _client; | ||||
| public ulong Id { get; } | public ulong Id { get; } | ||||
| public ulong ChannelId { get; } | |||||
| public string Token { get; } | public string Token { get; } | ||||
| public ulong ChannelId { get; private set; } | |||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| public string AvatarId { get; private set; } | public string AvatarId { get; private set; } | ||||
| public ulong? GuildId { get; private set; } | public ulong? GuildId { get; private set; } | ||||
| @@ -36,6 +36,8 @@ namespace Discord.Webhook | |||||
| internal void Update(Model model) | internal void Update(Model model) | ||||
| { | { | ||||
| if (ChannelId != model.ChannelId) | |||||
| ChannelId = model.ChannelId; | |||||
| if (model.Avatar.IsSpecified) | if (model.Avatar.IsSpecified) | ||||
| AvatarId = model.Avatar.Value; | AvatarId = model.Avatar.Value; | ||||
| if (model.GuildId.IsSpecified) | if (model.GuildId.IsSpecified) | ||||
| @@ -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.3.0-dev$suffix$</version> | |||||
| <version>2.4.0$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.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Rest" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.WebSocket" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Commands" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Webhook" version="2.3.0-dev$suffix$" /> | |||||
| <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$" /> | |||||
| </group> | </group> | ||||
| <group targetFramework="netstandard2.0"> | <group targetFramework="netstandard2.0"> | ||||
| <dependency id="Discord.Net.Core" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Rest" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.WebSocket" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Commands" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Webhook" version="2.3.0-dev$suffix$" /> | |||||
| <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$" /> | |||||
| </group> | </group> | ||||
| <group targetFramework="netstandard2.1"> | <group targetFramework="netstandard2.1"> | ||||
| <dependency id="Discord.Net.Core" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Rest" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.WebSocket" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Commands" version="2.3.0-dev$suffix$" /> | |||||
| <dependency id="Discord.Net.Webhook" version="2.3.0-dev$suffix$" /> | |||||
| <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$" /> | |||||
| </group> | </group> | ||||
| </dependencies> | </dependencies> | ||||
| </metadata> | </metadata> | ||||