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