Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Member search v2 #2931

Merged
merged 9 commits into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Discord.Net.Core/Entities/Guilds/IGuild.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,7 @@ public interface IGuild : IDeletable, ISnowflakeEntity
/// be or has been removed from this guild.
/// </returns>
Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null, IEnumerable<ulong> includeRoleIds = null);

/// <summary>
/// Gets a collection of users in this guild that the name or nickname starts with the
/// provided <see cref="string"/> at <paramref name="query"/>.
Expand All @@ -1104,6 +1105,8 @@ public interface IGuild : IDeletable, ISnowflakeEntity
/// </returns>
Task<IReadOnlyCollection<IGuildUser>> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);

Task<MemberSearchResult> SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null);

/// <summary>
/// Gets the specified number of audit log entries for this guild.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions src/Discord.Net.Core/Entities/Guilds/JoinSourceType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Discord;

public enum JoinSourceType
{
Unknown = 0,

BotInvite = 1,

Integration = 2,

ServerDiscovery = 3,

StudentHub = 4,

InviteCode = 5,

VanityUrl = 6,
}
19 changes: 19 additions & 0 deletions src/Discord.Net.Core/Entities/Guilds/MemberSearchPropertiesV2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Discord;

public class MemberSearchPropertiesV2
{
public MemberSearchPropertiesV2After After { get; set; }
}

public struct MemberSearchPropertiesV2After
{
public ulong UserId { get; set; }

public ulong GuildJoinedAt { get; set; }

public MemberSearchPropertiesV2After(ulong userId, ulong guildJoinedAt)
{
UserId = userId;
GuildJoinedAt = guildJoinedAt;
}
}
45 changes: 45 additions & 0 deletions src/Discord.Net.Core/Entities/Guilds/MemberSearchResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Diagnostics;

namespace Discord;

public struct MemberSearchResult
{
public ulong GuildId { get; }

public IReadOnlyCollection<MemberSearchData> Members { get; }

public int PageResultCount { get; }

public int TotalResultCount { get; }

public MemberSearchResult(ulong guildId, IReadOnlyCollection<MemberSearchData> members, int pageResultCount, int totalResultCount)
{
GuildId = guildId;
Members = members;
PageResultCount = pageResultCount;
TotalResultCount = totalResultCount;
}
}

[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public readonly struct MemberSearchData
{
public IGuildUser User { get; }

public string SourceInviteCode { get; }

public JoinSourceType JoinSourceType { get; }

public ulong? InviterId { get; }

public MemberSearchData(IGuildUser user, string sourceInviteCode, JoinSourceType joinSourceType, ulong? inviterId)
{
User = user;
SourceInviteCode = sourceInviteCode;
JoinSourceType = joinSourceType;
InviterId = inviterId;
}

private string DebuggerDisplay => $"{User.Username} ({User.Id})";
}
18 changes: 18 additions & 0 deletions src/Discord.Net.Rest/API/Common/GuildMemberSearchResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Newtonsoft.Json;

namespace Discord.API;

internal class GuildMemberSearchResponse
{
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }

[JsonProperty("members")]
public GuildSearchMemberData[] Members { get; set; }

[JsonProperty("page_result_count")]
public int PageResultCount { get; set; }

[JsonProperty("total_result_count")]
public int TotalResultCount { get; set; }
}
18 changes: 18 additions & 0 deletions src/Discord.Net.Rest/API/Common/GuildSearchMemberData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Newtonsoft.Json;

namespace Discord.API;

internal class GuildSearchMemberData
{
[JsonProperty("member")]
public GuildMember Member { get; set; }

[JsonProperty("source_invite_code")]
public string InviteCode { get; set; }

[JsonProperty("join_source_type")]
public JoinSourceType JoinSourceType { get; set; }

[JsonProperty("inviter_id")]
public ulong? InviterId { get; set; }
}
28 changes: 28 additions & 0 deletions src/Discord.Net.Rest/API/Rest/SearchGuildMembersParamsV2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Discord.API.Rest;

internal class SearchGuildMembersParamsV2
{
[JsonProperty("limit")]
public Optional<int?> Limit { get; set; }

[JsonProperty("and_query")]
public Dictionary<string, string[]> AndQuery { get; set; }

[JsonProperty("or_query")]
public Dictionary<string, string[]> OrQuery { get; set; }

[JsonProperty("after")]
public SearchParamsAfter After { get; set; }
}

internal class SearchParamsAfter
{
[JsonProperty("guild_joined_at")]
public ulong GuildJoinedAt { get; set; }

[JsonProperty("user_id")]
public ulong UserId { get; set; }
}
10 changes: 10 additions & 0 deletions src/Discord.Net.Rest/DiscordRestApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 +2038,16 @@ public Task<IReadOnlyCollection<GuildMember>> SearchGuildMembersAsync(ulong guil
Expression<Func<string>> endpoint = () => $"guilds/{guildId}/members/search?limit={limit}&query={query}";
return SendAsync<IReadOnlyCollection<GuildMember>>("GET", endpoint, ids, options: options);
}

public Task<GuildMemberSearchResponse> SearchGuildMembersAsyncV2(ulong guildId, SearchGuildMembersParamsV2 args, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);
return SendJsonAsync<GuildMemberSearchResponse>("POST", () => $"guilds/{guildId}/members-search", args, ids, options: options);
}

#endregion

#region Guild Roles
Expand Down
31 changes: 31 additions & 0 deletions src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,37 @@ public static async Task<IReadOnlyCollection<RestGuildUser>> SearchUsersAsync(IG
var models = await client.ApiClient.SearchGuildMembersAsync(guild.Id, apiArgs, options).ConfigureAwait(false);
return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray();
}

public static async Task<MemberSearchResult> SearchUsersAsyncV2(IGuild guild, BaseDiscordClient client, int limit, MemberSearchPropertiesV2 args,
RequestOptions options)
{
var apiArgs = new SearchGuildMembersParamsV2
{
Limit = limit,
After = args is null
? null
: new ()
{
UserId = args.After.UserId,
GuildJoinedAt = args.After.GuildJoinedAt
}
};
var model = await client.ApiClient.SearchGuildMembersAsyncV2(guild.Id, apiArgs, options);

return new MemberSearchResult(
model.GuildId,
model.Members.Select(x =>
new MemberSearchData(
RestGuildUser.Create(client, guild, x.Member),
x.InviteCode,
x.JoinSourceType,
x.InviterId)
).ToImmutableArray(),
model.PageResultCount,
model.TotalResultCount
);
}

#endregion

#region Audit logs
Expand Down
3 changes: 3 additions & 0 deletions src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,9 @@ public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOp
/// </returns>
public Task<IReadOnlyCollection<RestGuildUser>> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, RequestOptions options = null)
=> GuildHelper.SearchUsersAsync(this, Discord, query, limit, options);

public Task<MemberSearchResult> SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null)
=> GuildHelper.SearchUsersAsyncV2(this, Discord, limit, args, options);
#endregion

#region Audit logs
Expand Down
3 changes: 3 additions & 0 deletions src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,9 @@ internal void CompleteDownloadUsers()
/// </returns>
public Task<IReadOnlyCollection<RestGuildUser>> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, RequestOptions options = null)
=> GuildHelper.SearchUsersAsync(this, Discord, query, limit, options);

public Task<MemberSearchResult> SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null)
=> GuildHelper.SearchUsersAsyncV2(this, Discord, limit, args, options);
#endregion

#region Guild Events
Expand Down