diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 778d2150..7732d50f 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -1475,5 +1475,10 @@ namespace Discord /// Gets a mapping of role IDs to the number of users that have each role. /// Task> GetRoleUserCountsAsync(RequestOptions options = null); + + /// + /// Modifies the current user in this guild. + /// + Task ModifyCurrentUserAsync(Action props, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Users/SelfGuildUserProperties.cs b/src/Discord.Net.Core/Entities/Users/SelfGuildUserProperties.cs new file mode 100644 index 00000000..551ef792 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Users/SelfGuildUserProperties.cs @@ -0,0 +1,22 @@ +namespace Discord; + +/// +/// Represents properties used to modify the current user's guild-specific information. +/// +public class SelfGuildUserProperties : GuildUserProperties +{ + /// + /// Gets or sets the banner image of the current user. + /// + public Optional Banner { get; set; } + + /// + /// Gets or sets the avatar image of the current user. + /// + public Optional Avatar { get; set; } + + /// + /// Gets or sets the user's biography text. + /// + public Optional Bio { get; set; } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyCurrentMemberParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyCurrentMemberParams.cs new file mode 100644 index 00000000..3bdea47f --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyCurrentMemberParams.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest; + +internal class ModifyCurrentMemberParams +{ + [JsonProperty("nick")] + public Optional Nickname { get; set; } + + [JsonProperty("banner")] + public Optional Banner { get; set; } + + [JsonProperty("avatar")] + public Optional Avatar { get; set; } + + [JsonProperty("bio")] + public Optional Bio { get; set; } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs deleted file mode 100644 index c10f2e4e..00000000 --- a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Newtonsoft.Json; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - internal class ModifyCurrentUserNickParams - { - [JsonProperty("nick")] - public string Nickname { get; } - - public ModifyCurrentUserNickParams(string nickname) - { - Nickname = nickname; - } - } -} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs index ad2b91f2..d2a3ef45 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs @@ -1,25 +1,28 @@ using Newtonsoft.Json; using System; -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - internal class ModifyGuildMemberParams - { - [JsonProperty("mute")] - public Optional Mute { get; set; } - [JsonProperty("deaf")] - public Optional Deaf { get; set; } - [JsonProperty("nick")] - public Optional Nickname { get; set; } - [JsonProperty("roles")] - public Optional RoleIds { get; set; } - [JsonProperty("channel_id")] - public Optional ChannelId { get; set; } - [JsonProperty("communication_disabled_until")] - public Optional TimedOutUntil { get; set; } +namespace Discord.API.Rest; - [JsonProperty("flags")] - public Optional Flags { get; set; } - } +internal class ModifyGuildMemberParams +{ + [JsonProperty("mute")] + public Optional Mute { get; set; } + + [JsonProperty("deaf")] + public Optional Deaf { get; set; } + + [JsonProperty("nick")] + public Optional Nickname { get; set; } + + [JsonProperty("roles")] + public Optional RoleIds { get; set; } + + [JsonProperty("channel_id")] + public Optional ChannelId { get; set; } + + [JsonProperty("communication_disabled_until")] + public Optional TimedOutUntil { get; set; } + + [JsonProperty("flags")] + public Optional Flags { get; set; } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 7050f917..a89ca648 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -2022,7 +2022,7 @@ namespace Discord.API return SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}", ids, options: options); } - public async Task ModifyGuildMemberAsync(ulong guildId, ulong userId, Rest.ModifyGuildMemberParams args, RequestOptions options = null) + public async Task ModifyGuildMemberAsync(ulong guildId, ulong userId, ModifyGuildMemberParams args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); @@ -2030,17 +2030,19 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); bool isCurrentUser = userId == CurrentUserId; - if (isCurrentUser && args.Nickname.IsSpecified) { - var nickArgs = new Rest.ModifyCurrentUserNickParams(args.Nickname.Value ?? ""); - await ModifyMyNickAsync(guildId, nickArgs).ConfigureAwait(false); - args.Nickname = Optional.Create(); //Remove + var nickArgs = new ModifyCurrentMemberParams + { + Nickname = args.Nickname + }; + await ModifyCurrentMemberAsync(guildId, nickArgs).ConfigureAwait(false); + args.Nickname = Optional.Create(); // Remove so it's not getting updated again } if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.RoleIds.IsSpecified) { var ids = new BucketIds(guildId: guildId); - await SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/{userId}", args, ids, options: options).ConfigureAwait(false); + await SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/{userId}", args, ids, options: options).ConfigureAwait(false); } } @@ -2454,14 +2456,14 @@ namespace Discord.API return SendJsonAsync("PATCH", () => "users/@me", args, new BucketIds(), options: options); } - public Task ModifyMyNickAsync(ulong guildId, Rest.ModifyCurrentUserNickParams args, RequestOptions options = null) + public Task ModifyCurrentMemberAsync(ulong guildId, ModifyCurrentMemberParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); - Preconditions.NotNull(args.Nickname, nameof(args.Nickname)); + options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/@me/nick", args, ids, options: options); + return SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/@me", args, ids, options: options); } public Task CreateDMChannelAsync(CreateDMChannelParams args, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 429d0311..9c87862b 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -991,6 +991,15 @@ namespace Discord.Rest /// public Task SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null) => GuildHelper.SearchUsersAsyncV2(this, Discord, limit, args, options); + + /// + public Task ModifyCurrentUserAsync(Action props, RequestOptions options = null) + { + var args = new SelfGuildUserProperties(); + props(args); + return UserHelper.ModifyCurrentUserAsync(this, Discord.CurrentUser, Discord, args, options); + } + #endregion #region Audit logs diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index b37d4343..2783c04c 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -148,7 +148,10 @@ namespace Discord.Rest /// public async Task ModifyAsync(Action func, RequestOptions options = null) { - var args = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); + var args = new GuildUserProperties(); + func(args); + + args = await UserHelper.ModifyAsync(Guild, this, Discord, args, options).ConfigureAwait(false); if (args.Deaf.IsSpecified) IsDeafened = args.Deaf.Value; if (args.Mute.IsSpecified) diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index b0292adc..bc4a01d2 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -26,16 +26,14 @@ namespace Discord.Rest return client.ApiClient.ModifySelfAsync(apiArgs, options); } - public static async Task ModifyAsync(IGuildUser user, BaseDiscordClient client, Action func, + + public static async Task ModifyAsync(IGuild guild, IUser user, BaseDiscordClient client, GuildUserProperties args, RequestOptions options) { - var args = new GuildUserProperties(); - func(args); - if (args.TimedOutUntil.IsSpecified && args.TimedOutUntil.Value.Value.Offset > (new TimeSpan(28, 0, 0, 0))) throw new ArgumentOutOfRangeException(nameof(args.TimedOutUntil), "Offset cannot be more than 28 days from the current date."); - var apiArgs = new API.Rest.ModifyGuildMemberParams + var apiArgs = new ModifyGuildMemberParams { Deaf = args.Deaf, Mute = args.Mute, @@ -59,13 +57,33 @@ namespace Discord.Rest * string.Empty ("") is the only way to reset the user nick in the API, * a value of null does not. This is a workaround. */ - if (apiArgs.Nickname.IsSpecified && apiArgs.Nickname.Value == null) + if (apiArgs.Nickname is { IsSpecified: true, Value: null }) apiArgs.Nickname = new Optional(string.Empty); - await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, apiArgs, options).ConfigureAwait(false); + await client.ApiClient.ModifyGuildMemberAsync(guild.Id, user.Id, apiArgs, options).ConfigureAwait(false); return args; } + public static async Task ModifyCurrentUserAsync(IGuild guild, IUser user, BaseDiscordClient client, SelfGuildUserProperties args, RequestOptions options) + { + if (args.Nickname.IsSpecified || args.Avatar.IsSpecified || args.Banner.IsSpecified || args.Bio.IsSpecified) + { + var props = new ModifyCurrentMemberParams + { + Nickname = args.Nickname, + Avatar = args.Avatar.IsSpecified ? args.Avatar.Value?.ToModel() : Optional.Unspecified, + Banner = args.Banner.IsSpecified ? args.Banner.Value?.ToModel() : Optional.Unspecified, + Bio = args.Bio + }; + + await client.ApiClient.ModifyCurrentMemberAsync(guild.Id, props, options).ConfigureAwait(false); + + args.Nickname = Optional.Unspecified; + } + + await ModifyAsync(guild, user, client, args, options); + } + public static Task KickAsync(IGuildUser user, BaseDiscordClient client, string reason, RequestOptions options) => client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, reason, options); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index ef8ed8cb..026991a2 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1343,6 +1343,15 @@ namespace Discord.WebSocket /// public Task SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null) => GuildHelper.SearchUsersAsyncV2(this, Discord, limit, args, options); + + /// + public Task ModifyCurrentUserAsync(Action props, RequestOptions options = null) + { + var args = new SelfGuildUserProperties(); + props(args); + return UserHelper.ModifyCurrentUserAsync(this, Discord.CurrentUser, Discord, args, options); + } + #endregion #region Guild Events diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 8a39fe46..9db6959d 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -241,7 +241,12 @@ namespace Discord.WebSocket /// public Task ModifyAsync(Action func, RequestOptions options = null) - => UserHelper.ModifyAsync(this, Discord, func, options); + { + var args = new GuildUserProperties(); + func(args); + return UserHelper.ModifyAsync(Guild, this, Discord, args, options); + } + /// public Task KickAsync(string reason = null, RequestOptions options = null) => UserHelper.KickAsync(this, Discord, reason, options);