From 1dd40402af020ab6a798e6e26ac0dd5a3bc0c51d Mon Sep 17 00:00:00 2001 From: Rory& Date: Sat, 21 Feb 2026 05:16:17 +0100 Subject: [PATCH] Admin API discovery stuff --- .../DiscoverableGuildModel.cs | 139 ++++++++++++++ .../Controllers/DiscoveryController.cs | 177 +++++++++++++++++- .../Spacebar.AdminApi/Spacebar.AdminApi.http | 9 +- extra/admin-api/update-deps.cs | 19 +- 4 files changed, 333 insertions(+), 11 deletions(-) diff --git a/extra/admin-api/Models/Spacebar.Models.AdminApi/DiscoverableGuildModel.cs b/extra/admin-api/Models/Spacebar.Models.AdminApi/DiscoverableGuildModel.cs index e69de29bb..f98d62b7e 100644 --- a/extra/admin-api/Models/Spacebar.Models.AdminApi/DiscoverableGuildModel.cs +++ b/extra/admin-api/Models/Spacebar.Models.AdminApi/DiscoverableGuildModel.cs @@ -0,0 +1,139 @@ +using System.Text.Json.Serialization; + +namespace Spacebar.Models.AdminApi; + +public class DiscoverableGuildModel { + [JsonPropertyName("id")] + public string Id { get; set; } = null!; + + [JsonPropertyName("afk_channel_id")] + public string? AfkChannelId { get; set; } + + [JsonPropertyName("afk_timeout")] + public int? AfkTimeout { get; set; } + + [JsonPropertyName("banner")] + public string? Banner { get; set; } + + [JsonPropertyName("default_message_notifications")] + public int? DefaultMessageNotifications { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("discovery_splash")] + public string? DiscoverySplash { get; set; } + + [JsonPropertyName("explicit_content_filter")] + public int? ExplicitContentFilter { get; set; } + + [JsonPropertyName("features")] + public List Features { get; set; } + + [JsonPropertyName("primary_category_id")] + public string? PrimaryCategoryId { get; set; } + + [JsonPropertyName("icon")] + public string? Icon { get; set; } + + [JsonPropertyName("large")] + public bool Large { get; set; } + + [JsonPropertyName("max_members")] + public int? MaxMembers { get; set; } + + [JsonPropertyName("max_presences")] + public int? MaxPresences { get; set; } + + [JsonPropertyName("max_video_channel_users")] + public int? MaxVideoChannelUsers { get; set; } + + [JsonPropertyName("member_count")] + public int? MemberCount { get; set; } + + [JsonPropertyName("presence_count")] + public int? PresenceCount { get; set; } + + [JsonPropertyName("template_id")] + public string? TemplateId { get; set; } + + [JsonPropertyName("mfa_level")] + public int? MfaLevel { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("owner_id")] + public string? OwnerId { get; set; } + + [JsonPropertyName("preferred_locale")] + public string? PreferredLocale { get; set; } + + [JsonPropertyName("premium_subscription_count")] + public int? PremiumSubscriptionCount { get; set; } + + [JsonPropertyName("premium_tier")] + public int PremiumTier { get; set; } + + [JsonPropertyName("public_updates_channel_id")] + public string? PublicUpdatesChannelId { get; set; } + + [JsonPropertyName("rules_channel_id")] + public string? RulesChannelId { get; set; } + + [JsonPropertyName("region")] + public string? Region { get; set; } + + [JsonPropertyName("splash")] + public string? Splash { get; set; } + + [JsonPropertyName("system_channel_id")] + public string? SystemChannelId { get; set; } + + [JsonPropertyName("system_channel_flags")] + public int? SystemChannelFlags { get; set; } + + [JsonPropertyName("unavailable")] + public bool Unavailable { get; set; } + + [JsonPropertyName("verification_level")] + public int? VerificationLevel { get; set; } + + [JsonPropertyName("welcome_screen")] + public string WelcomeScreen { get; set; } = null!; + + [JsonPropertyName("widget_channel_id")] + public string? WidgetChannelId { get; set; } + + [JsonPropertyName("widget_enabled")] + public bool WidgetEnabled { get; set; } + + [JsonPropertyName("nsfw_level")] + public int? NsfwLevel { get; set; } + + [JsonPropertyName("nsfw")] + public bool Nsfw { get; set; } + + [JsonPropertyName("parent")] + public string? Parent { get; set; } + + [JsonPropertyName("premium_progress_bar_enabled")] + public bool? PremiumProgressBarEnabled { get; set; } + + [JsonPropertyName("channel_ordering")] + public List ChannelOrdering { get; set; } + + [JsonPropertyName("discovery_weight")] + public int DiscoveryWeight { get; set; } + + [JsonPropertyName("discovery_excluded")] + public bool DiscoveryExcluded { get; set; } +} + +public class DiscoverableGuildUpdateModel { + [JsonPropertyName("discovery_weight")] + public int? DiscoveryWeight { get; set; } + + [JsonPropertyName("discovery_excluded")] + public bool? DiscoveryExcluded { get; set; } +} \ No newline at end of file diff --git a/extra/admin-api/Spacebar.AdminApi/Controllers/DiscoveryController.cs b/extra/admin-api/Spacebar.AdminApi/Controllers/DiscoveryController.cs index 39d72b45c..3f38b3a81 100644 --- a/extra/admin-api/Spacebar.AdminApi/Controllers/DiscoveryController.cs +++ b/extra/admin-api/Spacebar.AdminApi/Controllers/DiscoveryController.cs @@ -19,9 +19,180 @@ public class DiscoveryController( ISpacebarReplication replication ) : ControllerBase { [HttpGet] - public async Task GetDiscoverableGuilds() { + public async IAsyncEnumerable GetDiscoverableGuilds(bool includeExcluded = false) { (await auth.GetCurrentUserAsync(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR); - // var discoverableGuilds = db.Guilds - // .Where(x=>x.) + var discoverableGuilds = db.Guilds + .AsNoTracking() + .Where(x => (!x.DiscoveryExcluded || includeExcluded) && x.Features.Contains("DISCOVERABLE")) + .OrderByDescending(x => x.DiscoveryWeight) + .ThenByDescending(x => x.MemberCount); + await foreach (var guild in discoverableGuilds.AsAsyncEnumerable()) { + yield return new DiscoverableGuildModel() { + Id = guild.Id, + Features = guild.Features.Split(",").ToList(), + Banner = guild.Banner, + DiscoveryExcluded = guild.DiscoveryExcluded, + DiscoveryWeight = guild.DiscoveryWeight, + MemberCount = guild.MemberCount, + Name = guild.Name, + SystemChannelFlags = guild.SystemChannelFlags, + AfkChannelId = guild.AfkChannelId, + AfkTimeout = guild.AfkTimeout, + ChannelOrdering = guild.ChannelOrdering.Split(",").ToList(), + DefaultMessageNotifications = guild.DefaultMessageNotifications, + Description = guild.Description, + DiscoverySplash = guild.DiscoverySplash, + ExplicitContentFilter = guild.ExplicitContentFilter, + Icon = guild.Icon, + Large = guild.Large, + MaxMembers = guild.MaxMembers, + MaxPresences = guild.MaxPresences, + MaxVideoChannelUsers = guild.MaxVideoChannelUsers, + MfaLevel = guild.MfaLevel, + Nsfw = guild.Nsfw, + NsfwLevel = guild.NsfwLevel, + OwnerId = guild.OwnerId, + Parent = guild.Parent, + PreferredLocale = guild.PreferredLocale, + PremiumProgressBarEnabled = guild.PremiumProgressBarEnabled, + PremiumTier = guild.PremiumTier, + PremiumSubscriptionCount = guild.PremiumSubscriptionCount, + PresenceCount = guild.PresenceCount, + PrimaryCategoryId = guild.PrimaryCategoryId, + PublicUpdatesChannelId = guild.PublicUpdatesChannelId, + Region = guild.Region, + RulesChannelId = guild.RulesChannelId, + Splash = guild.Splash, + SystemChannelId = guild.SystemChannelId, + TemplateId = guild.TemplateId, + Unavailable = guild.Unavailable, + VerificationLevel = guild.VerificationLevel, + WelcomeScreen = guild.WelcomeScreen, + WidgetChannelId = guild.WidgetChannelId, + WidgetEnabled = guild.WidgetEnabled + }; + } + } + + [HttpGet("{guildId}")] + public async Task GetDiscoverableGuild(string guildId, bool includeExcluded = false) { + (await auth.GetCurrentUserAsync(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR); + var discoverableGuilds = db.Guilds + .AsNoTracking() + .Where(x => x.Id == guildId) + .Where(x => (!x.DiscoveryExcluded || includeExcluded) && x.Features.Contains("DISCOVERABLE")) + .OrderByDescending(x => x.DiscoveryWeight) + .ThenByDescending(x => x.MemberCount); + var guild = await discoverableGuilds.SingleAsync(); + return new DiscoverableGuildModel() { + Id = guild.Id, + Features = guild.Features.Split(",").ToList(), + Banner = guild.Banner, + DiscoveryExcluded = guild.DiscoveryExcluded, + DiscoveryWeight = guild.DiscoveryWeight, + MemberCount = guild.MemberCount, + Name = guild.Name, + SystemChannelFlags = guild.SystemChannelFlags, + AfkChannelId = guild.AfkChannelId, + AfkTimeout = guild.AfkTimeout, + ChannelOrdering = guild.ChannelOrdering.Split(",").ToList(), + DefaultMessageNotifications = guild.DefaultMessageNotifications, + Description = guild.Description, + DiscoverySplash = guild.DiscoverySplash, + ExplicitContentFilter = guild.ExplicitContentFilter, + Icon = guild.Icon, + Large = guild.Large, + MaxMembers = guild.MaxMembers, + MaxPresences = guild.MaxPresences, + MaxVideoChannelUsers = guild.MaxVideoChannelUsers, + MfaLevel = guild.MfaLevel, + Nsfw = guild.Nsfw, + NsfwLevel = guild.NsfwLevel, + OwnerId = guild.OwnerId, + Parent = guild.Parent, + PreferredLocale = guild.PreferredLocale, + PremiumProgressBarEnabled = guild.PremiumProgressBarEnabled, + PremiumTier = guild.PremiumTier, + PremiumSubscriptionCount = guild.PremiumSubscriptionCount, + PresenceCount = guild.PresenceCount, + PrimaryCategoryId = guild.PrimaryCategoryId, + PublicUpdatesChannelId = guild.PublicUpdatesChannelId, + Region = guild.Region, + RulesChannelId = guild.RulesChannelId, + Splash = guild.Splash, + SystemChannelId = guild.SystemChannelId, + TemplateId = guild.TemplateId, + Unavailable = guild.Unavailable, + VerificationLevel = guild.VerificationLevel, + WelcomeScreen = guild.WelcomeScreen, + WidgetChannelId = guild.WidgetChannelId, + WidgetEnabled = guild.WidgetEnabled + }; + } + + [HttpPatch("{guildId}")] + public async Task UpdateDiscoverableGuild(string guildId, [FromBody] DiscoverableGuildUpdateModel guildUpdateModel, bool includeExcluded = false) { + (await auth.GetCurrentUserAsync(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR); + var guild = await db.Guilds + .AsNoTracking() + .Where(x => x.Id == guildId) + .Where(x => (!x.DiscoveryExcluded || includeExcluded) && x.Features.Contains("DISCOVERABLE")) + .OrderByDescending(x => x.DiscoveryWeight) + .ThenByDescending(x => x.MemberCount) + .SingleAsync(); + + if (guildUpdateModel.DiscoveryExcluded != null) + guild.DiscoveryExcluded = guildUpdateModel.DiscoveryExcluded.Value; + + if (guildUpdateModel.DiscoveryWeight != null) + guild.DiscoveryWeight = guildUpdateModel.DiscoveryWeight.Value; + + db.Guilds.Update(guild); + await db.SaveChangesAsync(); + + return new DiscoverableGuildModel() { + Id = guild.Id, + Features = guild.Features.Split(",").ToList(), + Banner = guild.Banner, + DiscoveryExcluded = guild.DiscoveryExcluded, + DiscoveryWeight = guild.DiscoveryWeight, + MemberCount = guild.MemberCount, + Name = guild.Name, + SystemChannelFlags = guild.SystemChannelFlags, + AfkChannelId = guild.AfkChannelId, + AfkTimeout = guild.AfkTimeout, + ChannelOrdering = guild.ChannelOrdering.Split(",").ToList(), + DefaultMessageNotifications = guild.DefaultMessageNotifications, + Description = guild.Description, + DiscoverySplash = guild.DiscoverySplash, + ExplicitContentFilter = guild.ExplicitContentFilter, + Icon = guild.Icon, + Large = guild.Large, + MaxMembers = guild.MaxMembers, + MaxPresences = guild.MaxPresences, + MaxVideoChannelUsers = guild.MaxVideoChannelUsers, + MfaLevel = guild.MfaLevel, + Nsfw = guild.Nsfw, + NsfwLevel = guild.NsfwLevel, + OwnerId = guild.OwnerId, + Parent = guild.Parent, + PreferredLocale = guild.PreferredLocale, + PremiumProgressBarEnabled = guild.PremiumProgressBarEnabled, + PremiumTier = guild.PremiumTier, + PremiumSubscriptionCount = guild.PremiumSubscriptionCount, + PresenceCount = guild.PresenceCount, + PrimaryCategoryId = guild.PrimaryCategoryId, + PublicUpdatesChannelId = guild.PublicUpdatesChannelId, + Region = guild.Region, + RulesChannelId = guild.RulesChannelId, + Splash = guild.Splash, + SystemChannelId = guild.SystemChannelId, + TemplateId = guild.TemplateId, + Unavailable = guild.Unavailable, + VerificationLevel = guild.VerificationLevel, + WelcomeScreen = guild.WelcomeScreen, + WidgetChannelId = guild.WidgetChannelId, + WidgetEnabled = guild.WidgetEnabled + }; } } \ No newline at end of file diff --git a/extra/admin-api/Spacebar.AdminApi/Spacebar.AdminApi.http b/extra/admin-api/Spacebar.AdminApi/Spacebar.AdminApi.http index 06d9d1747..bdb509e9b 100644 --- a/extra/admin-api/Spacebar.AdminApi/Spacebar.AdminApi.http +++ b/extra/admin-api/Spacebar.AdminApi/Spacebar.AdminApi.http @@ -1,4 +1,4 @@ -@Spacebar.AdminApi_HostAddress = http://localhost:5112 +@SpacebarAdminApi_HostAddress = http://localhost:5112 POST {{Spacebar.AdminApi_HostAddress}}/_spacebar/admin/guilds/1473141782615941382/force_join Content-Type: application/json @@ -10,3 +10,10 @@ Accept: application/json } ### + +GET {{SpacebarAdminApi_HostAddress}}/_spacebar/admin/discovery +Content-Type: application/json +Accept: application/json +Authorization: null null + +### diff --git a/extra/admin-api/update-deps.cs b/extra/admin-api/update-deps.cs index bf0516f8a..0ec7bd219 100755 --- a/extra/admin-api/update-deps.cs +++ b/extra/admin-api/update-deps.cs @@ -16,7 +16,8 @@ if (args.Length > 0) { Console.WriteLine($"==> Updating dependencies for {outs.Length} projects..."); -foreach (var outp in outs) { +var ss = new SemaphoreSlim(1, 1); +var tasks = outs.Select(outp => Task.Run(async () => { Console.WriteLine(ConsoleUtils.ColoredString($" ==> Updating {outp}...", 0x80, 0x80, 0xff)); Console.Write(ConsoleUtils.ColoredString($" ==> Getting project root directory... ", 0x80, 0xff, 0xff)); var rootDir = JsonSerializer.Deserialize(Util.GetCommandOutputSync("nix", $"eval --json .#packages.x86_64-linux.{outp}.srcRoot", silent: true, stderr: false)).Split("/extra/admin-api/",2)[1]; @@ -27,18 +28,22 @@ foreach (var outp in outs) { Console.WriteLine(ConsoleUtils.ColoredString($" ==> {nugetDepsFilePath} exists: {File.Exists(nugetDepsFilePath)}", 0x80, 0xff, 0xff)); if (!File.Exists(nugetDepsFilePath)) { Console.WriteLine(ConsoleUtils.ColoredString($" ==> No NuGet deps file, skipping!", 0xff, 0x80, 0x80)); - continue; + return; } - Console.WriteLine(ConsoleUtils.ColoredString($" ==> Building fetch-deps script...", 0x80, 0xff, 0x80)); - Util.RunCommandSync("nix", $"build .#{outp}.passthru.fetch-deps"); + var fname = $"./update-deps-{outp}"; + Console.WriteLine(ConsoleUtils.ColoredString($" ==> Building fetch-deps script {fname}...", 0x80, 0xff, 0x80)); + Util.RunCommandSync("nix", $"build .#{outp}.passthru.fetch-deps --out-link {fname}"); Console.WriteLine(ConsoleUtils.ColoredString($" ==> Running fetch-deps script...", 0x80, 0xff, 0x80)); - Util.RunCommandSync("./result", nugetDepsFilePath); + Util.RunCommandSync(fname, nugetDepsFilePath); - var deps = JsonSerializer.Deserialize(File.ReadAllText(nugetDepsFilePath)); + var deps = JsonSerializer.Deserialize(await File.ReadAllTextAsync(nugetDepsFilePath)); Console.WriteLine(ConsoleUtils.ColoredString($" ==> Locked {deps.Length} dependencies...", (byte)(deps.Length == 0 ? 0xff : 0x80), (byte)(deps.Length == 0 ? 0x80 : 0xff), 0x80)); + File.Delete(fname); // await Task.Delay(250); -} \ No newline at end of file +})).ToList(); + +await Task.WhenAll(tasks);