[Feature] Add modify current guild member support (#3196)

* refactors & add `ModifyCurrentUserAsync`

* i mean, it said "remove", so I removed it
This commit is contained in:
Mihail Gribkov
2025-10-27 22:42:23 +03:00
committed by GitHub
parent dade9b2df6
commit 80fbbc2e2b
11 changed files with 132 additions and 54 deletions

View File

@@ -1475,5 +1475,10 @@ namespace Discord
/// Gets a mapping of role IDs to the number of users that have each role. /// Gets a mapping of role IDs to the number of users that have each role.
/// </summary> /// </summary>
Task<ImmutableDictionary<ulong, int>> GetRoleUserCountsAsync(RequestOptions options = null); Task<ImmutableDictionary<ulong, int>> GetRoleUserCountsAsync(RequestOptions options = null);
/// <summary>
/// Modifies the current user in this guild.
/// </summary>
Task ModifyCurrentUserAsync(Action<SelfGuildUserProperties> props, RequestOptions options = null);
} }
} }

View File

@@ -0,0 +1,22 @@
namespace Discord;
/// <summary>
/// Represents properties used to modify the current user's guild-specific information.
/// </summary>
public class SelfGuildUserProperties : GuildUserProperties
{
/// <summary>
/// Gets or sets the banner image of the current user.
/// </summary>
public Optional<Image?> Banner { get; set; }
/// <summary>
/// Gets or sets the avatar image of the current user.
/// </summary>
public Optional<Image?> Avatar { get; set; }
/// <summary>
/// Gets or sets the user's biography text.
/// </summary>
public Optional<string> Bio { get; set; }
}

View File

@@ -0,0 +1,18 @@
using Newtonsoft.Json;
namespace Discord.API.Rest;
internal class ModifyCurrentMemberParams
{
[JsonProperty("nick")]
public Optional<string> Nickname { get; set; }
[JsonProperty("banner")]
public Optional<Image?> Banner { get; set; }
[JsonProperty("avatar")]
public Optional<Image?> Avatar { get; set; }
[JsonProperty("bio")]
public Optional<string> Bio { get; set; }
}

View File

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

View File

@@ -1,25 +1,28 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
namespace Discord.API.Rest namespace Discord.API.Rest;
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class ModifyGuildMemberParams internal class ModifyGuildMemberParams
{ {
[JsonProperty("mute")] [JsonProperty("mute")]
public Optional<bool> Mute { get; set; } public Optional<bool> Mute { get; set; }
[JsonProperty("deaf")] [JsonProperty("deaf")]
public Optional<bool> Deaf { get; set; } public Optional<bool> Deaf { get; set; }
[JsonProperty("nick")] [JsonProperty("nick")]
public Optional<string> Nickname { get; set; } public Optional<string> Nickname { get; set; }
[JsonProperty("roles")] [JsonProperty("roles")]
public Optional<ulong[]> RoleIds { get; set; } public Optional<ulong[]> RoleIds { get; set; }
[JsonProperty("channel_id")] [JsonProperty("channel_id")]
public Optional<ulong?> ChannelId { get; set; } public Optional<ulong?> ChannelId { get; set; }
[JsonProperty("communication_disabled_until")] [JsonProperty("communication_disabled_until")]
public Optional<DateTimeOffset?> TimedOutUntil { get; set; } public Optional<DateTimeOffset?> TimedOutUntil { get; set; }
[JsonProperty("flags")] [JsonProperty("flags")]
public Optional<GuildUserFlags> Flags { get; set; } public Optional<GuildUserFlags> Flags { get; set; }
} }
}

View File

@@ -2022,7 +2022,7 @@ namespace Discord.API
return SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}", ids, options: options); 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(guildId, 0, nameof(guildId));
Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotEqual(userId, 0, nameof(userId));
@@ -2030,17 +2030,19 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
bool isCurrentUser = userId == CurrentUserId; bool isCurrentUser = userId == CurrentUserId;
if (isCurrentUser && args.Nickname.IsSpecified) if (isCurrentUser && args.Nickname.IsSpecified)
{ {
var nickArgs = new Rest.ModifyCurrentUserNickParams(args.Nickname.Value ?? ""); var nickArgs = new ModifyCurrentMemberParams
await ModifyMyNickAsync(guildId, nickArgs).ConfigureAwait(false); {
args.Nickname = Optional.Create<string>(); //Remove Nickname = args.Nickname
};
await ModifyCurrentMemberAsync(guildId, nickArgs).ConfigureAwait(false);
args.Nickname = Optional.Create<string>(); // Remove so it's not getting updated again
} }
if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.RoleIds.IsSpecified) if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.RoleIds.IsSpecified)
{ {
var ids = new BucketIds(guildId: guildId); var ids = new BucketIds(guildId: guildId);
await SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/{userId}", args, ids, options: options).ConfigureAwait(false); await SendJsonAsync<GuildMember>("PATCH", () => $"guilds/{guildId}/members/{userId}", args, ids, options: options).ConfigureAwait(false);
} }
} }
@@ -2454,14 +2456,14 @@ namespace Discord.API
return SendJsonAsync<User>("PATCH", () => "users/@me", args, new BucketIds(), options: options); return SendJsonAsync<User>("PATCH", () => "users/@me", args, new BucketIds(), options: options);
} }
public Task ModifyMyNickAsync(ulong guildId, Rest.ModifyCurrentUserNickParams args, RequestOptions options = null) public Task<GuildMember> ModifyCurrentMemberAsync(ulong guildId, ModifyCurrentMemberParams args, RequestOptions options = null)
{ {
Preconditions.NotNull(args, nameof(args)); Preconditions.NotNull(args, nameof(args));
Preconditions.NotNull(args.Nickname, nameof(args.Nickname));
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
var ids = new BucketIds(guildId: guildId); var ids = new BucketIds(guildId: guildId);
return SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/@me/nick", args, ids, options: options); return SendJsonAsync<GuildMember>("PATCH", () => $"guilds/{guildId}/members/@me", args, ids, options: options);
} }
public Task<Channel> CreateDMChannelAsync(CreateDMChannelParams args, RequestOptions options = null) public Task<Channel> CreateDMChannelAsync(CreateDMChannelParams args, RequestOptions options = null)

View File

@@ -991,6 +991,15 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
public Task<MemberSearchResult> SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null) public Task<MemberSearchResult> SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null)
=> GuildHelper.SearchUsersAsyncV2(this, Discord, limit, args, options); => GuildHelper.SearchUsersAsyncV2(this, Discord, limit, args, options);
/// <inheritdoc />
public Task ModifyCurrentUserAsync(Action<SelfGuildUserProperties> props, RequestOptions options = null)
{
var args = new SelfGuildUserProperties();
props(args);
return UserHelper.ModifyCurrentUserAsync(this, Discord.CurrentUser, Discord, args, options);
}
#endregion #endregion
#region Audit logs #region Audit logs

View File

@@ -148,7 +148,10 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
public async Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null) public async Task ModifyAsync(Action<GuildUserProperties> 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) if (args.Deaf.IsSpecified)
IsDeafened = args.Deaf.Value; IsDeafened = args.Deaf.Value;
if (args.Mute.IsSpecified) if (args.Mute.IsSpecified)

View File

@@ -26,16 +26,14 @@ namespace Discord.Rest
return client.ApiClient.ModifySelfAsync(apiArgs, options); return client.ApiClient.ModifySelfAsync(apiArgs, options);
} }
public static async Task<GuildUserProperties> ModifyAsync(IGuildUser user, BaseDiscordClient client, Action<GuildUserProperties> func,
public static async Task<GuildUserProperties> ModifyAsync(IGuild guild, IUser user, BaseDiscordClient client, GuildUserProperties args,
RequestOptions options) RequestOptions options)
{ {
var args = new GuildUserProperties();
func(args);
if (args.TimedOutUntil.IsSpecified && args.TimedOutUntil.Value.Value.Offset > (new TimeSpan(28, 0, 0, 0))) 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."); 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, Deaf = args.Deaf,
Mute = args.Mute, Mute = args.Mute,
@@ -59,13 +57,33 @@ namespace Discord.Rest
* string.Empty ("") is the only way to reset the user nick in the API, * string.Empty ("") is the only way to reset the user nick in the API,
* a value of null does not. This is a workaround. * 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>(string.Empty); apiArgs.Nickname = new Optional<string>(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; 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<ImageModel?>.Unspecified,
Banner = args.Banner.IsSpecified ? args.Banner.Value?.ToModel() : Optional<ImageModel?>.Unspecified,
Bio = args.Bio
};
await client.ApiClient.ModifyCurrentMemberAsync(guild.Id, props, options).ConfigureAwait(false);
args.Nickname = Optional<string>.Unspecified;
}
await ModifyAsync(guild, user, client, args, options);
}
public static Task KickAsync(IGuildUser user, BaseDiscordClient client, string reason, RequestOptions options) public static Task KickAsync(IGuildUser user, BaseDiscordClient client, string reason, RequestOptions options)
=> client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, reason, options); => client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, reason, options);

View File

@@ -1343,6 +1343,15 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public Task<MemberSearchResult> SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null) public Task<MemberSearchResult> SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null)
=> GuildHelper.SearchUsersAsyncV2(this, Discord, limit, args, options); => GuildHelper.SearchUsersAsyncV2(this, Discord, limit, args, options);
/// <inheritdoc />
public Task ModifyCurrentUserAsync(Action<SelfGuildUserProperties> props, RequestOptions options = null)
{
var args = new SelfGuildUserProperties();
props(args);
return UserHelper.ModifyCurrentUserAsync(this, Discord.CurrentUser, Discord, args, options);
}
#endregion #endregion
#region Guild Events #region Guild Events

View File

@@ -241,7 +241,12 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null) public Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null)
=> UserHelper.ModifyAsync(this, Discord, func, options); {
var args = new GuildUserProperties();
func(args);
return UserHelper.ModifyAsync(Guild, this, Discord, args, options);
}
/// <inheritdoc /> /// <inheritdoc />
public Task KickAsync(string reason = null, RequestOptions options = null) public Task KickAsync(string reason = null, RequestOptions options = null)
=> UserHelper.KickAsync(this, Discord, reason, options); => UserHelper.KickAsync(this, Discord, reason, options);