Invite schemas?

This commit is contained in:
Rory&
2026-09-14 19:26:10 +02:00
parent 104e2aa016
commit 1c01ecd773
10 changed files with 111 additions and 25 deletions
Binary file not shown.
Binary file not shown.
@@ -1,14 +1,10 @@
using System.Diagnostics.Metrics;
using System.Text.Json;
using System.Text.Json.Serialization;
using ArcaneLibs.Extensions;
using Microsoft.EntityFrameworkCore;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using Spacebar.Interop.Authentication;
using Spacebar.Interop.Authentication.AspNetCore;
using Spacebar.Models.Db.Contexts;
using Spacebar.Models.Generic;
var builder = WebApplication.CreateBuilder(args);
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("APPSETTINGS_PATH")))
@@ -23,6 +23,7 @@ import { Channel, Guild, Invite, PublicInviteRelation, User } from "@spacebar/da
import { InviteCreateEvent, emitEvent } from "@spacebar/util";
import { InviteCreateSchema, isTextChannel } from "@spacebar/schemas";
import { Random } from "@spacebar/extensions";
import { InviteListResponse } from "@spacebar/schemas/api/guilds/Invite";
const router: Router = Router({ mergeParams: true });
@@ -94,7 +95,7 @@ router.get(
permission: "MANAGE_CHANNELS",
responses: {
200: {
body: "APIInviteArray",
body: "InviteListResponse",
},
404: {},
},
@@ -110,12 +111,14 @@ router.get(
}
const { guild_id } = channel;
const invites = await Invite.find({
where: { guild_id, channel_id },
relations: Object.fromEntries(PublicInviteRelation.map((i) => [i, true])), //TODO: cleanup
});
const invites = (
await Invite.find({
where: { guild_id, channel_id },
relations: Object.fromEntries(PublicInviteRelation.map((i) => [i, true])), //TODO: cleanup
})
).map((x) => x.toPublicJSON());
res.status(200).send(invites);
res.status(200).send(invites satisfies InviteListResponse);
},
);
+3 -2
View File
@@ -19,6 +19,7 @@
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api/middlewares";
import { Invite, PublicInviteRelation } from "@spacebar/database";
import { InviteListResponse } from "@spacebar/schemas/api/guilds/Invite";
const router = Router({ mergeParams: true });
@@ -28,7 +29,7 @@ router.get(
permission: "MANAGE_GUILD",
responses: {
200: {
body: "APIInviteArray",
body: "InviteListResponse",
},
},
}),
@@ -48,7 +49,7 @@ router.get(
}),
);
return res.json(invites.filter((i) => !i.isExpired()));
return res.json(invites.filter((i) => !i.isExpired()).map((x) => x.toPublicJSON()) satisfies InviteListResponse);
},
);
+56 -2
View File
@@ -18,8 +18,17 @@
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
import { arrayRemove } from "@spacebar/extensions";
import { Config, Snowflake, handleFile } from "@spacebar/util";
import { DiscoverableGuild, GuildWelcomeScreen, IntegrationGuild } from "@spacebar/schemas";
import { Config, handleFile, Snowflake } from "@spacebar/util";
import {
DiscoverableGuild,
GuildNsfwLevel,
GuildPremiumTier,
GuildProfileResponse,
GuildVerificationLevel,
GuildVisibilityLevel,
GuildWelcomeScreen,
IntegrationGuild,
} from "@spacebar/schemas";
import { Ban } from "./Ban";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
@@ -33,6 +42,7 @@ import { User } from "./User";
import { VoiceState } from "./VoiceState";
import { Webhook } from "./Webhook";
import { Categories } from "./Categories";
import { InviteGuild } from "@spacebar/schemas/api/guilds/Invite";
// TODO: application_command_count, application_command_counts: {1: 0, 2: 0, 3: 0}
// TODO: guild_scheduled_events
// TODO: stage_instances
@@ -512,4 +522,48 @@ export class Guild extends BaseClass {
presence_count: undefined,
};
}
toInviteGuild(): InviteGuild {
return {
id: this.id,
name: this.name,
icon: this.icon ?? null,
description: this.description ?? null,
banner: this.banner ?? null,
splash: this.splash ?? null,
verification_level: this.verification_level ?? GuildVerificationLevel.NONE,
features: this.features,
vanity_url_code: null, //this.vanity_url_code, // TODO: store this in db?
premium_subscription_count: this.premium_subscription_count,
premium_tier: this.premium_tier ?? GuildPremiumTier.NONE,
nsfw: this.nsfw,
nsfw_level: this.nsfw_level ?? GuildNsfwLevel.DEFAULT,
} satisfies InviteGuild;
}
toGuildProfile(): GuildProfileResponse {
return {
id: this.id,
name: this.name,
icon_hash: this.icon ?? null,
member_count: this.member_count ?? 0,
online_count: this.presence_count ?? 0,
description: this.description ?? "",
brand_color_primary: undefined, // TODO
game_application_ids: [], // TODO
game_activity: {}, // TODO
tag: null, // TODO
badge: null, // TODO
badge_color_primary: "", // TODO
badge_color_secondary: "", // TODO
badge_hash: "", // TODO
traits: [], // TODO
features: this.features, // TODO: should we filter this?
visibility: GuildVisibilityLevel.PUBLIC, // TODO
custom_banner_hash: this.discovery_splash ?? null,
premium_subscription_count: this.premium_subscription_count ?? 0,
premium_tier: this.premium_tier ?? GuildPremiumTier.NONE,
banner_hash: null, // Deprecated, TODO: clan banner hash
} satisfies GuildProfileResponse;
}
}
+20 -3
View File
@@ -22,6 +22,7 @@ import { Channel } from "./Channel";
import { Guild } from "./Guild";
import { Member } from "./Member";
import { User } from "./User";
import { InviteType, PublicInvite } from "@spacebar/schemas/api/guilds/Invite";
export const PublicInviteRelation = ["inviter", "guild", "channel"];
@@ -104,10 +105,26 @@ export class Invite extends BaseClassWithoutId {
if (this.max_uses !== 0 && this.uses >= this.max_uses) return true;
return false;
}
toPublicJSON() {
toPublicJSON(): PublicInvite {
return {
...this,
inviter: this.inviter.toPublicUser(),
code: this.code,
type: InviteType.GUILD, // TODO: support other invite types
channel: this.channel.toJSON(),
guild_id: this.guild_id,
guild: this.guild.toInviteGuild(),
profile: this.guild.toGuildProfile(),
inviter: this.inviter.toPartialUser(),
flags: this.flags,
expires_at: this.expires_at?.toString() ?? null,
approximate_member_count: this.guild.member_count,
approximate_presence_count: this.guild.presence_count,
is_nickname_changeable: true, // TODO
new_member: true, // TODO
roles: [], // TODO
show_verification_form: false, // TODO
target_type: undefined, // TODO
target_user: undefined, // TODO
target_users_job_status: undefined, // TODO
};
}
@@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { GuildPremiumTier } from "@spacebar/schemas";
export interface GuildProfileResponse {
id: string;
name: string;
@@ -23,12 +25,12 @@ export interface GuildProfileResponse {
member_count: number;
online_count: number;
description: string;
brand_color_primary: string;
brand_color_primary?: string;
banner_hash: string | null;
game_application_ids: string[];
game_activity: { [id: string]: GameActivity };
tag: string | null;
badge: GuildBadgeType;
badge: GuildBadgeType | null;
badge_color_primary: string;
badge_color_secondary: string;
badge_hash: string;
@@ -37,7 +39,7 @@ export interface GuildProfileResponse {
visibility: GuildVisibilityLevel;
custom_banner_hash: string | null;
premium_subscription_count: number;
premium_tier: number;
premium_tier: GuildPremiumTier;
}
export interface GameActivity {
+18 -4
View File
@@ -20,17 +20,18 @@ import { Snowflake } from "../../Identifiers";
import { PartialUser } from "../users";
import { GuildNsfwLevel, GuildPremiumTier, GuildVerificationLevel } from "./GuildSchema";
import { GuildProfileResponse, PublicChannel, RoleColors } from "@spacebar/schemas";
import { Role } from "@spacebar/database";
export interface Invite {
export type InviteListResponse = PublicInvite[];
export interface PublicInvite {
code: string;
type: number;
type: InviteType;
channel: PublicChannel | null; // TODO: Figure out what fields this is *supposed* to have as it's labeled as "Partial channel object"
guild_id?: Snowflake;
guild?: InviteGuild;
profile?: GuildProfileResponse;
inviter?: PartialUser;
flags?: number;
flags?: InviteFlags;
target_type?: number;
target_user?: PartialUser;
// target_application?: PartialApplication; // TODO - voice activities
@@ -45,6 +46,19 @@ export interface Invite {
target_users_job_status?: InviteTargetUsersJob; // TODO
}
export enum InviteType {
GUILD = 0,
GROUP_DM = 1,
FRIEND = 2,
}
export enum InviteFlags {
IS_GUEST_INVITE = 1 << 0, // temporary voice channel access
IS_VIEWED = 1 << 1, // TODO
IS_ENHANCED = 1 << 2, // Unknown
IS_APPLICATION_BYPASS = 1 << 3, // Bypass guild join requests -> pending=false
}
export interface InviteTargetUsersJob {
status: InviteTargetUsersJobStatus;
total_users: number;
-1
View File
@@ -37,7 +37,6 @@ export type ApplicationDetectableResponse = unknown[];
export type ApplicationEntitlementsResponse = unknown[];
export type ApplicationSkusResponse = unknown[];
export type APIApplicationArray = Application[];
export type APIInviteArray = Invite[];
export type APIDiscoveryCategoryArray = Categories[];
export type APIChannelArray = Channel[];