all repos — stealth-developers @ 19c7bd4f30727e8414aa1cdee33d83ada53035a7

bot: initiate bot actor for each guild; provisioning status for tickets
vi did:web:vt3e.cat
Wed, 24 Jun 2026 04:25:10 +0100
commit

19c7bd4f30727e8414aa1cdee33d83ada53035a7

parent

ffd6c0466e217d92c38d70ca07fd3a17fa91b63c

6 files changed, 64 insertions(+), 95 deletions(-)

jump to
A apps/bot/src/events/index.ts

@@ -0,0 +1,41 @@

+import type { OAuth2Guild } from "discord.js"; +import { upsertActor, upsertGuild } from "@stealth-developers/db"; +import { client } from "@/client"; +import { logger } from "@/lib"; + +client.on("clientReady", async () => { + const guilds = client.guilds.fetch(); + if (!guilds) return; + + for (const guild of (await guilds).values()) await getGuildActor(guild); +}); + +async function getGuildActor(guild: OAuth2Guild) { + const member = await client.guilds.cache.get(guild.id)?.members.fetchMe(); + if (!member) return; + + const dbGuild = await upsertGuild({ + discordId: guild.id, + icon: guild.iconURL(), + name: guild.name, + }); + if (!dbGuild) return; + + const actor = await upsertActor({ + moderator: true, + guild: { + discordId: guild.id, + icon: guild.iconURL(), + name: guild.name, + }, + user: { + discordId: member.user.id, + username: member.user.username, + displayName: member.displayName, + avatar: member.user.avatarURL(), + }, + }); + + logger.info({ actor }, `upserted guild actor for ${guild.id} (${guild.name})`); + return actor; +}
M apps/bot/src/feats/tickets/actions.tsapps/bot/src/feats/tickets/actions.ts

@@ -80,7 +80,7 @@ .values({

id: publicId, guildId: creator.guildId, localId: localId, - status: "open", + status: "provisioning", openedAt: new Date(), openedBy: creator.id, subject: subjectUser.id,

@@ -89,11 +89,8 @@ .returning();

if (!ticket) throw new PublicError("Failed to add ticket to database, please try again."); - const ticketNumber = ticket.localId; - const paddedTicketNumber = String(ticketNumber).padStart(4, "0"); - const channel = await guild.channels.create({ - name: `ticket-${paddedTicketNumber}`, + name: channelName(localId, ticket.status), type: ChannelType.GuildText, parent: ticketCategory, });

@@ -162,7 +159,15 @@ components,

flags: ["IsComponentsV2"], }); - await tx.update(tickets).set({ channelId: channel.id }).where(eq(tickets.id, ticket.id)); + await tx + .update(tickets) + .set({ channelId: channel.id, status: "open" }) + .where(eq(tickets.id, ticket.id)); + + await channel.edit({ + name: channelName(localId, "open"), + }); + return channel; }); });

@@ -242,3 +247,13 @@ }

return result.unwrap(); } + +export function channelName(localId: number, status: string) { + const initNumber = localId.toString().padStart(4, "0"); + + // discord errors if we try to create channels with certain phrases in them + const bad = ["1488", "1919"]; + if (bad.includes(initNumber)) return `${status}-filtered`; + + return `${status}-ticket-${localId}`; +}
M apps/bot/src/index.tsapps/bot/src/index.ts

@@ -4,6 +4,8 @@ import { logger } from "$/logger";

import { client, kitten } from "@/client"; import { commands, components } from "f/index"; +import "./events"; + const main = async () => { try { await client.login(config.discord.token);
D apps/bot/src/preconditions/guild.ts

@@ -1,37 +0,0 @@

-import { container, Precondition } from "@sapphire/framework"; -import config from "@stealth-developers/config"; -import type { ChatInputCommandInteraction, ContextMenuCommandInteraction } from "discord.js"; - -export class GuildCheckPrecondition extends Precondition { - public override chatInputRun(interaction: ChatInputCommandInteraction) { - if (!interaction.guildId) - return this.error({ message: "This command can only be used in a server." }); - if (interaction.guildId === config.discord.server.id) return this.ok(); - - return this.error({ - message: "This command can only be used in the configured server.", - identifier: "CONFIGURED_GUILD_ONLY", - }); - } - - public override contextMenuRun(interaction: ContextMenuCommandInteraction) { - if (interaction.guildId !== config.discord.server.id) return this.ok(); - - return this.error({ - message: "This command can only be used in the configured server.", - identifier: "CONFIGURED_GUILD_ONLY", - }); - } -} - -declare module "@sapphire/framework" { - interface Preconditions { - GuildCheck: never; - } -} - -container.stores.loadPiece({ - name: "GuildCheck", - piece: GuildCheckPrecondition, - store: "preconditions", -});
D apps/bot/src/preconditions/index.ts

@@ -1,2 +0,0 @@

-import "./guild"; -import "./moderator";
D apps/bot/src/preconditions/moderator.ts

@@ -1,50 +0,0 @@

-import type { ChatInputCommandInteraction, ContextMenuCommandInteraction } from "discord.js"; -import { container, Precondition } from "@sapphire/framework"; -import { getActorByDiscordIdAndGuildDiscordId } from "@stealth-developers/db"; - -export class ModeratorCheckPrecondition extends Precondition { - public override async chatInputRun(interaction: ChatInputCommandInteraction) { - if (!interaction.inGuild()) - return this.error({ message: "This command can only be used in a server." }); - - const actor = await getActorByDiscordIdAndGuildDiscordId( - interaction.user.id, - interaction.guildId, - ); - if (actor?.moderator) return this.ok(); - - return this.error({ - message: "This command can only be usedby moderators.", - identifier: "MODERATOR_ONLY", - }); - } - - public override async contextMenuRun(interaction: ContextMenuCommandInteraction) { - if (!interaction.inGuild()) - return this.error({ message: "This command can only be used in a server." }); - - const actor = await getActorByDiscordIdAndGuildDiscordId( - interaction.user.id, - interaction.guildId, - ); - - if (actor?.moderator) return this.ok(); - - return this.error({ - message: "This option can only be usedby moderators.", - identifier: "MODERATOR_ONLY", - }); - } -} - -declare module "@sapphire/framework" { - interface Preconditions { - ModeratorCheck: never; - } -} - -container.stores.loadPiece({ - name: "ModeratorCheck", - piece: ModeratorCheckPrecondition, - store: "preconditions", -});