namespace WxAgent.Core; public sealed record WechatDatabaseGroupMember(long MemberId, string Username, string DisplayName, bool IsOwner); public sealed record WechatGroupMemberPage(string GroupUsername, IReadOnlyList Members, int? NextOffset) { public bool HasMore => NextOffset is not null; } public static class WechatGroupMemberQuery { public const string Sql = """ SELECT m.member_id, c.username, c.nick_name, c.remark, cr.owner FROM chat_room AS cr JOIN chatroom_member AS m ON m.room_id = cr.id JOIN contact AS c ON c.id = m.member_id WHERE cr.username = $group ORDER BY m.member_id LIMIT $limit OFFSET $offset; """; public static IReadOnlyList> Parameters(string groupUsername, int limit, int offset) { if (string.IsNullOrWhiteSpace(groupUsername) || !groupUsername.EndsWith("@chatroom", StringComparison.Ordinal)) throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "A stable @chatroom username is required."); if (limit is < 1 or > 10000 || offset < 0 || offset > int.MaxValue - limit) throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "Member limit must be 1-10000 and offset must leave room for the next page."); return new KeyValuePair[] { new("$group", groupUsername), new("$limit", limit + 1), new("$offset", offset) }; } public static WechatGroupMemberPage Page(string groupUsername, IReadOnlyList rows, int limit, int offset) { _ = Parameters(groupUsername, limit, offset); return new(groupUsername, rows.Take(limit).ToArray(), rows.Count > limit ? offset + limit : null); } }