From 2620a5551803734c6f498dfd00b6539e60e52bcf Mon Sep 17 00:00:00 2001 From: Rory& Date: Mon, 29 Sep 2025 02:19:20 +0200 Subject: [PATCH] Add fetching guild profiles --- assets/schemas.json | Bin 41166407 -> 41883801 bytes src/api/routes/guilds/#guild_id/profile.ts | 65 ++++++++++++ src/util/entities/Invite.ts | 3 + .../schemas/responses/GuildProfileResponse.ts | 96 ++++++++++++++++++ src/util/schemas/responses/index.ts | 1 + 5 files changed, 165 insertions(+) create mode 100644 src/api/routes/guilds/#guild_id/profile.ts create mode 100644 src/util/schemas/responses/GuildProfileResponse.ts diff --git a/assets/schemas.json b/assets/schemas.json index 45bf1841f81ce81043cb99ded6b173cb11d3483d..ef9875903c3fc629c89bbbad2e641ca1994e53eb 100755 GIT binary patch delta 63447 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 delta 5994 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 SCBk?2G9vvmWmXxnSoS~c. +*/ + +import { route } from "@spacebar/api"; +import { Guild, GuildProfileResponse, GuildVisibilityLevel } from "@spacebar/util"; +import { Request, Response, Router } from "express"; + +const router = Router(); + +router.get( + "/", + route({ + responses: { + "200": { + body: "GuildProfileResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + const profileResponse: GuildProfileResponse = { + id: guild_id, + name: guild.name, + icon_hash: guild.icon ?? null, + member_count: guild.member_count!, + online_count: guild.member_count!, + description: guild.description ?? "A Spacebar guild", + brand_color_primary: "#FF00FF", + banner_hash: null, + game_application_ids: [], // We don't track this + game_activity: {}, // We don't track this + tag: guild.name.substring(0, 4).toUpperCase(), // TODO: allow custom tags + badge: 0, + badge_color_primary: "#FF00FF", + badge_color_secondary: "#00FFFF", + badge_hash: "", + traits: [], + features: guild.features ?? [], + visibility: GuildVisibilityLevel.PUBLIC, + custom_banner_hash: guild.banner ?? null, + premium_subscription_count: guild.premium_subscription_count ?? 0, + premium_tier: guild.premium_tier ?? 0 + }; + + res.send(profileResponse); + }, +); + +export default router; diff --git a/src/util/entities/Invite.ts b/src/util/entities/Invite.ts index e1b8cabd..4bf43b94 100644 --- a/src/util/entities/Invite.ts +++ b/src/util/entities/Invite.ts @@ -23,6 +23,9 @@ import { Guild } from "./Guild"; import { Member } from "./Member"; import { User } from "./User"; import { dbEngine } from "../util/Database"; +import { emitEvent } from "../util"; +import { GuildCreateEvent } from "../interfaces"; +import { ReadyGuildDTO } from "../dtos"; export const PublicInviteRelation = ["inviter", "guild", "channel"]; diff --git a/src/util/schemas/responses/GuildProfileResponse.ts b/src/util/schemas/responses/GuildProfileResponse.ts new file mode 100644 index 00000000..58e4b485 --- /dev/null +++ b/src/util/schemas/responses/GuildProfileResponse.ts @@ -0,0 +1,96 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2025 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +export interface GuildProfileResponse { + id: string; + name: string; + icon_hash: string | null; + member_count: number; + online_count: number; + description: string; + brand_color_primary: string; + banner_hash: string | null; + game_application_ids: string[]; + game_activity: {[id: string]: GameActivity}; + tag: string | null; + badge: GuildBadgeType; + badge_color_primary: string; + badge_color_secondary: string; + badge_hash: string; + traits: GuildTrait[]; + features: string[]; + visibility: GuildVisibilityLevel; + custom_banner_hash: string | null; + premium_subscription_count: number; + premium_tier: number; +} + +export interface GameActivity { + activity_level: number; + activity_score: number; +} + +export interface GuildTrait { + emoji_id: string | null; + emoji_name: string | null; + emoji_animated: boolean; + label: string; + position: number; +} + +// TODO: move +export enum GuildVisibilityLevel { + PUBLIC = 1, + RESTRICTED = 2, + PUBLIC_WITH_RECRUITMENT = 3 +} + +export enum GuildBadgeType { + SWORD = 0, + WATER_DROP = 1, + SKULL = 2, + TOADSTOOL = 3, + MOON = 4, + LIGHTNING = 5, + LEAF = 6, + HEART = 7, + FIRE = 8, + COMPASS = 9, + CROSSHAIRS = 10, + FLOWER = 11, + FORCE = 12, + GEM = 13, + LAVA = 14, + PSYCHIC = 15, + SMOKE = 16, + SNOW = 17, + SOUND = 18, + SUN = 19, + WIND = 20, + BUNNY = 21, + DOG = 22, + FROG = 23, + GOAT = 24, + CAT = 25, + DIAMOND = 26, + CROWN = 27, + TROPHY = 28, + MONEY_BAG = 29, + DOLLAR_SIGN = 30, +} + diff --git a/src/util/schemas/responses/index.ts b/src/util/schemas/responses/index.ts index 561af184..dcf49f3c 100644 --- a/src/util/schemas/responses/index.ts +++ b/src/util/schemas/responses/index.ts @@ -36,6 +36,7 @@ export * from "./GuildBansResponse"; export * from "./GuildCreateResponse"; export * from "./GuildDiscoveryRequirements"; export * from "./GuildMessagesSearchResponse"; +export * from "./GuildProfileResponse"; export * from "./GuildPruneResponse"; export * from "./GuildRecommendationsResponse"; export * from "./GuildVanityUrl";