[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.
/// </summary>
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 System;
namespace Discord.API.Rest
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
namespace Discord.API.Rest;
internal class ModifyGuildMemberParams
{
[JsonProperty("mute")]
public Optional<bool> Mute { get; set; }
[JsonProperty("deaf")]
public Optional<bool> Deaf { get; set; }
[JsonProperty("nick")]
public Optional<string> Nickname { get; set; }
[JsonProperty("roles")]
public Optional<ulong[]> RoleIds { get; set; }
[JsonProperty("channel_id")]
public Optional<ulong?> ChannelId { get; set; }
[JsonProperty("communication_disabled_until")]
public Optional<DateTimeOffset?> TimedOutUntil { get; set; }
[JsonProperty("flags")]
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);
}
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<string>(); //Remove
var nickArgs = new ModifyCurrentMemberParams
{
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)
{
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);
}
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.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<GuildMember>("PATCH", () => $"guilds/{guildId}/members/@me", args, ids, options: options);
}
public Task<Channel> CreateDMChannelAsync(CreateDMChannelParams args, RequestOptions options = null)

View File

@@ -991,6 +991,15 @@ namespace Discord.Rest
/// <inheritdoc />
public Task<MemberSearchResult> SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null)
=> 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
#region Audit logs

View File

@@ -148,7 +148,10 @@ namespace Discord.Rest
/// <inheritdoc />
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)
IsDeafened = args.Deaf.Value;
if (args.Mute.IsSpecified)

View File

@@ -26,16 +26,14 @@ namespace Discord.Rest
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)
{
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>(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<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)
=> client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, reason, options);

View File

@@ -1343,6 +1343,15 @@ namespace Discord.WebSocket
/// <inheritdoc />
public Task<MemberSearchResult> SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null)
=> 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
#region Guild Events

View File

@@ -241,7 +241,12 @@ namespace Discord.WebSocket
/// <inheritdoc />
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 />
public Task KickAsync(string reason = null, RequestOptions options = null)
=> UserHelper.KickAsync(this, Discord, reason, options);