| @@ -1,5 +1,108 @@ | |||
| # 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 | |||
| ### Added | |||
| - #1247 Implement Client Status Support (9da11b4) | |||
| @@ -1,6 +1,6 @@ | |||
| <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
| <PropertyGroup> | |||
| <VersionPrefix>2.3.0</VersionPrefix> | |||
| <VersionPrefix>2.4.0</VersionPrefix> | |||
| <VersionSuffix>dev</VersionSuffix> | |||
| <LangVersion>latest</LangVersion> | |||
| <Authors>Discord.Net Contributors</Authors> | |||
| @@ -8,8 +8,6 @@ An unofficial .NET API Wrapper for the Discord client (https://discord.com). | |||
| ## Documentation | |||
| - [Stable](https://discord.foxbot.me/) | |||
| - Hosted by @foxbot | |||
| - [Nightly](https://docs.stillu.cc/) | |||
| - [Latest CI repo](https://github.com/discord-net/docs-static) | |||
| @@ -9,7 +9,7 @@ namespace _03_sharded_client.Modules | |||
| [Command("info")] | |||
| 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}"; | |||
| await ReplyAsync(msg); | |||
| } | |||
| @@ -136,7 +136,7 @@ namespace Discord.Commands | |||
| builder.Name = typeInfo.Name; | |||
| // 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) | |||
| { | |||
| @@ -92,10 +92,10 @@ namespace Discord | |||
| /// Gets all embeds included in this message. | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// </remarks> | |||
| /// 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 | |||
| /// are generated; however, only one <see cref="EmbedType.Rich"/> <see cref="Embed"/> can be featured. | |||
| /// </remarks> | |||
| /// <returns> | |||
| /// A read-only collection of embed objects. | |||
| /// </returns> | |||
| @@ -171,6 +171,17 @@ namespace Discord | |||
| /// A read-only collection of sticker objects. | |||
| /// </returns> | |||
| 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> | |||
| /// 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. | |||
| /// </summary> | |||
| 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. | |||
| /// </returns> | |||
| 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> | |||
| /// 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> | |||
| IReadOnlyCollection<ulong> RoleIds { get; } | |||
| /// <summary> | |||
| /// Whether the user has passed the guild's Membership Screening requirements. | |||
| /// </summary> | |||
| bool? IsPending { get; } | |||
| /// <summary> | |||
| /// Gets the level permissions granted to this user to a given channel. | |||
| /// </summary> | |||
| @@ -75,6 +75,16 @@ namespace Discord | |||
| /// Gets the username for this user. | |||
| /// </summary> | |||
| 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> | |||
| /// Gets the direct message channel of this user, or create one if it does not already exist. | |||
| @@ -10,32 +10,62 @@ namespace Discord | |||
| /// </summary> | |||
| None = 0, | |||
| /// <summary> | |||
| /// Flag given to Discord staff. | |||
| /// Flag given to users who are a Discord employee. | |||
| /// </summary> | |||
| Staff = 0b1, | |||
| Staff = 1 << 0, | |||
| /// <summary> | |||
| /// Flag given to Discord partners. | |||
| /// Flag given to users who are owners of a partnered Discord server. | |||
| /// </summary> | |||
| Partner = 0b10, | |||
| Partner = 1 << 1, | |||
| /// <summary> | |||
| /// Flag given to users in HypeSquad events. | |||
| /// </summary> | |||
| HypeSquadEvents = 1 << 2, | |||
| /// <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. | |||
| /// </summary> | |||
| BugHunter = 0b1000, | |||
| BugHunterLevel1 = 1 << 3, | |||
| /// <summary> | |||
| /// Flag given to users who are in the HypeSquad House of Bravery. | |||
| /// </summary> | |||
| HypeSquadBravery = 0b100_0000, | |||
| HypeSquadBravery = 1 << 6, | |||
| /// <summary> | |||
| /// Flag given to users who are in the HypeSquad House of Brilliance. | |||
| /// </summary> | |||
| HypeSquadBrilliance = 0b1000_0000, | |||
| HypeSquadBrilliance = 1 << 7, | |||
| /// <summary> | |||
| /// Flag given to users who are in the HypeSquad House of Balance. | |||
| /// </summary> | |||
| HypeSquadBalance = 0b1_0000_0000, | |||
| HypeSquadBalance = 1 << 8, | |||
| /// <summary> | |||
| /// Flag given to users who subscribed to Nitro before games were added. | |||
| /// </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 | |||
| { | |||
| public class AllowedMentions | |||
| internal class AllowedMentions | |||
| { | |||
| [JsonProperty("parse")] | |||
| public Optional<string[]> Parse { get; set; } | |||
| @@ -7,7 +7,7 @@ namespace Discord.API | |||
| [JsonProperty("target_id")] | |||
| public ulong? TargetId { get; set; } | |||
| [JsonProperty("user_id")] | |||
| public ulong UserId { get; set; } | |||
| public ulong? UserId { get; set; } | |||
| [JsonProperty("changes")] | |||
| public AuditLogChange[] Changes { get; set; } | |||
| @@ -18,6 +18,8 @@ namespace Discord.API | |||
| public Optional<bool> Deaf { get; set; } | |||
| [JsonProperty("mute")] | |||
| public Optional<bool> Mute { get; set; } | |||
| [JsonProperty("pending")] | |||
| public Optional<bool> Pending { get; set; } | |||
| [JsonProperty("premium_since")] | |||
| 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; | |||
| namespace Discord.API | |||
| @@ -21,5 +21,7 @@ namespace Discord.API | |||
| public ulong Permissions { get; set; } | |||
| [JsonProperty("managed")] | |||
| 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; } | |||
| [JsonProperty("locale")] | |||
| 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; | |||
| namespace Discord.API.Rest | |||
| @@ -10,5 +10,9 @@ namespace Discord.API.Rest | |||
| public Optional<string> Content { get; set; } | |||
| [JsonProperty("embed")] | |||
| 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; | |||
| if (IsSpoiler) | |||
| payload["hasSpoiler"] = IsSpoiler.ToString(); | |||
| if (MessageReference.IsSpecified) | |||
| payload["message_reference"] = MessageReference.Value; | |||
| var json = new StringBuilder(); | |||
| using (var text = new StringWriter(json)) | |||
| @@ -5,13 +5,14 @@ namespace Discord.Rest | |||
| /// </summary> | |||
| 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; | |||
| Topic = topic; | |||
| SlowModeInterval = rateLimit; | |||
| IsNsfw = nsfw; | |||
| Bitrate = bitrate; | |||
| ChannelType = type; | |||
| } | |||
| /// <summary> | |||
| @@ -53,5 +54,12 @@ namespace Discord.Rest | |||
| /// <c>null</c> if this is not mentioned in this entry. | |||
| /// </returns> | |||
| 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 nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw"); | |||
| 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), | |||
| newName = nameModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
| @@ -37,9 +38,11 @@ namespace Discord.Rest | |||
| newNsfw = nsfwModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
| int? oldBitrate = bitrateModel?.OldValue?.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); | |||
| } | |||
| @@ -19,8 +19,14 @@ namespace Discord.Rest | |||
| 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> | |||
| @@ -38,10 +44,10 @@ namespace Discord.Rest | |||
| /// </returns> | |||
| public ulong ChannelId { get; } | |||
| /// <summary> | |||
| /// Gets the user of the message that was pinned. | |||
| /// Gets the user of the message that was pinned if available. | |||
| /// </summary> | |||
| /// <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> | |||
| public IUser Target { get; } | |||
| } | |||
| @@ -19,8 +19,14 @@ namespace Discord.Rest | |||
| 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> | |||
| @@ -38,10 +44,10 @@ namespace Discord.Rest | |||
| /// </returns> | |||
| public ulong ChannelId { get; } | |||
| /// <summary> | |||
| /// Gets the user of the message that was unpinned. | |||
| /// Gets the user of the message that was unpinned if available. | |||
| /// </summary> | |||
| /// <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> | |||
| public IUser Target { get; } | |||
| } | |||
| @@ -22,7 +22,7 @@ namespace Discord.Rest | |||
| 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; | |||
| if (userInfo != null) | |||
| 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, | |||
| 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(); | |||
| 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 hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : msg.Embeds.Any(); | |||
| if (!hasText && !hasEmbed) | |||
| 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 | |||
| { | |||
| 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); | |||
| } | |||
| @@ -69,6 +69,8 @@ namespace Discord.Rest | |||
| public MessageApplication Application { get; private set; } | |||
| /// <inheritdoc /> | |||
| 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) | |||
| : base(discord, id) | |||
| @@ -126,6 +128,9 @@ namespace Discord.Rest | |||
| }; | |||
| } | |||
| if (model.Flags.IsSpecified) | |||
| Flags = model.Flags.Value; | |||
| if (model.Reactions.IsSpecified) | |||
| { | |||
| var value = model.Reactions.Value; | |||
| @@ -13,7 +13,7 @@ namespace Discord.Rest | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class RestUserMessage : RestMessage, IUserMessage | |||
| { | |||
| private bool _isMentioningEveryone, _isTTS, _isPinned, _isSuppressed; | |||
| private bool _isMentioningEveryone, _isTTS, _isPinned; | |||
| private long? _editedTimestampTicks; | |||
| private IUserMessage _referencedMessage; | |||
| private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | |||
| @@ -28,7 +28,7 @@ namespace Discord.Rest | |||
| /// <inheritdoc /> | |||
| public override bool IsPinned => _isPinned; | |||
| /// <inheritdoc /> | |||
| public override bool IsSuppressed => _isSuppressed; | |||
| public override bool IsSuppressed => Flags.HasValue && Flags.Value.HasFlag(MessageFlags.SuppressEmbeds); | |||
| /// <inheritdoc /> | |||
| public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | |||
| /// <inheritdoc /> | |||
| @@ -73,10 +73,6 @@ namespace Discord.Rest | |||
| _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | |||
| if (model.MentionEveryone.IsSpecified) | |||
| _isMentioningEveryone = model.MentionEveryone.Value; | |||
| if (model.Flags.IsSpecified) | |||
| { | |||
| _isSuppressed = model.Flags.Value.HasFlag(API.MessageFlags.Suppressed); | |||
| } | |||
| if (model.RoleMentions.IsSpecified) | |||
| _roleMentionIds = model.RoleMentions.Value.ToImmutableArray(); | |||
| @@ -26,6 +26,8 @@ namespace Discord.Rest | |||
| public GuildPermissions Permissions { get; private set; } | |||
| /// <inheritdoc /> | |||
| public int Position { get; private set; } | |||
| /// <inheritdoc /> | |||
| public RoleTags Tags { get; private set; } | |||
| /// <inheritdoc /> | |||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
| @@ -56,6 +58,8 @@ namespace Discord.Rest | |||
| Position = model.Position; | |||
| Color = new Color(model.Color); | |||
| Permissions = new GuildPermissions(model.Permissions); | |||
| if (model.Tags.IsSpecified) | |||
| Tags = model.Tags.Value.ToEntity(); | |||
| } | |||
| /// <inheritdoc /> | |||
| @@ -29,6 +29,8 @@ namespace Discord.Rest | |||
| public DateTimeOffset? PremiumSince => DateTimeUtils.FromTicks(_premiumSinceTicks); | |||
| /// <inheritdoc /> | |||
| public ulong GuildId => Guild.Id; | |||
| /// <inheritdoc /> | |||
| public bool? IsPending { get; private set; } | |||
| /// <inheritdoc /> | |||
| /// <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); | |||
| if (model.PremiumSince.IsSpecified) | |||
| _premiumSinceTicks = model.PremiumSince.Value?.UtcTicks; | |||
| if (model.Pending.IsSpecified) | |||
| IsPending = model.Pending.Value; | |||
| } | |||
| private void UpdateRoles(ulong[] roleIds) | |||
| { | |||
| @@ -21,6 +21,8 @@ namespace Discord.Rest | |||
| public ushort DiscriminatorValue { get; private set; } | |||
| /// <inheritdoc /> | |||
| public string AvatarId { get; private set; } | |||
| /// <inheritdoc /> | |||
| public UserProperties? PublicFlags { get; private set; } | |||
| /// <inheritdoc /> | |||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
| @@ -65,6 +67,8 @@ namespace Discord.Rest | |||
| IsBot = model.Bot.Value; | |||
| if (model.Username.IsSpecified) | |||
| Username = model.Username.Value; | |||
| if (model.PublicFlags.IsSpecified) | |||
| PublicFlags = model.PublicFlags.Value; | |||
| } | |||
| /// <inheritdoc /> | |||
| @@ -52,6 +52,8 @@ namespace Discord.Rest | |||
| /// <inheritdoc /> | |||
| string IGuildUser.Nickname => null; | |||
| /// <inheritdoc /> | |||
| bool? IGuildUser.IsPending => null; | |||
| /// <inheritdoc /> | |||
| GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; | |||
| /// <inheritdoc /> | |||
| @@ -11,11 +11,11 @@ namespace Discord.Rest | |||
| internal IGuild Guild { get; private set; } | |||
| internal ITextChannel Channel { get; private set; } | |||
| /// <inheritdoc /> | |||
| public ulong ChannelId { get; } | |||
| /// <inheritdoc /> | |||
| public string Token { get; } | |||
| /// <inheritdoc /> | |||
| public ulong ChannelId { get; private set; } | |||
| /// <inheritdoc /> | |||
| public string Name { get; private set; } | |||
| /// <inheritdoc /> | |||
| @@ -56,6 +56,8 @@ namespace Discord.Rest | |||
| internal void Update(Model model) | |||
| { | |||
| if (ChannelId != model.ChannelId) | |||
| ChannelId = model.ChannelId; | |||
| if (model.Avatar.IsSpecified) | |||
| AvatarId = model.Avatar.Value; | |||
| if (model.Creator.IsSpecified) | |||
| @@ -34,6 +34,13 @@ namespace Discord.Rest | |||
| model.Thumbnail.IsSpecified ? model.Thumbnail.Value.ToEntity() : (EmbedThumbnail?)null, | |||
| 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) | |||
| { | |||
| if (entity == null) return null; | |||
| @@ -1,3 +1,4 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Threading.Tasks; | |||
| @@ -75,6 +76,7 @@ namespace Discord.WebSocket | |||
| /// <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) | |||
| @@ -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 | |||
| /// found. | |||
| /// </returns> | |||
| [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||
| public abstract RestVoiceRegion GetVoiceRegion(string id); | |||
| /// <summary> | |||
| /// Gets all voice regions. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| /// <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 /> | |||
| public abstract Task StartAsync(); | |||
| /// <inheritdoc /> | |||
| @@ -188,6 +209,12 @@ namespace Discord.WebSocket | |||
| /// <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="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> | |||
| /// A task that represents the asynchronous set operation. | |||
| /// </returns> | |||
| @@ -201,6 +228,10 @@ namespace Discord.WebSocket | |||
| /// Discord will only accept setting of name and the type of activity. | |||
| /// </note> | |||
| /// <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 | |||
| /// clients only. | |||
| /// </note> | |||
| @@ -37,6 +37,7 @@ namespace Discord.WebSocket | |||
| public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); | |||
| public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | |||
| /// <inheritdoc /> | |||
| [Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] | |||
| public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | |||
| /// <summary> | |||
| @@ -264,9 +265,22 @@ namespace Discord.WebSocket | |||
| } | |||
| /// <inheritdoc /> | |||
| [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||
| public override RestVoiceRegion GetVoiceRegion(string id) | |||
| => _shards[0].GetVoiceRegion(id); | |||
| /// <inheritdoc /> | |||
| public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | |||
| { | |||
| 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 /> | |||
| /// <exception cref="ArgumentNullException"><paramref name="guilds"/> is <see langword="null"/></exception> | |||
| public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | |||
| @@ -110,7 +110,8 @@ namespace Discord.WebSocket | |||
| public IReadOnlyCollection<SocketGroupChannel> GroupChannels | |||
| => State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); | |||
| /// <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> | |||
| /// Initializes a new REST/WebSocket-based Discord client. | |||
| @@ -178,7 +179,6 @@ namespace Discord.WebSocket | |||
| return Task.Delay(0); | |||
| }; | |||
| _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||
| _largeGuilds = new ConcurrentQueue<ulong>(); | |||
| } | |||
| private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
| @@ -204,13 +204,6 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| 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); | |||
| } | |||
| /// <inheritdoc /> | |||
| @@ -218,7 +211,7 @@ namespace Discord.WebSocket | |||
| { | |||
| await StopAsync().ConfigureAwait(false); | |||
| _applicationInfo = null; | |||
| _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||
| _voiceRegions = null; | |||
| await Rest.OnLogoutAsync(); | |||
| } | |||
| @@ -252,15 +245,15 @@ namespace Discord.WebSocket | |||
| await _gatewayLogger.DebugAsync("Identifying").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 | |||
| { | |||
| if (locked) | |||
| _shardedClient.ReleaseIdentifyLock(); | |||
| } | |||
| //Wait for READY | |||
| await _connection.WaitAsync().ConfigureAwait(false); | |||
| } | |||
| private async Task OnDisconnectingAsync(Exception ex) | |||
| { | |||
| @@ -350,11 +343,39 @@ namespace Discord.WebSocket | |||
| => State.RemoveUser(id); | |||
| /// <inheritdoc /> | |||
| [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||
| public override RestVoiceRegion GetVoiceRegion(string id) | |||
| => GetVoiceRegionAsync(id).GetAwaiter().GetResult(); | |||
| /// <inheritdoc /> | |||
| public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | |||
| { | |||
| 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 /> | |||
| @@ -611,7 +632,7 @@ namespace Discord.WebSocket | |||
| } | |||
| else if (_connection.CancelToken.IsCancellationRequested) | |||
| return; | |||
| if (BaseConfig.AlwaysDownloadUsers) | |||
| _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); | |||
| @@ -2120,11 +2141,11 @@ namespace Discord.WebSocket | |||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
| /// <inheritdoc /> | |||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | |||
| async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| => await GetVoiceRegionsAsync(options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||
| async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| => await GetVoiceRegionAsync(id, options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| async Task IDiscordClient.StartAsync() | |||
| @@ -58,6 +58,9 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public MessageReference Reference { get; private set; } | |||
| /// <inheritdoc /> | |||
| public MessageFlags? Flags { get; private set; } | |||
| /// <summary> | |||
| /// Returns all attachments included in this message. | |||
| /// </summary> | |||
| @@ -158,6 +161,9 @@ namespace Discord.WebSocket | |||
| MessageId = model.Reference.Value.MessageId | |||
| }; | |||
| } | |||
| if (model.Flags.IsSpecified) | |||
| Flags = model.Flags.Value; | |||
| } | |||
| /// <inheritdoc /> | |||
| @@ -15,7 +15,7 @@ namespace Discord.WebSocket | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketUserMessage : SocketMessage, IUserMessage | |||
| { | |||
| private bool _isMentioningEveryone, _isTTS, _isPinned, _isSuppressed; | |||
| private bool _isMentioningEveryone, _isTTS, _isPinned; | |||
| private long? _editedTimestampTicks; | |||
| private IUserMessage _referencedMessage; | |||
| private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | |||
| @@ -30,7 +30,7 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public override bool IsPinned => _isPinned; | |||
| /// <inheritdoc /> | |||
| public override bool IsSuppressed => _isSuppressed; | |||
| public override bool IsSuppressed => Flags.HasValue && Flags.Value.HasFlag(MessageFlags.SuppressEmbeds); | |||
| /// <inheritdoc /> | |||
| public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | |||
| /// <inheritdoc /> | |||
| @@ -77,10 +77,6 @@ namespace Discord.WebSocket | |||
| _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | |||
| if (model.MentionEveryone.IsSpecified) | |||
| _isMentioningEveryone = model.MentionEveryone.Value; | |||
| if (model.Flags.IsSpecified) | |||
| { | |||
| _isSuppressed = model.Flags.Value.HasFlag(API.MessageFlags.Suppressed); | |||
| } | |||
| if (model.RoleMentions.IsSpecified) | |||
| _roleMentions = model.RoleMentions.Value.Select(x => guild.GetRole(x)).ToImmutableArray(); | |||
| @@ -36,6 +36,8 @@ namespace Discord.WebSocket | |||
| public GuildPermissions Permissions { get; private set; } | |||
| /// <inheritdoc /> | |||
| public int Position { get; private set; } | |||
| /// <inheritdoc /> | |||
| public RoleTags Tags { get; private set; } | |||
| /// <inheritdoc /> | |||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
| @@ -71,6 +73,8 @@ namespace Discord.WebSocket | |||
| Position = model.Position; | |||
| Color = new Color(model.Color); | |||
| Permissions = new GuildPermissions(model.Permissions); | |||
| if (model.Tags.IsSpecified) | |||
| Tags = model.Tags.Value.ToEntity(); | |||
| } | |||
| /// <inheritdoc /> | |||
| @@ -57,6 +57,8 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public bool IsStreaming => VoiceState?.IsStreaming ?? false; | |||
| /// <inheritdoc /> | |||
| public bool? IsPending { get; private set; } | |||
| /// <inheritdoc /> | |||
| public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | |||
| /// <summary> | |||
| /// Returns a collection of roles that the user possesses. | |||
| @@ -142,6 +144,8 @@ namespace Discord.WebSocket | |||
| UpdateRoles(model.Roles.Value); | |||
| if (model.PremiumSince.IsSpecified) | |||
| _premiumSinceTicks = model.PremiumSince.Value?.UtcTicks; | |||
| if (model.Pending.IsSpecified) | |||
| IsPending = model.Pending.Value; | |||
| } | |||
| internal void Update(ClientState state, PresenceModel model, bool updatePresence) | |||
| { | |||
| @@ -26,6 +26,8 @@ namespace Discord.WebSocket | |||
| public abstract string AvatarId { get; internal set; } | |||
| /// <inheritdoc /> | |||
| public abstract bool IsWebhook { get; } | |||
| /// <inheritdoc /> | |||
| public UserProperties? PublicFlags { get; private set; } | |||
| internal abstract SocketGlobalUser GlobalUser { get; } | |||
| internal abstract SocketPresence Presence { get; set; } | |||
| @@ -83,6 +85,11 @@ namespace Discord.WebSocket | |||
| Username = model.Username.Value; | |||
| hasChanges = true; | |||
| } | |||
| if (model.PublicFlags.IsSpecified && model.PublicFlags.Value != PublicFlags) | |||
| { | |||
| PublicFlags = model.PublicFlags.Value; | |||
| hasChanges = true; | |||
| } | |||
| return hasChanges; | |||
| } | |||
| @@ -65,6 +65,8 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| DateTimeOffset? IGuildUser.PremiumSince => null; | |||
| /// <inheritdoc /> | |||
| bool? IGuildUser.IsPending => null; | |||
| /// <inheritdoc /> | |||
| GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; | |||
| /// <inheritdoc /> | |||
| @@ -1,4 +1,4 @@ | |||
| using System; | |||
| using System; | |||
| using System.Diagnostics; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Webhook; | |||
| @@ -11,9 +11,9 @@ namespace Discord.Webhook | |||
| private DiscordWebhookClient _client; | |||
| public ulong Id { get; } | |||
| public ulong ChannelId { get; } | |||
| public string Token { get; } | |||
| public ulong ChannelId { get; private set; } | |||
| public string Name { get; private set; } | |||
| public string AvatarId { get; private set; } | |||
| public ulong? GuildId { get; private set; } | |||
| @@ -36,6 +36,8 @@ namespace Discord.Webhook | |||
| internal void Update(Model model) | |||
| { | |||
| if (ChannelId != model.ChannelId) | |||
| ChannelId = model.ChannelId; | |||
| if (model.Avatar.IsSpecified) | |||
| AvatarId = model.Avatar.Value; | |||
| if (model.GuildId.IsSpecified) | |||
| @@ -2,7 +2,7 @@ | |||
| <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | |||
| <metadata> | |||
| <id>Discord.Net</id> | |||
| <version>2.3.0-dev$suffix$</version> | |||
| <version>2.4.0$suffix$</version> | |||
| <title>Discord.Net</title> | |||
| <authors>Discord.Net Contributors</authors> | |||
| <owners>foxbot</owners> | |||
| @@ -14,25 +14,25 @@ | |||
| <iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl> | |||
| <dependencies> | |||
| <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 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 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> | |||
| </dependencies> | |||
| </metadata> | |||