using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using RadicadeServer.DB; using RadicadeServer.Extensions; using RadicadeServer.RecNet.Config; using RadicadeServer.RecNet.Auth; using RadicadeServer.RecNet.CDN; using RadicadeServer.RecNet.Enums; using System.Text.Json; using System.Text.Json.Nodes; namespace RadicadeServer.Clubs; public sealed class ClubRecord { public long clubId { get; set; } public string name { get; set; } = "Radicade Club"; public string description { get; set; } = ""; public string primaryTag { get; set; } = "Social"; public string imageName { get; set; } = "default"; public string bannerImageName { get; set; } = "default"; public long ownerPlayerId { get; set; } public DateTime createdAt { get; set; } = DateTime.UtcNow; public long? clubhouseRoomId { get; set; } public int visibility { get; set; } = 1; public int joinability { get; set; } = 0; public bool allowJuniors { get; set; } = true; public int minLevel { get; set; } public string[] customTags { get; set; } = Array.Empty(); public string[] additionalImages { get; set; } = Array.Empty(); public List members { get; set; } = new(); } public sealed class ClubMemberRecord { public long accountId { get; set; } public int membershipType { get; set; } public int pendingMembershipType { get; set; } public long inviterAccountId { get; set; } public DateTime createdAt { get; set; } = DateTime.UtcNow; } public static class ClubMembershipTypes { public const int Banned = -1; public const int None = 0; public const int PendingRequested = 1; public const int PendingInvited = 2; public const int PendingDenied = 3; public const int Member = 10; public const int Moderator = 20; public const int Coowner = 30; public const int Creator = 100; } public sealed record ClubMemberView(long AccountId, int MembershipType, long InviterAccountId, DateTime CreatedAt); public static class ClubStore { private const string ConfigKey = "Clubs"; public const string DefaultImageName = "DefaultImgPurple"; private const int ActiveMemberThreshold = ClubMembershipTypes.Member; private static readonly string[] CategoryQueryKeys = { "category", "Category", "categoryTag", "CategoryTag", "categoryName", "CategoryName", "primaryTag", "PrimaryTag", "tag", "Tag", "tags", "Tags" }; public static string? CategoryFilter(IQueryCollection query) { foreach (var key in CategoryQueryKeys) { var value = query[key].FirstOrDefault(); if (!string.IsNullOrWhiteSpace(value)) return value.Trim().TrimStart('#'); } return null; } public static bool MatchesCategory(ClubRecord? club, string? category) { if (club == null) return false; var wanted = category?.Trim().TrimStart('#'); if (string.IsNullOrWhiteSpace(wanted)) return true; return string.Equals(club.primaryTag?.Trim(), wanted, StringComparison.OrdinalIgnoreCase) || (club.customTags ?? Array.Empty()) .Any(tag => string.Equals(tag?.Trim(), wanted, StringComparison.OrdinalIgnoreCase)); } public static List FilterByCategory(List clubs, string? category) => string.IsNullOrWhiteSpace(category) ? clubs : clubs.Where(club => MatchesCategory(club, category)).ToList(); public static async Task> LoadAsync(RecDBContext db) { var config = await db.ServerConfig.AsNoTracking().FirstOrDefaultAsync(x => x.Key == ConfigKey); if (config == null || string.IsNullOrWhiteSpace(config.Value)) return new List(); try { var clubs = config.Value.FromJson>() ?? new List(); foreach (var club in clubs) { club.members ??= new List(); club.customTags ??= Array.Empty(); club.additionalImages ??= Array.Empty(); } return clubs; } catch { return new List(); } } private static readonly string[] HomeClubConfigPrefixes = ["ClubHome:", "ClubHome.", "HomeClub:"]; private static string HomeClubConfigKey(long accountId) => $"ClubHome:{accountId}"; private static async Task GetHomeClubIdAsync(RecDBContext db, long accountId) { var keys = HomeClubConfigPrefixes.Select(prefix => prefix + accountId.ToString(System.Globalization.CultureInfo.InvariantCulture)).ToArray(); var value = await db.ServerConfig.AsNoTracking() .Where(config => keys.Contains(config.Key)) .Select(config => config.Value) .FirstOrDefaultAsync(); return long.TryParse(value, out var clubId) && clubId > 0 ? clubId : null; } public static async Task GetHomeClubAsync(RecDBContext db, long accountId) { var clubId = await GetHomeClubIdAsync(db, accountId); if (!clubId.HasValue) return null; var club = await GetAsync(db, clubId.Value); return club?.clubhouseRoomId is > 0 ? club : null; } public static async Task SetHomeClubAsync(RecDBContext db, long accountId, long? clubId) { var configKey = HomeClubConfigKey(accountId); var config = await db.ServerConfig.FirstOrDefaultAsync(row => row.Key == configKey); if (clubId is > 0) { if (config == null) { config = new ServerConfigVal(configKey, clubId.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)); await db.ServerConfig.AddAsync(config); } else { config.Value = clubId.Value.ToString(System.Globalization.CultureInfo.InvariantCulture); } } else if (config != null) { db.ServerConfig.Remove(config); } await db.SaveChangesAsync(); return clubId is > 0 ? await GetAsync(db, clubId.Value) : null; } public static async Task ClearHomeClubsAsync(RecDBContext db, long clubId) { var clubIdValue = clubId.ToString(System.Globalization.CultureInfo.InvariantCulture); var matching = await db.ServerConfig.Where(config => config.Value == clubIdValue).ToListAsync(); matching.RemoveAll(config => !HomeClubConfigPrefixes.Any(prefix => config.Key.StartsWith(prefix, StringComparison.Ordinal))); if (matching.Count == 0) return; db.ServerConfig.RemoveRange(matching); await db.SaveChangesAsync(); } public static async Task DeleteAsync(RecDBContext db, long clubId) { var clubs = await LoadAsync(db); if (clubs.RemoveAll(club => club.clubId == clubId) == 0) return false; await SaveAsync(db, clubs); await ClearHomeClubsAsync(db, clubId); return true; } public static async Task UpdateAsync(RecDBContext db, long clubId, JsonObject body) { var clubs = await LoadAsync(db); var club = clubs.FirstOrDefault(item => item.clubId == clubId); if (club == null) return null; if (GetNonEmptyString(body, "name") is { } name) club.name = name.Trim(); if (GetNonEmptyString(body, "description") is { } description) club.description = description; if (GetNonEmptyString(body, "category", "primaryTag") is { } category) club.primaryTag = category.Trim(); if (GetNonEmptyString(body, "mainImageName") is { } imageName) club.imageName = imageName.Trim(); if (GetNonEmptyString(body, "bannerImageName") is { } bannerName) club.bannerImageName = bannerName.Trim(); if (GetInt(body, "visibility") is { } visibility) club.visibility = visibility; if (GetInt(body, "joinability") is { } joinability) club.joinability = joinability; if (GetInt(body, "minLevel") is { } minLevel && minLevel >= 0) club.minLevel = minLevel; if (GetBool(body, "allowJuniors") is { } allowJuniors) club.allowJuniors = allowJuniors; if (GetStringArray(body, "customTags") is { } customTags) club.customTags = NormalizeTags(customTags); await SaveAsync(db, clubs); return club; } public static async Task SetClubhouseAsync(RecDBContext db, long clubId, long? roomId) { var clubs = await LoadAsync(db); var club = clubs.FirstOrDefault(item => item.clubId == clubId); if (club == null) return null; club.clubhouseRoomId = roomId; await SaveAsync(db, clubs); return club; } public static async Task SetAdditionalImageAsync(RecDBContext db, long clubId, int index, string imageName) { var clubs = await LoadAsync(db); var club = clubs.FirstOrDefault(item => item.clubId == clubId); if (club == null) return null; var images = (club.additionalImages ?? Array.Empty()).ToList(); if (string.IsNullOrWhiteSpace(imageName)) { if (index < images.Count) images.RemoveAt(index); } else if (index < images.Count) { images[index] = imageName.Trim(); } else if (images.Count < 3) { images.Add(imageName.Trim()); } club.additionalImages = images.ToArray(); await SaveAsync(db, clubs); return club; } public static async Task InviteMemberAsync(RecDBContext db, long clubId, long accountId, int membershipType, long inviterAccountId) { var clubs = await LoadAsync(db); var club = clubs.FirstOrDefault(item => item.clubId == clubId); if (club == null || club.ownerPlayerId == accountId) return null; club.members ??= new List(); var membership = club.members.FirstOrDefault(item => item.accountId == accountId); if (membership == null) { membership = new ClubMemberRecord { accountId = accountId, createdAt = DateTime.UtcNow }; club.members.Add(membership); } membership.membershipType = ClubMembershipTypes.PendingInvited; membership.pendingMembershipType = membershipType; membership.inviterAccountId = inviterAccountId; await SaveAsync(db, clubs); return club; } public static async Task AcceptInvitationAsync(RecDBContext db, long clubId, long accountId) { var clubs = await LoadAsync(db); var club = clubs.FirstOrDefault(item => item.clubId == clubId); var membership = club?.members?.FirstOrDefault(item => item.accountId == accountId); if (club == null || membership?.membershipType != ClubMembershipTypes.PendingInvited) return null; var grantedType = membership.pendingMembershipType is ClubMembershipTypes.Member or ClubMembershipTypes.Moderator or ClubMembershipTypes.Coowner ? membership.pendingMembershipType : ClubMembershipTypes.Member; membership.membershipType = grantedType; membership.pendingMembershipType = 0; membership.createdAt = DateTime.UtcNow; await SaveAsync(db, clubs); return club; } public static async Task RequestToJoinAsync(RecDBContext db, long clubId, long accountId) { var clubs = await LoadAsync(db); var club = clubs.FirstOrDefault(item => item.clubId == clubId); if (club == null) return null; var current = GetMembershipType(club, accountId); if (current == ClubMembershipTypes.Banned || current >= ClubMembershipTypes.Member || current == ClubMembershipTypes.PendingRequested) return club; if (club.joinability == 1) return club; return await SetMemberTypeAsync(db, clubId, accountId, club.joinability == 0 ? ClubMembershipTypes.Member : ClubMembershipTypes.PendingRequested, accountId); } private static string? GetString(JsonObject body, params string[] keys) { foreach (var pair in body) { if (!keys.Any(key => pair.Key.Equals(key, StringComparison.OrdinalIgnoreCase)) || pair.Value == null) continue; if (pair.Value is JsonValue jsonValue && jsonValue.TryGetValue(out var value)) return value; return pair.Value.ToString(); } return null; } private static string? GetNonEmptyString(JsonObject body, params string[] keys) { var value = GetString(body, keys); return string.IsNullOrWhiteSpace(value) ? null : value; } private static int? GetInt(JsonObject body, params string[] keys) { var raw = GetString(body, keys)?.Trim(); if (int.TryParse(raw, out var value)) return value; return raw?.ToLowerInvariant() switch { "private" => 0, "public" => 1, "open" => 0, "inviteonly" or "invite_only" => 1, "asktojoin" or "requesttojoin" or "ask_to_join" => 2, _ => null }; } private static bool? GetBool(JsonObject body, params string[] keys) { var raw = GetString(body, keys)?.Trim(); if (bool.TryParse(raw, out var value)) return value; return raw?.ToLowerInvariant() switch { "1" or "yes" => true, "0" or "no" => false, _ => null }; } private static string[]? GetStringArray(JsonObject body, params string[] keys) { foreach (var pair in body) { if (!keys.Any(key => pair.Key.Equals(key, StringComparison.OrdinalIgnoreCase))) continue; if (pair.Value is JsonArray array) return NormalizeTags(array.Select(item => item?.ToString() ?? string.Empty)); return NormalizeTags(new[] { pair.Value?.ToString() ?? string.Empty }); } return null; } private static string[] NormalizeTags(IEnumerable tags) { var output = new List(); var seen = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var raw in tags) { var tag = raw.Trim(); if (tag.Length > 0 && seen.Add(tag)) output.Add(tag); } return output.ToArray(); } public static async Task SaveAsync(RecDBContext db, List clubs) { var config = await db.ServerConfig.FirstOrDefaultAsync(x => x.Key == ConfigKey); if (config == null) { config = new ServerConfigVal(ConfigKey, "[]"); await db.ServerConfig.AddAsync(config); } config.Value = clubs.ToJson(); await db.SaveChangesAsync(); } public static async Task GetAsync(RecDBContext db, long clubId) { var clubs = await LoadAsync(db); return clubs.FirstOrDefault(x => x.clubId == clubId); } public static async Task CreateAsync( RecDBContext db, long ownerPlayerId, string? name, string? description, string? primaryTag = null, int visibility = 1, int joinability = 0, bool allowJuniors = true, int minLevel = 0, string? mainImageName = null) { var clubs = await LoadAsync(db); var clubName = string.IsNullOrWhiteSpace(name) ? "Radicade Club" : name.Trim(); var tag = string.IsNullOrWhiteSpace(primaryTag) ? "Social" : primaryTag.Trim(); var now = DateTime.UtcNow; var clubId = Math.Abs((long)HashCode.Combine(ownerPlayerId, clubName, now.Ticks)); while (clubId == 0 || clubs.Any(x => x.clubId == clubId)) { clubId = Math.Abs((long)HashCode.Combine(ownerPlayerId, clubName, Guid.NewGuid())); } var club = new ClubRecord { clubId = clubId, name = clubName, description = description?.Trim() ?? "", primaryTag = tag, imageName = string.IsNullOrWhiteSpace(mainImageName) ? DefaultImageName : mainImageName.Trim(), visibility = visibility, joinability = joinability, allowJuniors = allowJuniors, minLevel = minLevel, ownerPlayerId = ownerPlayerId, createdAt = now }; club.members.Add(new ClubMemberRecord { accountId = ownerPlayerId, membershipType = ClubMembershipTypes.Creator, inviterAccountId = ownerPlayerId, createdAt = now }); clubs.Add(club); await SaveAsync(db, clubs); return club; } public static int GetMembershipType(ClubRecord? club, long accountId) { if (club == null || club.clubId <= 0 || accountId <= 0) return ClubMembershipTypes.None; if (club.ownerPlayerId == accountId) return ClubMembershipTypes.Creator; return club.members?.FirstOrDefault(x => x.accountId == accountId)?.membershipType ?? ClubMembershipTypes.None; } public static int GetMemberCount(ClubRecord? club) { if (club == null || club.clubId <= 0) return 0; return 1 + (club.members ?? new List()) .Where(x => x.accountId != club.ownerPlayerId && x.membershipType >= ActiveMemberThreshold) .Select(x => x.accountId) .Distinct() .Count(); } public static IReadOnlyList GetMembers(ClubRecord? club) { if (club == null || club.clubId <= 0) return Array.Empty(); var members = new List { new(club.ownerPlayerId, ClubMembershipTypes.Creator, club.ownerPlayerId, club.createdAt) }; members.AddRange((club.members ?? new List()) .Where(x => x.accountId != club.ownerPlayerId) .GroupBy(x => x.accountId) .Select(group => group.Last()) .Select(x => new ClubMemberView( x.accountId, x.membershipType, x.inviterAccountId, NormalizeMembershipDate(x.createdAt, club.createdAt)))); return members; } private static DateTime NormalizeMembershipDate(DateTime memberCreatedAt, DateTime clubCreatedAt) { if (memberCreatedAt > DateTime.MinValue) return memberCreatedAt; if (clubCreatedAt > DateTime.MinValue) return clubCreatedAt; return DateTime.UnixEpoch; } public static async Task SetMemberTypeAsync( RecDBContext db, long clubId, long accountId, int membershipType, long inviterAccountId) { var clubs = await LoadAsync(db); var club = clubs.FirstOrDefault(x => x.clubId == clubId); if (club == null || club.ownerPlayerId == accountId) return null; club.members ??= new List(); var membership = club.members.FirstOrDefault(x => x.accountId == accountId); if (membership == null) { club.members.Add(new ClubMemberRecord { accountId = accountId, membershipType = membershipType, inviterAccountId = inviterAccountId, createdAt = DateTime.UtcNow }); } else { if (membership.membershipType < ClubMembershipTypes.Member && membershipType >= ClubMembershipTypes.Member) membership.createdAt = DateTime.UtcNow; membership.membershipType = membershipType; membership.inviterAccountId = inviterAccountId; } await SaveAsync(db, clubs); return club; } public static async Task RemoveMemberAsync(RecDBContext db, long clubId, long accountId) { var clubs = await LoadAsync(db); var club = clubs.FirstOrDefault(x => x.clubId == clubId); if (club == null || club.ownerPlayerId == accountId) return null; club.members ??= new List(); club.members.RemoveAll(member => member.accountId == accountId && member.membershipType != ClubMembershipTypes.Banned); await SaveAsync(db, clubs); return club; } private static long ToUnix(DateTime value) => new DateTimeOffset(value).ToUnixTimeSeconds(); public static object BuildClub(ClubRecord? club, long viewerId = 0) { club ??= new ClubRecord { clubId = 0 }; var memberCount = GetMemberCount(club); return new Dictionary { ["ClubId"] = club.clubId, ["Name"] = club.name, ["Description"] = club.description, ["MainImageName"] = club.imageName, ["State"] = 0, ["CreatorAccountId"] = club.ownerPlayerId, ["Category"] = club.primaryTag, ["Visibility"] = club.visibility, ["Joinability"] = club.joinability, ["AllowJuniors"] = club.allowJuniors, ["MinLevel"] = club.minLevel, ["MemberCount"] = memberCount, ["IsRRO"] = false, ["ClubhouseRoomId"] = club.clubhouseRoomId, ["ClubType"] = 0 }; } public static async Task BuildClubDetailsAsync(RecDBContext db, ClubRecord? club, long viewerId = 0) { club ??= new ClubRecord { clubId = 0 }; var details = (Dictionary)BuildClubDetails(club, viewerId); details["AdditionalImages"] = await GetAdditionalImagesAsync(db, club.additionalImages ?? Array.Empty()); return details; } public static object BuildClubDetails(ClubRecord? club, long viewerId = 0) { club ??= new ClubRecord { clubId = 0 }; return new Dictionary { ["Club"] = BuildClub(club, viewerId), ["ClubId"] = club.clubId, ["CustomTags"] = club.customTags ?? Array.Empty(), ["AdditionalImages"] = Array.Empty(), ["CoownerPermissions"] = BuildPermissions(club.clubId, 0), ["ModeratorPermissions"] = BuildPermissions(club.clubId, 1), ["MemberPermissions"] = BuildPermissions(club.clubId, 2), ["MyMembershipType"] = GetMembershipType(club, viewerId) }; } private static async Task GetAdditionalImagesAsync(RecDBContext db, string[] imageNames) { if (imageNames.Length == 0) return Array.Empty(); var records = await db.Images.AsNoTracking() .Where(image => imageNames.Contains(image.Name)) .ToDictionaryAsync(image => image.Name); var imageIds = records.Values.Select(image => image.Id).ToList(); var cheers = await db.ImageInteractions.AsNoTracking() .Where(interaction => interaction.IsCheered && imageIds.Contains(interaction.SavedImageId)) .GroupBy(interaction => interaction.SavedImageId) .Select(group => new { ImageId = group.Key, Count = group.Count() }) .ToDictionaryAsync(group => group.ImageId, group => group.Count); return imageNames.Select(name => { if (!records.TryGetValue(name, out var image)) return (object)new Dictionary { ["Id"] = 0L, ["Type"] = 1, ["Accessibility"] = (int)SavedImageAccessibility.Public, ["AccessibilityLocked"] = false, ["ImageName"] = name, ["Description"] = null, ["PlayerId"] = 0L, ["TaggedPlayerIds"] = Array.Empty(), ["RoomId"] = null, ["PlayerEventId"] = null, ["CreatedAt"] = DateTime.UnixEpoch, ["CheerCount"] = 0, ["CommentCount"] = 0 }; return (object)new Dictionary { ["Id"] = image.Id, ["Type"] = (int)image.ImageType, ["Accessibility"] = (int)image.Accessibility, ["AccessibilityLocked"] = false, ["ImageName"] = image.Name, ["Description"] = null, ["PlayerId"] = image.OwnerId, ["TaggedPlayerIds"] = ParseImagePlayerIds(image.PlayerIds), ["RoomId"] = image.RoomId, ["PlayerEventId"] = image.EventId, ["CreatedAt"] = image.CreatedAt, ["CheerCount"] = cheers.GetValueOrDefault(image.Id), ["CommentCount"] = 0 }; }).ToArray(); } private static long[] ParseImagePlayerIds(string? playerIds) { try { return JsonSerializer.Deserialize(playerIds ?? "[]") ?? Array.Empty(); } catch { return Array.Empty(); } } public static object BuildPermissions(long clubId, int type) { var isCoowner = type == 0; var isModerator = type == 1; return new Dictionary { ["Type"] = type switch { 0 => ClubMembershipTypes.Coowner, 1 => ClubMembershipTypes.Moderator, _ => ClubMembershipTypes.Member }, ["ClubId"] = clubId, ["ApproveMember"] = isCoowner || isModerator, ["BanUnban"] = isCoowner || isModerator, ["CreateEvent"] = isCoowner, ["EditDetails"] = isCoowner, ["EditPermissionSettings"] = isCoowner, ["PostAnnouncement"] = isCoowner }; } public static object BuildMemberListItem(long playerId, int role, bool isOwner, long clubId = 0, long created = 0) { var type = isOwner ? ClubMembershipTypes.Creator : role; var createdAt = created > 0 ? DateTimeOffset.FromUnixTimeSeconds(created).UtcDateTime.ToString("O") : null; return new Dictionary { ["ClubMemberId"] = playerId, ["ClubId"] = clubId, ["AccountId"] = playerId, ["MembershipType"] = type, ["CreatedAt"] = createdAt }; } public static object BuildMemberListItem(ClubMemberView member, long clubId) => BuildMemberListItem( member.AccountId, member.MembershipType, member.MembershipType == ClubMembershipTypes.Creator, clubId, ToUnix(member.CreatedAt)); public static object BuildMember(long playerId, int role, bool isOwner, long clubId = 0, long created = 0) => new Dictionary { ["AccountId"] = playerId, ["ClubId"] = clubId, ["CreatedAt"] = created > 0 ? DateTimeOffset.FromUnixTimeSeconds(created).UtcDateTime.ToString("O") : null, ["MembershipType"] = isOwner ? ClubMembershipTypes.Creator : role, ["membershipRequestId"] = 0L, ["inviterAccountId"] = 0L, ["subscriptionId"] = clubId, ["playerId"] = playerId, ["username"] = string.Empty, ["displayName"] = string.Empty, ["level"] = 0, ["role"] = isOwner ? ClubMembershipTypes.Creator : role, ["membershipStatus"] = role >= ActiveMemberThreshold || isOwner ? 1 : 0, ["isOwner"] = isOwner, ["created"] = created, ["createdAt"] = created }; public static object BuildMember(ClubMemberView membership, ClubRecord? club, Profile? profile) { var role = membership.MembershipType; var created = ToUnix(membership.CreatedAt); var isOwner = role == ClubMembershipTypes.Creator; return new Dictionary { ["AccountId"] = membership.AccountId, ["ClubId"] = club?.clubId ?? 0L, ["CreatedAt"] = membership.CreatedAt.ToString("O"), ["MembershipType"] = role, ["membershipRequestId"] = 0L, ["inviterAccountId"] = membership.InviterAccountId, ["subscriptionId"] = club?.clubId ?? 0L, ["playerId"] = membership.AccountId, ["username"] = profile?.username ?? string.Empty, ["displayName"] = profile?.displayName ?? string.Empty, ["level"] = 0, ["role"] = role, ["membershipStatus"] = role >= ActiveMemberThreshold || isOwner ? 1 : 0, ["isOwner"] = isOwner, ["created"] = created, ["createdAt"] = created }; } public static object BuildMembership(ClubRecord? club, long viewerId = 0) { club ??= new ClubRecord { clubId = 0 }; var role = GetMembershipType(club, viewerId); var isMember = role >= ActiveMemberThreshold; return new Dictionary { ["accountId"] = viewerId, ["clubId"] = club.clubId, ["creatorAccountId"] = club.ownerPlayerId, ["membershipType"] = role, ["LastAnnouncementId"] = 0L, ["LastReadAnnouncementId"] = 0L, ["announcements"] = Array.Empty(), ["subscriptionId"] = club.clubId }; } public static object BuildSubscriptionEnvelope(ClubRecord? club, long viewerId = 0) => new Dictionary { ["success"] = club != null, ["error"] = string.Empty, ["value"] = club == null ? null : BuildMembership(club, viewerId) }; public static object BuildSearchResponse(object[] clubs, int skip, int take) => new Dictionary { ["Clubs"] = clubs, ["TotalClubs"] = clubs.Length, ["ContinuationToken"] = null }; public static object BuildHome(ClubRecord? homeClub, long viewerId, IReadOnlyList allClubs, IReadOnlyList mine) { return BuildClub(homeClub, viewerId); } public static object BuildEmptyHome(long viewerId) { return BuildClub(null, viewerId); } public static object BuildModerationBlockDetails() => new Dictionary { ["Duration"] = 0L, ["GameSessionId"] = 0L, ["PlayerIdReporter"] = 0L, ["IsBan"] = false, ["IsHostKick"] = false, ["Message"] = string.Empty, ["ReportCategory"] = 0, ["VoteKickReason"] = string.Empty, ["bannedUntil"] = string.Empty, ["displayReason"] = string.Empty, ["banType"] = string.Empty, ["banAppealCode"] = string.Empty, ["banAppealText"] = string.Empty }; public static object BuildEligibility() => new Dictionary { ["canCreateClub"] = true, ["canCreate"] = true, ["creationAllowed"] = true, ["isAllowed"] = true, ["isCreationRestricted"] = false, ["creationRestricted"] = false, ["moderationRestricted"] = false, ["moderationIncidentCount"] = 0, ["myCreatedClubsCount"] = 0, ["maxCreatedClubs"] = 100, ["cooldownSeconds"] = 0, ["restrictionReason"] = string.Empty }; }