From 55ffabb352be0ade346285213896b4084a026a7e Mon Sep 17 00:00:00 2001 From: Bas van Zanten Date: Thu, 26 Sep 2024 10:11:07 +0200 Subject: [PATCH] feat: discord-bot (#1069) * feat: discord-bot * feat: final things * chore: add to matrix * feat: add sentry * chore: move some things --- .github/workflows/cd.yaml | 1 + .github/workflows/ci.yaml | 1 + .gitignore | 3 +- apps/api-worker/package.json | 2 +- apps/discord-bot/package.json | 25 + apps/discord-bot/src/commands/help.ts | 83 ++++ apps/discord-bot/src/commands/info.ts | 289 ++++++++++++ apps/discord-bot/src/commands/presence.ts | 95 ++++ apps/discord-bot/src/constants.ts | 69 +++ apps/discord-bot/src/events/guildMemberAdd.ts | 31 ++ .../src/events/guildMemberRemove.ts | 12 + .../src/events/guildMemberUpdate.ts | 80 ++++ .../src/events/interactionCreate.ts | 42 ++ apps/discord-bot/src/events/ready.ts | 179 ++++++++ apps/discord-bot/src/index.ts | 89 ++++ .../src/util/createStandardEmbed.ts | 25 + apps/discord-bot/src/util/getActivity.ts | 21 + apps/discord-bot/src/util/loadCommands.ts | 39 ++ apps/discord-bot/src/util/loadEvents.ts | 8 + apps/discord-bot/src/util/logger.ts | 41 ++ apps/discord-bot/src/util/presenceList.ts | 14 + apps/discord-bot/tsconfig.app.json | 8 + apps/discord-bot/tsconfig.json | 8 + packages/db/package.json | 2 +- packages/db/src/AlphaUsers.ts | 11 + packages/db/src/BetaUsers.ts | 11 + packages/db/src/Credits.ts | 35 ++ packages/db/src/DiscordUsers.ts | 15 + packages/db/src/Presence.ts | 2 +- packages/db/src/index.ts | 4 + pnpm-lock.yaml | 427 ++++++++++++++---- tsconfig.app.json | 3 + 32 files changed, 1586 insertions(+), 89 deletions(-) create mode 100644 apps/discord-bot/package.json create mode 100644 apps/discord-bot/src/commands/help.ts create mode 100644 apps/discord-bot/src/commands/info.ts create mode 100644 apps/discord-bot/src/commands/presence.ts create mode 100644 apps/discord-bot/src/constants.ts create mode 100644 apps/discord-bot/src/events/guildMemberAdd.ts create mode 100644 apps/discord-bot/src/events/guildMemberRemove.ts create mode 100644 apps/discord-bot/src/events/guildMemberUpdate.ts create mode 100644 apps/discord-bot/src/events/interactionCreate.ts create mode 100644 apps/discord-bot/src/events/ready.ts create mode 100644 apps/discord-bot/src/index.ts create mode 100644 apps/discord-bot/src/util/createStandardEmbed.ts create mode 100644 apps/discord-bot/src/util/getActivity.ts create mode 100644 apps/discord-bot/src/util/loadCommands.ts create mode 100644 apps/discord-bot/src/util/loadEvents.ts create mode 100644 apps/discord-bot/src/util/logger.ts create mode 100644 apps/discord-bot/src/util/presenceList.ts create mode 100644 apps/discord-bot/tsconfig.app.json create mode 100644 apps/discord-bot/tsconfig.json create mode 100644 packages/db/src/AlphaUsers.ts create mode 100644 packages/db/src/BetaUsers.ts create mode 100644 packages/db/src/Credits.ts create mode 100644 packages/db/src/DiscordUsers.ts diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 1ea1dca..b07db58 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -20,6 +20,7 @@ jobs: - website - api-worker - api-master + - discord-bot steps: - name: Checkout Repository uses: actions/checkout@v4 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f4b493c..06b1022 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,6 +48,7 @@ jobs: - api-worker - api-master - website + - discord-bot steps: - name: Checkout Repository uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index d619766..52bb4ab 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ src/update.ini coverage *.tsbuildinfo -.DS_Store \ No newline at end of file +.DS_Store +*.log \ No newline at end of file diff --git a/apps/api-worker/package.json b/apps/api-worker/package.json index a82d921..325bf03 100644 --- a/apps/api-worker/package.json +++ b/apps/api-worker/package.json @@ -36,7 +36,7 @@ "graphql-parse-resolve-info": "^4.13.0", "graphql-yoga": "^5.6.0", "ioredis": "^5.3.2", - "mongoose": "^8.5.1" + "mongoose": "^8.6.3" }, "devDependencies": { "@graphql-codegen/cli": "5.0.2", diff --git a/apps/discord-bot/package.json b/apps/discord-bot/package.json new file mode 100644 index 0000000..0ebf133 --- /dev/null +++ b/apps/discord-bot/package.json @@ -0,0 +1,25 @@ +{ + "name": "@premid/discord-bot", + "type": "module", + "version": "0.0.1", + "private": true, + "description": "PreMiD's discord bot", + "license": "MPL-2.0", + "main": "dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "start": "node --enable-source-maps .", + "dev": "node --watch --env-file .env --enable-source-maps ." + }, + "dependencies": { + "@premid/db": "workspace:*", + "@sentry/node": "^8.17.0", + "defu": "^6.1.4", + "discord.js": "^14.16.2", + "glob": "^11.0.0", + "mongoose": "^8.2.0", + "winston": "^3.14.2" + } +} diff --git a/apps/discord-bot/src/commands/help.ts b/apps/discord-bot/src/commands/help.ts new file mode 100644 index 0000000..fbbe2c7 --- /dev/null +++ b/apps/discord-bot/src/commands/help.ts @@ -0,0 +1,83 @@ +import { + ActionRowBuilder, + type AutocompleteInteraction, + ButtonBuilder, + ButtonStyle, + type ChatInputCommandInteraction, + SlashCommandBuilder, +} from "discord.js"; +import { createStandardEmbed } from "../util/createStandardEmbed.js"; +import { type Command, commands } from "../util/loadCommands.js"; + +export default { + data: new SlashCommandBuilder() + .setName("help") + .setDescription("Shows help and usage information for PreMiD commands") + .addStringOption(option => + option + .setName("command") + .setDescription("The specific command to get help for") + .setAutocomplete(true), + ), + autocomplete: async (interaction: AutocompleteInteraction) => { + const focusedValue = interaction.options.getFocused(); + const choices = [...commands.values()] + .filter(cmd => cmd.help) + .map(cmd => ({ name: cmd.help!.name, value: cmd.help!.value })); + + const filtered = choices.filter(choice => choice.name.toLowerCase().includes(focusedValue.toLowerCase())); + return interaction.respond(filtered.slice(0, 25)); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + const command = interaction.options.getString("command"); + + if (command) { + const help = [...commands.values()].find(({ help }) => help?.value === command)?.help; + if (!help) + return interaction.reply({ content: "Command not found", ephemeral: true }); + + return interaction.reply({ + embeds: [help.embed], + ephemeral: true, + }); + } + + const generalCommands = []; + + for (const cmd of commands.values()) { + if (cmd.help) { + if (cmd.help.command) + generalCommands.push(`\`${cmd.help.command}\` - ${cmd.help.commandDescription}`); + } + } + + const embed = createStandardEmbed({ + title: "🛠️ PreMiD Help", + description: "PreMiD is a simple, configurable utility that allows you to show what you're doing on the web in your Discord now playing status.", + fields: [ + { + name: "🔧 Commands", + value: generalCommands.join("\n") || "No commands available", + inline: false, + }, + { + name: "📚 Additional Information", + value: "Use `/help ` for detailed information about a specific command.", + inline: false, + }, + ], + }); + + return interaction.reply({ + embeds: [embed], + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder().setLabel("View Website").setURL("https://premid.app/").setStyle(ButtonStyle.Link), + new ButtonBuilder().setLabel("View Store").setURL("https://premid.app/store").setStyle(ButtonStyle.Link), + new ButtonBuilder().setLabel("Downloads").setURL("https://premid.app/downloads").setStyle(ButtonStyle.Link), + ), + ], + ephemeral: true, + }); + }, +} satisfies Command; diff --git a/apps/discord-bot/src/commands/info.ts b/apps/discord-bot/src/commands/info.ts new file mode 100644 index 0000000..8fdc5c5 --- /dev/null +++ b/apps/discord-bot/src/commands/info.ts @@ -0,0 +1,289 @@ +import type { APIButtonComponent, AutocompleteInteraction, ChatInputCommandInteraction, ColorResolvable } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from "discord.js"; +import { createStandardEmbed } from "../util/createStandardEmbed.js"; +import type { Command } from "../util/loadCommands.js"; + +const shortInfos: { + [key: string]: { + title: string; + description: string; + emoji?: string; + image?: string; + color?: ColorResolvable; + links?: Partial[]; + }; +} = { + troubleshooting: { + title: "Troubleshooting", + emoji: "❓", + description: + "If you have problems with PreMiD, you should read our troubleshooting guide and if that doesn't help, create a new post in <#1019726199494279248>.", + + links: [ + { + label: "Troubleshooting Guide", + url: "https://docs.premid.app/troubleshooting", + }, + ], + }, + modifiedClients: { + title: "Modified Clients", + color: "#FF5050", + description: + "Using a modified client is an violation of Discord's ToS and therefore you run the risk of losing your account. If you want to keep using Discord, you have to follow them and make sure you're not breaking any of the rules Discord. Even using modified clients for theming or other customizations are against Discord's ToS. If you don't believe us, read it yourself.", + links: [ + { + label: "Discord's ToS", + url: "https://discordapp.com/terms", + }, + { + label: "Discord's Tweet", + url: "https://twitter.com/discordapp/status/908000828690182145", + }, + ], + }, + creatingAPresence: { + title: "Creating a Presence", + emoji: "🏗", + description: + "If you wish to add support for a service that does not have a Presence yet, you can either open an issue on GitHub to request the presence to be created or you create it yourself. If you wish to create a Presence for PreMiD you need to have basic knowledge of TypeScript. For more information and docs on how to create a Presence follow our documentation.", + links: [ + { + label: "Documentation", + url: "https://docs.premid.app/dev/presence", + }, + { + label: "Service Request", + url: "https://github.com/PreMiD/Presences/issues/new?assignees=&labels=Service+Request&template=service_request.yml", + }, + ], + }, + docs: { + title: "Read the Docs", + description: + "If you have any questions regarding PreMiD, please read our documentation before creating a ticket. Presence development related queries should be redirected to <#607524579874832446>", + links: [ + { + label: "Documentation", + url: "https://docs.premid.app", + }, + ], + }, + website: { + title: "Visit Our Website", + emoji: "🌐", + description: "Press the button below to visit our website full of greatness.", + links: [ + { + label: "Website", + url: "https://premid.app", + }, + ], + }, + presenceStore: { + title: "Presence Store", + emoji: "🏪", + description: "Press the button below to visit our Presence Store full of the your favourite services!", + links: [{ label: "Presence Store", url: "https://premid.app/store" }], + }, + downloadPreMiD: { + title: "Download PreMiD", + emoji: "📦", + description: + "You can download PreMiD and its extension for your browser, but don't forget that **you need both application and extension** to get PreMiD to work.\n :warning: PreMiD does not support the web version of Discord, you **must** use the desktop version of Discord.", + links: [{ label: "Downloads", url: "https://premid.app/downloads" }], + }, + donate: { + title: "Donate", + emoji: "💵", + description: + "Want to support PreMiD's development? Great! You can do so by boosting our Discord server, which you will get a special role, or you can support us on Patreon!", + links: [ + { label: "Patreon", url: "https://patreon.com/Timeraa" }, + { label: "GitHub Sponsors", url: "https://github.com/sponsors/Timeraa" }, + ], + }, + creatingATicket: { + title: "Creating a Support Ticket", + emoji: "🙋", + description: + "Recently, we have migrated to use Discord's new forum channels for our support system. You can now create a ticket by creating a new post in <#1019726199494279248>", + }, + suggestingAPresence: { + title: "Suggesting a Presence", + emoji: "🗳", + description: + "If you'd like to suggest a presence, you can do this on our GitHub repository by creating a new issue with the Service Request template! If want to create a Presence yourself, you can find more information on our documentation", + links: [ + { + label: "PreMiD Documentation", + url: "https://docs.premid.app/dev/presence", + }, + { + label: "GitHub Repository", + url: "https://github.com/PreMiD/Presences", + }, + { + label: "Service Request", + url: "https://github.com/PreMiD/Presences/issues/new?assignees=&labels=Service+Request&template=service_request.yml", + }, + ], + }, + tos: { + title: "PreMiD and Discord", + emoji: "🧬", + description: "PreMiD is compliant to Discord's ToS and therefore you can use it without any risk of losing your Discord account.", + links: [ + { + label: "Proof", + url: "https://twitter.com/discord/status/1233704070390669312", + }, + ], + }, + unidentifiedDeveloper: { + title: "Allow apps from unidentified developers (macOS)", + description: + "Steps for **macOS Big Sur (11.0+)**:\n1. Right click on our installer.\n2. Click `Open` in the dropdown menu.\n3. Click `Open` in popup.\n\nSteps for **older macOS versions**:\n1. Open System Preferences.\n2. Go to the Security & Privacy tab.\n3. Click on the lock and enter your password or scan your fingerprint so you can make changes.\n4. Change the setting for 'Allow apps downloaded from' to 'App Store and identified developers' from just 'App Store'.", + }, + reportingaPresenceBug: { + title: "Reporting a Presence bug", + emoji: "🐛", + description: + "If you've found an issue with a presence, it is important that you report your issue on the Presence repository so the bug is resolved within a timely fashion. You can report the bug using the Bug Report template, **ensuring you fill in the template properly**.", + links: [ + { + label: "Presence Repository", + url: "https://github.com/PreMiD/Presences", + }, + { + label: "Bug Report", + url: "https://github.com/PreMiD/Presences/issues/new?assignees=&labels=%F0%9F%90%9B+Bug&template=bug_report.yml&title=Service+Name+%7C+Service+URL", + }, + ], + }, + adblockDetection: { + title: "Adblock Detection", + emoji: "🚫", + description: + "If our website has falsely detected the presence of an ad-blocker, you can simply press \"I don't want to support\" six times and you will be redirected to the download. Alternatively, you can find direct download links below.", + links: [ + { + label: "Download Links", + url: "https://discord.com/channels/493130730549805057/527675240231206934/715852870062309386", + }, + ], + }, + requestANewFeature: { + title: "Requesting a Presence feature", + emoji: "🗳", + description: + "Does a presence you use not support a crucial page or not support all the possible domains for the website? If you believe a presence should include more features, you should open an issue on the Presence Repository using the Feature Request template.", + links: [ + { + label: "Template", + url: "https://github.com/PreMiD/Presences/issues/new?assignees=&labels=Feature+Request&template=feature_request.yml", + }, + ], + }, + beta: { + title: "PreMiD Beta", + emoji: "✨", + description: + "Do you want cool new features? Want to use PreMiD with the browser version of Discord? Download the beta!", + links: [ + { + label: "Beta Release Page", + url: "https://premid.app/beta", + }, + ], + }, + frequentFixes: { + title: "Frequent fixes for Presence bugs", + emoji: "🗳", + description: + "There are some frequent fixes for presences, use the buttons to navigate to these.\n If this doesn't work, please submit your issue to <#1019726199494279248>", + links: [ + { + label: "YouTube/Netflix", + url: "https://discord.com/channels/493130730549805057/527675240231206934/831995042469642251", + }, + { + label: "YouTube", + url: "https://discord.com/channels/493130730549805057/527675240231206934/827037909504753704", + }, + { + label: "General fix", + url: "https://discord.com/channels/493130730549805057/527675240231206934/723231955893747763", + }, + ], + }, +}; + +export default { + data: new SlashCommandBuilder() + .setName("info") + .setDescription("Posts an information message") + .addStringOption(option => + option + .setName("query") + .setDescription("The infomation message to search for") + .setAutocomplete(true), + ) + .addUserOption(option => + option + .setName("user") + .setDescription("User to mention") + .setRequired(false), + ), + autocomplete: async (interaction: AutocompleteInteraction) => { + const focusedValue = interaction.options.getFocused(); + const choices = Object.entries(shortInfos).map(([key, data]) => ({ name: data.title, value: key })); + + const filtered = choices.filter(choice => choice.name.toLowerCase().includes(focusedValue.toLowerCase())); + return interaction.respond(filtered.slice(0, 25)); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + const query = interaction.options.getString("query"); + const user = interaction.options.getUser("user"); + + if (!query) + return interaction.reply({ content: "Please provide a query to search for", ephemeral: true }); + + const info = shortInfos[query]; + if (!info) + return interaction.reply({ content: "No information found for that query", ephemeral: true }); + + const embed = createStandardEmbed({ + title: `${info.emoji || "🔖"} ${info.title}`, + description: info.description, + }); + + let actionRow: ActionRowBuilder | undefined; + if (info.links) { + actionRow = new ActionRowBuilder(); + + for (const link of info.links) { + actionRow.addComponents(new ButtonBuilder({ + style: ButtonStyle.Link, + ...link, + })); + } + } + + return interaction.reply({ embeds: [embed], content: user ? user.toString() : undefined, components: actionRow ? [actionRow] : undefined }); + }, + help: { + name: "info", + value: "info", + command: "/info [user]", + commandDescription: "Posts an information message", + embed: createStandardEmbed({ + title: "Command: /info", + description: "Posts an information message", + fields: [ + { name: "Usage", value: "`/info [user]`", inline: true }, + { name: "Example", value: "`/info troubleshooting`\n`/info beta @User`", inline: true }, + ], + }), + }, +} satisfies Command; diff --git a/apps/discord-bot/src/commands/presence.ts b/apps/discord-bot/src/commands/presence.ts new file mode 100644 index 0000000..170a5ed --- /dev/null +++ b/apps/discord-bot/src/commands/presence.ts @@ -0,0 +1,95 @@ +import type { AutocompleteInteraction, ChatInputCommandInteraction } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from "discord.js"; +import { Presence } from "@premid/db"; +import { createStandardEmbed } from "../util/createStandardEmbed.js"; +import type { Command } from "../util/loadCommands.js"; +import { presenceList } from "../util/presenceList.js"; +import { client } from "../constants.js"; + +export default { + data: new SlashCommandBuilder() + .setName("presence") + .setDescription("Search for a presence") + .addStringOption(option => + option + .setName("query") + .setDescription("The presence to search for") + .setAutocomplete(true), + ), + autocomplete: async (interaction: AutocompleteInteraction) => { + const focusedValue = interaction.options.getFocused(); + const filtered = presenceList.filter(({ service }) => service.toLowerCase().includes(focusedValue.toLowerCase())); + return interaction.respond(filtered.slice(0, 25).map(({ service }) => ({ name: service, value: service }))); + }, + execute: async (interaction: ChatInputCommandInteraction) => { + const query = interaction.options.getString("query"); + + if (!query) + return interaction.reply({ content: "Please provide a query to search for", ephemeral: true }); + + const presence = await Presence.findOne({ name: query }, { + _id: false, + metadata: { + service: true, + author: { id: true }, + contributors: { id: true }, + url: true, + description: { + en: true, + }, + logo: true, + color: true, + }, + }); + + if (!presence) + return interaction.reply({ content: "Presence not found", ephemeral: true }); + + const embed = createStandardEmbed({ + title: presence.metadata.service, + description: presence.metadata.description.en, + color: presence.metadata.color, + fields: presence.metadata.contributors?.length + ? [{ + name: "Contributors", + value: presence.metadata.contributors.map(contributor => `<@${contributor.id}>`).join(", "), + }] + : undefined, + }); + + embed.setURL(`https://${Array.isArray(presence.metadata.url) ? presence.metadata.url[0] : presence.metadata.url}`); + embed.setThumbnail(presence.metadata.logo); + + const author = await client.users.fetch(presence.metadata.author.id).catch(() => null); + if (author) { + embed.setAuthor({ + name: author.username, + iconURL: author.displayAvatarURL(), + }); + } + + return interaction.reply({ embeds: [embed], components: [ + new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setLabel("Open in Store") + .setURL(`https://premid.app/store/presences/${encodeURI(presence.metadata.service)}`) + .setStyle(ButtonStyle.Link), + ), + ] }); + }, + help: { + name: "presence", + value: "presence", + command: "/presence ", + commandDescription: "Search for a presence", + embed: createStandardEmbed({ + title: "Command: /presence", + description: "Search for a presence", + fields: [ + { name: "Usage", value: "`/presence `", inline: true }, + { name: "Example", value: "`/presence YouTube`", inline: true }, + ], + }), + }, +} satisfies Command; diff --git a/apps/discord-bot/src/constants.ts b/apps/discord-bot/src/constants.ts new file mode 100644 index 0000000..672f52a --- /dev/null +++ b/apps/discord-bot/src/constants.ts @@ -0,0 +1,69 @@ +import process from "node:process"; +import { defu } from "defu"; +import { Client, GatewayIntentBits, REST } from "discord.js"; + +export const processEnv = defu({ + TOKEN: process.env.TOKEN, + DATABASE_URL: process.env.DATABASE_URL, + SENTRY_DSN: process.env.SENTRY_DSN, +}, { + TOKEN: "", + DATABASE_URL: "mongodb://localhost:27017/premid", + SENTRY_DSN: "", + GUILD_ID: "493130730549805057", + BETA_ROLE: "591284574823120909", + ALPHA_ROLE: "694481247564595211", +}); + +export const client = new Client({ + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildPresences], + presence: { + status: "online", + }, +}); + +export const rest = new REST().setToken(processEnv.TOKEN); + +export const roles = { + PROJECT_LEADER: "493135149274365975", + STAFF_COORDINATOR: "691382096878370837", + ADMINISTRATOR: "685969048399249459", + PROJECT_MANAGEMENT: "673682085608816652", + REVIEWER: "630445337143935009", + DEVELOPER: "691396820236107837", + DESIGNER: "691386502566903850", + MODERATOR: "514546359865442304", + SUPPORT_AGENT: "566417964820070421", + MARKETING_DIRECTOR: "673681900476432387", + LOCALIZATION_MANAGER: "811262682408943616", + REPRESENTATIVE: "691384256672563332", + CONTRIBUTOR: "1032759805732978708", + PATRON: "515874214750715904", + DONATOR: "502165799172309013", + BOOSTER: "585532751663333383", + PROOFREADER: "522755339448483840", + TRANSLATOR: "502148045991968788", + PRESENCE_DEV: "606222296016879722", +} as const; + +export const roleColors = { + PROJECT_LEADER: "#E43725", + STAFF_COORDINATOR: "#E43725", + ADMINISTRATOR: "#E43725", + PROJECT_MANAGEMENT: "#E43725", + REVIEWER: "#3BA576", + DEVELOPER: "#3BA576", + DESIGNER: "#3BA576", + MODERATOR: "#D67118", + SUPPORT_AGENT: "#D67118", + MARKETING_DIRECTOR: "#3BA576", + LOCALIZATION_MANAGER: "#3BA576", + REPRESENTATIVE: "#3BA576", + CONTRIBUTOR: "#EB459E", + PATRON: "#E5472F", + DONATOR: "#FFAA33", + BOOSTER: "#F265FF", + PROOFREADER: "#00B0E6", + TRANSLATOR: "#2286D0", + PRESENCE_DEV: "#96A5E9", +} as const; diff --git a/apps/discord-bot/src/events/guildMemberAdd.ts b/apps/discord-bot/src/events/guildMemberAdd.ts new file mode 100644 index 0000000..180c287 --- /dev/null +++ b/apps/discord-bot/src/events/guildMemberAdd.ts @@ -0,0 +1,31 @@ +import { Events } from "discord.js"; +import { DiscordUsers, Presence } from "@premid/db"; +import { client, roles as rolesEnv } from "../constants.js"; + +client.on(Events.GuildMemberAdd, async (member) => { + const [presence] = await Promise.all([ + Presence.findOne({ + $or: [{ "metadata.author.id": member.id }, { "metadata.contributors.id": member.id }], + }, { name: true }), + DiscordUsers.updateOne( + { userId: member.id }, + { + $set: { + avatar: member.user.avatar, + created: member.user.createdTimestamp, + discriminator: member.user.discriminator, + userId: member.id, + username: member.user.username, + }, + }, + { upsert: true }, + ), + ]); + + //* User should have Presence Developer Role + if (presence) { + if (!member.roles.cache.has(rolesEnv.PRESENCE_DEV)) { + await member.roles.add(rolesEnv.PRESENCE_DEV, "User should have Presence Developer Role"); + } + } +}); diff --git a/apps/discord-bot/src/events/guildMemberRemove.ts b/apps/discord-bot/src/events/guildMemberRemove.ts new file mode 100644 index 0000000..4070463 --- /dev/null +++ b/apps/discord-bot/src/events/guildMemberRemove.ts @@ -0,0 +1,12 @@ +import { Events } from "discord.js"; +import { AlphaUsers, BetaUsers, Credits, DiscordUsers } from "@premid/db"; +import { client } from "../constants.js"; + +client.on(Events.GuildMemberRemove, async (member) => { + await Promise.all([ + BetaUsers.deleteOne({ userId: member.id }), + AlphaUsers.deleteOne({ userId: member.id }), + DiscordUsers.deleteOne({ userId: member.id }), + Credits.deleteOne({ userId: member.id }), + ]); +}); diff --git a/apps/discord-bot/src/events/guildMemberUpdate.ts b/apps/discord-bot/src/events/guildMemberUpdate.ts new file mode 100644 index 0000000..ee2e62e --- /dev/null +++ b/apps/discord-bot/src/events/guildMemberUpdate.ts @@ -0,0 +1,80 @@ +import { Events } from "discord.js"; +import { AlphaUsers, BetaUsers, Credits, DiscordUsers } from "@premid/db"; +import { client, processEnv, roleColors, roles as rolesEnv } from "../constants.js"; + +client.on(Events.GuildMemberUpdate, async (oldMember, newMember) => { + const highestRole = newMember.roles.cache + .filter(role => (Object.values(rolesEnv) as string[]).includes(role.id)) + .sort((a, b) => b.position - a.position) + .at(0); + + await Promise.all([ + DiscordUsers.updateOne( + { userId: newMember.id }, + { + $set: { + avatar: newMember.user.avatar, + created: newMember.user.createdTimestamp, + discriminator: newMember.user.discriminator, + userId: newMember.id, + username: newMember.user.username, + }, + }, + { upsert: true }, + ), + highestRole + ? Credits.updateOne( + { userId: newMember.id }, + { + $set: { + userId: newMember.id, + name: newMember.user.username, + tag: newMember.user.discriminator, + avatar: newMember.user.avatar, + premium_since: newMember.premiumSince !== null ? newMember.premiumSinceTimestamp! : undefined, + role: newMember.roles.cache.filter(r => r.name !== "@everyone").map(r => r.name), + roleIds: newMember.roles.cache.filter(r => r.name !== "@everyone").map(r => r.id), + roleColor: roleColors[ + Object.entries(rolesEnv).find(([, id]) => id === highestRole.id)![0] as keyof typeof roleColors + ], + rolePosition: highestRole.position, + status: newMember.presence?.status ?? "offline", + }, + }, + { upsert: true }, + ) + : Promise.resolve(), + ]); + + const roles = newMember.roles.cache.map(role => role.id); + + //* User should have Alpha Role + if (roles.includes(rolesEnv.BOOSTER) || roles.includes(rolesEnv.PATRON) || newMember.roles.cache.has(processEnv.ALPHA_ROLE)) { + if (!newMember.roles.cache.has(processEnv.ALPHA_ROLE)) { + await newMember.roles.add(processEnv.ALPHA_ROLE, "User should have Alpha Role"); + } + if (newMember.roles.cache.has(processEnv.BETA_ROLE)) { + await newMember.roles.remove(processEnv.BETA_ROLE, "User should have Alpha Role"); + } + await Promise.all([ + AlphaUsers.updateOne({ userId: newMember.id }, { $set: { userId: newMember.id } }, { upsert: true }), + BetaUsers.deleteOne({ userId: newMember.id }), + ]); + return; + } + + //* User should have Beta Role + const betaUser = await BetaUsers.findOne({ userId: newMember.id }); + if (roles.includes(rolesEnv.DONATOR) || betaUser || newMember.roles.cache.has(processEnv.BETA_ROLE) || oldMember.roles.cache.has(processEnv.ALPHA_ROLE)) { + if (newMember.roles.cache.has(processEnv.ALPHA_ROLE)) { + await newMember.roles.remove(processEnv.ALPHA_ROLE, "User should have Beta Role"); + } + if (!newMember.roles.cache.has(processEnv.BETA_ROLE)) { + await newMember.roles.add(processEnv.BETA_ROLE, "User should have Beta Role"); + } + await Promise.all([ + BetaUsers.updateOne({ userId: newMember.id }, { $set: { userId: newMember.id } }, { upsert: true }), + AlphaUsers.deleteOne({ userId: newMember.id }), + ]); + } +}); diff --git a/apps/discord-bot/src/events/interactionCreate.ts b/apps/discord-bot/src/events/interactionCreate.ts new file mode 100644 index 0000000..3eb4d46 --- /dev/null +++ b/apps/discord-bot/src/events/interactionCreate.ts @@ -0,0 +1,42 @@ +import { Events, InteractionType } from "discord.js"; +import { client } from "../constants.js"; +import { commands } from "../util/loadCommands.js"; +import { logger } from "../util/logger.js"; + +client.on(Events.InteractionCreate, async (interaction) => { + if (!interaction.inGuild()) { + logger.debug("Interaction received outside of a guild, ignoring."); + return; + } + + if (interaction.type === InteractionType.ApplicationCommand || interaction.type === InteractionType.ApplicationCommandAutocomplete) { + const { commandName } = interaction; + logger.info(`Command "${commandName}" ${interaction.type === InteractionType.ApplicationCommandAutocomplete ? "autocomplete" : "executed"} in guild ${interaction.guildId} by user ${interaction.user.id}`); + + if (!commands.has(commandName.toLowerCase())) + return; + + const command = commands.get(commandName.toLowerCase())!; + + if (interaction.type === InteractionType.ApplicationCommandAutocomplete) { + if (command.autocomplete) { + try { + await command.autocomplete(interaction); + logger.debug(`Autocomplete for command "${commandName}" handled successfully`); + } + catch (error) { + logger.error(`Error handling autocomplete for command "${commandName}":`, error); + } + } + return; + } + + try { + await command.execute(interaction); + logger.debug(`Command "${commandName}" executed successfully`); + } + catch (error) { + logger.error(`Error executing command "${commandName}":`, error); + } + } +}); diff --git a/apps/discord-bot/src/events/ready.ts b/apps/discord-bot/src/events/ready.ts new file mode 100644 index 0000000..2369fab --- /dev/null +++ b/apps/discord-bot/src/events/ready.ts @@ -0,0 +1,179 @@ +import type { GuildMember } from "discord.js"; +import { Events } from "discord.js"; +import { AlphaUsers, BetaUsers, Credits, Presence } from "@premid/db"; +import { client, processEnv, roleColors, roles as rolesEnv } from "../constants.js"; +import { logger } from "../util/logger.js"; + +client.once(Events.ClientReady, async () => { + logger.debug("Giving roles to members"); + const guild = await client.guilds.fetch(processEnv.GUILD_ID); + const members = await guild.members.fetch(); + + let count = 0; + for (const [, member] of members) { + count++; + const roles = member.roles.cache.map(role => role.id); + + //* User should have Alpha Role + if (roles.includes(rolesEnv.BOOSTER) || roles.includes(rolesEnv.PATRON) || member.roles.cache.has(processEnv.ALPHA_ROLE)) { + if (!member.roles.cache.has(processEnv.ALPHA_ROLE)) { + await member.roles.add(processEnv.ALPHA_ROLE, "User should have Alpha Role"); + } + if (member.roles.cache.has(processEnv.BETA_ROLE)) { + await member.roles.remove(processEnv.BETA_ROLE, "User should have Alpha Role"); + } + await Promise.all([ + AlphaUsers.updateOne({ userId: member.id }, { $set: { userId: member.id } }, { upsert: true }), + BetaUsers.deleteOne({ userId: member.id }), + ]); + } + else { + const betaUser = await BetaUsers.findOne({ userId: member.id }); + //* User should have Beta Role + if (roles.includes(rolesEnv.DONATOR) || betaUser || member.roles.cache.has(processEnv.BETA_ROLE)) { + if (member.roles.cache.has(processEnv.ALPHA_ROLE)) { + await member.roles.remove(processEnv.ALPHA_ROLE, "User should have Beta Role"); + } + if (!member.roles.cache.has(processEnv.BETA_ROLE)) { + await member.roles.add(processEnv.BETA_ROLE, "User should have Beta Role"); + } + await Promise.all([ + BetaUsers.updateOne({ userId: member.id }, { $set: { userId: member.id } }, { upsert: true }), + AlphaUsers.deleteOne({ userId: member.id }), + ]); + } + } + + if (count % 1000 === 0) { + logger.debug(`Processed ${count}/${members.size} members`); + } + } + logger.debug(`Gave roles to ${count}/${members.size} members`); + + //* Presence Developers + logger.debug("Checking for presence developers"); + const presenceDevelopers = await Presence.find({}, { + "metadata.author.id": true, + "metadata.contributors.id": true, + "_id": false, + }); + + for (const presenceDeveloper of [ + ...new Set( + presenceDevelopers + .map(presence => [presence.metadata.author.id, ...(presence.metadata.contributors?.map(c => c.id) || [])]) + .flat(), + ), + ]) { + const member = guild.members.cache.get(presenceDeveloper); + + if (!member) + continue; + + if (!member.roles.cache.has(rolesEnv.PRESENCE_DEV)) { + await member.roles.add(rolesEnv.PRESENCE_DEV, "User should have Presence Developer Role"); + } + } + logger.debug("Checked for presence developers"); + + //* Update Credits + const usersToCredit = new Set(); + + for (const roleId of Object.values(rolesEnv)) { + const role = await guild.roles.fetch(roleId); + if (!role) + continue; + + for (const member of role.members.values()) { + usersToCredit.add(member); + } + } + + const usersToRemove = await Credits.find({ userId: { $nin: [...usersToCredit].map(member => member.user.id) } }); + + await Credits.bulkWrite([ + ...usersToRemove.map(user => ({ deleteOne: { filter: { userId: user.userId } } })), + ...[...usersToCredit].map((member) => { + const highestRole = member.roles.cache + .filter(role => (Object.values(rolesEnv) as string[]).includes(role.id)) + .sort((a, b) => b.position - a.position) + .at(0)!; + + const color = roleColors[ + Object.entries(rolesEnv).find(([, id]) => id === highestRole.id)![0] as keyof typeof roleColors + ]; + return { + updateOne: { + filter: { userId: member.id }, + update: { + $set: { + userId: member.id, + name: member.user.username, + tag: member.user.discriminator, + avatar: member.user.displayAvatarURL({ + extension: "png", + forceStatic: false, + }), + premium_since: member.premiumSince !== null ? member.premiumSinceTimestamp! : undefined, + role: highestRole.name, + roleId: highestRole.id, + roles: member.roles.cache.filter(r => r.name !== "@everyone").map(r => r.name), + roleIds: member.roles.cache.filter(r => r.name !== "@everyone").map(r => r.id), + roleColor: color, + rolePosition: highestRole.position, + status: member.presence?.status ?? "offline", + }, + }, + upsert: true, + }, + }; + }), + ]); + logger.debug("Updated Credits"); + + //* Beta can be requested from the website so we need to periodically check for that, presence developers are external too so we need to periodically check for that + setInterval(async () => { + //* Beta Users + logger.debug("Checking for beta users"); + const betaUsers = await BetaUsers.find({}); + const guild = await client.guilds.fetch(processEnv.GUILD_ID); + + for (const betaUser of betaUsers) { + const member = guild.members.cache.get(betaUser.userId); + + if (!member) + continue; + + if (!member.roles.cache.has(processEnv.BETA_ROLE)) { + await member.roles.add(processEnv.BETA_ROLE, "User should have Beta Role"); + } + } + logger.debug("Checked for beta users"); + + //* Presence Developers + logger.debug("Checking for presence developers"); + const presenceDevelopers = await Presence.find({}, { + "metadata.author.id": true, + "metadata.contributors.id": true, + "_id": false, + }); + + for (const presenceDeveloper of [ + ...new Set( + presenceDevelopers + .map(presence => [presence.metadata.author.id, ...(presence.metadata.contributors?.map(c => c.id) || [])]) + .flat(), + ), + ]) { + const member = guild.members.cache.get(presenceDeveloper); + + if (!member) + continue; + + if (!member.roles.cache.has(rolesEnv.PRESENCE_DEV)) { + await member.roles.add(rolesEnv.PRESENCE_DEV, "User should have Presence Developer Role"); + } + } + logger.debug("Checked for presence developers"); + }, 1000 * 60 * 5); +}); diff --git a/apps/discord-bot/src/index.ts b/apps/discord-bot/src/index.ts new file mode 100644 index 0000000..06be173 --- /dev/null +++ b/apps/discord-bot/src/index.ts @@ -0,0 +1,89 @@ +import process from "node:process"; +import { connect } from "mongoose"; +import { Routes } from "discord.js"; +import * as Sentry from "@sentry/node"; +import { client, processEnv, rest } from "./constants.js"; +import { getActivity } from "./util/getActivity.js"; +import loadCommands, { commands } from "./util/loadCommands.js"; +import loadEvents from "./util/loadEvents.js"; +import { logger } from "./util/logger.js"; +import { updatePresenceList } from "./util/presenceList.js"; + +Sentry.init({ + integrations: [ + Sentry.mongooseIntegration(), + ], + dsn: processEnv.SENTRY_DSN, +}); + +logger.info("Starting bot initialization..."); + +try { + await loadCommands(); + logger.info("Commands loaded successfully"); +} +catch (error) { + logger.error("Error loading commands:", error); + process.exit(1); +} + +try { + await loadEvents(); + logger.info("Events loaded successfully"); +} +catch (error) { + logger.error("Error loading events:", error); + process.exit(1); +} + +try { + await connect(processEnv.DATABASE_URL, { appName: "PreMiD Discord Bot" }); + logger.info("Successfully connected to database"); + await updatePresenceList(); + logger.info("Successfully updated presence list"); +} +catch (error) { + logger.error("Error connecting to database:", error); + process.exit(1); +} + +try { + await client.login(processEnv.TOKEN); + logger.info("Bot logged in successfully"); + client.user?.setActivity(getActivity({})); +} +catch (error) { + logger.error("Failed to log in:", error); + process.exit(1); +} + +client.once("ready", async (client) => { + logger.info(`Bot is ready! Logged in as ${client.user?.tag}`); + + try { + //* Register guild-specific commands + await rest.put(Routes.applicationGuildCommands(client.application.id, processEnv.GUILD_ID), { + body: Array.from(commands.values()).map(({ data }) => data), + }); + + //* Clear global commands + await rest.put(Routes.applicationCommands(client.application.id), { body: [] }); + logger.info("Successfully registered commands"); + } + catch (error) { + logger.error("Failed to register commands:", error); + process.exit(1); + } +}); + +setInterval(async () => { + const newActivity = getActivity({ + previous: client.user?.presence.activities[0]?.name, + }); + client.user?.setActivity(newActivity); + logger.debug(`Updated bot activity to: ${newActivity.name}`); +}, 60000); + +setInterval(() => { + updatePresenceList(); +}, 1000 * 60 * 5); diff --git a/apps/discord-bot/src/util/createStandardEmbed.ts b/apps/discord-bot/src/util/createStandardEmbed.ts new file mode 100644 index 0000000..a389ebc --- /dev/null +++ b/apps/discord-bot/src/util/createStandardEmbed.ts @@ -0,0 +1,25 @@ +import { type APIEmbedField, type ColorResolvable, EmbedBuilder } from "discord.js"; + +interface StandardEmbedOptions { + title: string; + description: string; + fields?: APIEmbedField[]; + footer?: string; + color?: number | string; +} + +export function createStandardEmbed({ + title, + description, + fields = [], + footer = "PreMiD", + color = "Blurple", +}: StandardEmbedOptions): EmbedBuilder { + return new EmbedBuilder() + .setColor(color as ColorResolvable) + .setTitle(title) + .setDescription(description) + .addFields(fields) + .setFooter({ text: footer }) + .setTimestamp(); +} diff --git a/apps/discord-bot/src/util/getActivity.ts b/apps/discord-bot/src/util/getActivity.ts new file mode 100644 index 0000000..2a04bc3 --- /dev/null +++ b/apps/discord-bot/src/util/getActivity.ts @@ -0,0 +1,21 @@ +import type { ActivitiesOptions } from "discord.js"; +import { ActivityType } from "discord.js"; +import { presenceList } from "./presenceList.js"; + +export function getActivity({ previous }: { + previous?: string; +}): ActivitiesOptions { + const statuses = presenceList.filter(status => status.service !== previous); + const selectedStatus = statuses[Math.floor(Math.random() * statuses.length)]!; + + return { + type: selectedStatus.category === "music" + ? ActivityType.Listening + : selectedStatus.category === "anime" + ? ActivityType.Watching + : selectedStatus.category === "videos" + ? ActivityType.Watching + : ActivityType.Playing, + name: selectedStatus.service, + }; +} diff --git a/apps/discord-bot/src/util/loadCommands.ts b/apps/discord-bot/src/util/loadCommands.ts new file mode 100644 index 0000000..c1b1a03 --- /dev/null +++ b/apps/discord-bot/src/util/loadCommands.ts @@ -0,0 +1,39 @@ +import { resolve } from "node:path"; +import type { APIApplicationCommandOptionChoice, EmbedBuilder, SharedSlashCommandOptions } from "discord.js"; +import { glob } from "glob"; + +export interface CommandHelp extends APIApplicationCommandOptionChoice { + embed: EmbedBuilder; + command?: string; + commandDescription?: string; +} + +export interface Command { + data: SharedSlashCommandOptions; + execute: (interaction: any) => Promise; + autocomplete?: (interaction: any) => Promise; + help?: CommandHelp; +} + +export const commands = new Map(); + +export default async function loadCommands() { + for (const file of await glob("*.js", { cwd: resolve(import.meta.dirname, "../commands") })) { + const imported = await import(`../commands/${file}`); + + const name = typeof imported.default.data === "function" ? imported.default.data().name : imported.default.data.name; + commands.set(name.toLowerCase(), imported.default); + + if (imported.default.init) { + try { + await imported.default.init(); + } + catch (error) { + //* Failed to initialize command + console.error(`Failed to initialize command ${name}:`, error); + } + } + } + + return commands; +} diff --git a/apps/discord-bot/src/util/loadEvents.ts b/apps/discord-bot/src/util/loadEvents.ts new file mode 100644 index 0000000..3b5c21a --- /dev/null +++ b/apps/discord-bot/src/util/loadEvents.ts @@ -0,0 +1,8 @@ +import { resolve } from "node:path"; +import { glob } from "glob"; + +export default async function loadEvents() { + for (const file of await glob("*.js", { cwd: resolve(import.meta.dirname, "../events") })) { + import(`../events/${file}`); + } +} diff --git a/apps/discord-bot/src/util/logger.ts b/apps/discord-bot/src/util/logger.ts new file mode 100644 index 0000000..77fda4d --- /dev/null +++ b/apps/discord-bot/src/util/logger.ts @@ -0,0 +1,41 @@ +import process from "node:process"; +import { createLogger, format, transports } from "winston"; + +export const logger = createLogger({ + level: process.env.NODE_ENV === "production" ? (process.env.LOG_LEVEL || "info") : (process.env.LOG_LEVEL || "debug"), + format: format.combine( + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.errors({ stack: true }), + format.splat(), + format.json(), + ), + transports: [ + new transports.Console({ + format: format.combine( + format.colorize(), + format.printf(({ timestamp, level, message, ...metadata }) => { + let msg = `${timestamp} [${level}] : ${message}`; + if (Object.keys(metadata).length > 0) { + msg += `\n${JSON.stringify(metadata, null, 2)}`; + } + return msg; + }), + ), + }), + new transports.File({ + filename: "error.log", + level: "error", + format: format.combine( + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.json(), + ), + }), + new transports.File({ + filename: "combined.log", + format: format.combine( + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.json(), + ), + }), + ], +}); diff --git a/apps/discord-bot/src/util/presenceList.ts b/apps/discord-bot/src/util/presenceList.ts new file mode 100644 index 0000000..92d0ce9 --- /dev/null +++ b/apps/discord-bot/src/util/presenceList.ts @@ -0,0 +1,14 @@ +import { Presence } from "@premid/db"; +import type { PresenceMetadataCategory } from "@premid/db/Presence.js"; +import { logger } from "./logger.js"; + +// eslint-disable-next-line import/no-mutable-exports +export let presenceList: { service: string; category: PresenceMetadataCategory }[] = []; + +export async function updatePresenceList() { + presenceList = (await Presence.find({}, { metadata: { category: true, service: true } })).map(presence => ({ + service: presence.metadata.service, + category: presence.metadata.category, + })); + logger.debug(`Updated presence list with ${presenceList.length} presences`); +} diff --git a/apps/discord-bot/tsconfig.app.json b/apps/discord-bot/tsconfig.app.json new file mode 100644 index 0000000..358d836 --- /dev/null +++ b/apps/discord-bot/tsconfig.app.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/apps/discord-bot/tsconfig.json b/apps/discord-bot/tsconfig.json new file mode 100644 index 0000000..c117df9 --- /dev/null +++ b/apps/discord-bot/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": { + "types": ["@types/node"], + "noEmit": true + }, + "include": ["src"] +} diff --git a/packages/db/package.json b/packages/db/package.json index 074b4a7..8ae1ec0 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -13,6 +13,6 @@ "lib" ], "dependencies": { - "mongoose": "^8.2.0" + "mongoose": "^8.6.3" } } diff --git a/packages/db/src/AlphaUsers.ts b/packages/db/src/AlphaUsers.ts new file mode 100644 index 0000000..e4eb4be --- /dev/null +++ b/packages/db/src/AlphaUsers.ts @@ -0,0 +1,11 @@ +import mongoose, { Schema } from "mongoose"; + +export interface AlphaUsersSchema { + userId: string; +} + +const alphaUsersSchema = new Schema({ + userId: { required: true, type: String }, +}); + +export default mongoose.model("AlphaUsers", alphaUsersSchema, "alphaUsers"); diff --git a/packages/db/src/BetaUsers.ts b/packages/db/src/BetaUsers.ts new file mode 100644 index 0000000..9718a90 --- /dev/null +++ b/packages/db/src/BetaUsers.ts @@ -0,0 +1,11 @@ +import mongoose, { Schema } from "mongoose"; + +export interface BetaUsersSchema { + userId: string; +} + +const betaUsersSchema = new Schema({ + userId: { required: true, type: String }, +}); + +export default mongoose.model("BetaUsers", betaUsersSchema, "betaUsers"); diff --git a/packages/db/src/Credits.ts b/packages/db/src/Credits.ts new file mode 100644 index 0000000..cf074a8 --- /dev/null +++ b/packages/db/src/Credits.ts @@ -0,0 +1,35 @@ +import mongoose, { Schema } from "mongoose"; + +export interface CreditsSchema { + userId: string; + avatar: string; + name: string; + premium_since: number; + role: string; + roleColor: string; + roleId: string; + roleIds: string[]; + rolePosition: number; + roles: string[]; + status: string; + tag: string; + flags: string[]; +} + +const creditsSchema = new Schema({ + userId: { required: true, type: String }, + avatar: { required: true, type: String }, + name: { required: true, type: String }, + premium_since: { required: true, type: Number }, + role: { required: true, type: String }, + roleColor: { required: true, type: String }, + roleId: { required: true, type: String }, + roleIds: { required: true, type: [String] }, + rolePosition: { required: true, type: Number }, + roles: { required: true, type: [String] }, + status: { required: true, type: String }, + tag: { required: true, type: String }, + flags: { required: true, type: [String] }, +}); + +export default mongoose.model("Credits", creditsSchema, "credits"); diff --git a/packages/db/src/DiscordUsers.ts b/packages/db/src/DiscordUsers.ts new file mode 100644 index 0000000..52f325c --- /dev/null +++ b/packages/db/src/DiscordUsers.ts @@ -0,0 +1,15 @@ +import mongoose, { Schema } from "mongoose"; + +export interface DiscordUsersSchema { + userId: string; + username: string; + avatar: string; + discriminator: string; + created: number; +} + +const discordUsersSchema = new Schema({ + userId: { required: true, type: String }, +}); + +export default mongoose.model("DiscordUsers", discordUsersSchema, "discordUsers"); diff --git a/packages/db/src/Presence.ts b/packages/db/src/Presence.ts index f6f61f8..0d763da 100644 --- a/packages/db/src/Presence.ts +++ b/packages/db/src/Presence.ts @@ -93,4 +93,4 @@ const presenceSchema = new Schema({ url: { required: true, type: String }, }); -export default mongoose.model("Presence", presenceSchema); +export default mongoose.model("Presence", presenceSchema, "presences"); diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index cdad3e3..45cd68e 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1 +1,5 @@ +export { default as AlphaUsers } from "./AlphaUsers.js"; +export { default as BetaUsers } from "./BetaUsers.js"; +export { default as Credits } from "./Credits.js"; +export { default as DiscordUsers } from "./DiscordUsers.js"; export { default as Presence } from "./Presence.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57b44c1..4827abc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,8 +157,8 @@ importers: specifier: ^5.3.2 version: 5.4.1 mongoose: - specifier: ^8.5.1 - version: 8.6.1 + specifier: ^8.6.3 + version: 8.6.3 devDependencies: '@graphql-codegen/cli': specifier: 5.0.2 @@ -179,6 +179,30 @@ importers: specifier: ^8.5.12 version: 8.5.12 + apps/discord-bot: + dependencies: + '@premid/db': + specifier: workspace:* + version: link:../../packages/db + '@sentry/node': + specifier: ^8.17.0 + version: 8.30.0 + defu: + specifier: ^6.1.4 + version: 6.1.4 + discord.js: + specifier: ^14.16.2 + version: 14.16.2 + glob: + specifier: ^11.0.0 + version: 11.0.0 + mongoose: + specifier: ^8.2.0 + version: 8.6.3 + winston: + specifier: ^3.14.2 + version: 3.14.2 + apps/docs: devDependencies: vitepress: @@ -357,8 +381,8 @@ importers: packages/db: dependencies: mongoose: - specifier: ^8.2.0 - version: 8.6.1 + specifier: ^8.6.3 + version: 8.6.3 packages: @@ -934,6 +958,10 @@ packages: resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} engines: {node: '>=16.13'} + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + '@commitlint/cli@19.5.0': resolution: {integrity: sha512-gaGqSliGwB86MDmAAKAtV9SV1SHdmN8pnGq4EJU4+hLisQ7IFfx4jvU4s+pk6tl0+9bv6yT+CaZkufOinkSJIQ==} engines: {node: '>=v18'} @@ -1003,6 +1031,9 @@ packages: resolution: {integrity: sha512-DSHae2obMSMkAtTBSOulg5X7/z+rGLxcXQIkg3OmWvY6wifojge5uVMydfhUvs7yQj+V7jNmRZ2Xzl8GJyqRgg==} engines: {node: '>=v18'} + '@dabh/diagnostics@2.0.3': + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + '@discord-user-card/core@0.0.9': resolution: {integrity: sha512-Vr7jbKtCCnz4Te1UgyC3gxzI/MUy2hN6TmCNfhrTqMuXPOJUxcV9JKDcgp5iYFODURH/tKOrmhDVaYB64G+snQ==} @@ -1011,10 +1042,22 @@ packages: peerDependencies: vue: ^3.0.0 + '@discordjs/builders@1.9.0': + resolution: {integrity: sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg==} + engines: {node: '>=18'} + + '@discordjs/collection@1.5.3': + resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} + engines: {node: '>=16.11.0'} + '@discordjs/collection@2.1.1': resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} engines: {node: '>=18'} + '@discordjs/formatters@0.5.0': + resolution: {integrity: sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==} + engines: {node: '>=18'} + '@discordjs/rest@2.4.0': resolution: {integrity: sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw==} engines: {node: '>=18'} @@ -1023,6 +1066,10 @@ packages: resolution: {integrity: sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==} engines: {node: '>=18'} + '@discordjs/ws@1.1.1': + resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==} + engines: {node: '>=16.11.0'} + '@docsearch/css@3.6.1': resolution: {integrity: sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==} @@ -3129,6 +3176,7 @@ packages: '@rollup/rollup-linux-arm64-gnu@4.21.2': resolution: {integrity: sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==} + cpu: [arm64] os: [linux] '@rollup/rollup-linux-arm64-musl@4.18.1': @@ -3228,6 +3276,10 @@ packages: resolution: {integrity: sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + '@sapphire/shapeshift@4.0.0': + resolution: {integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==} + engines: {node: '>=v16'} + '@sapphire/snowflake@3.5.3': resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -3418,6 +3470,9 @@ packages: '@types/tinycolor2@1.4.6': resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -4568,6 +4623,9 @@ packages: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + color@4.2.3: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} @@ -4578,6 +4636,9 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -4872,15 +4933,6 @@ packages: supports-color: optional: true - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -5014,12 +5066,19 @@ packages: discord-api-types@0.37.100: resolution: {integrity: sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA==} + discord-api-types@0.37.83: + resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==} + discord-api-types@0.37.97: resolution: {integrity: sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA==} discord-user-card@0.0.9: resolution: {integrity: sha512-oNW2WLScbDhmNSxE3f5wgJYKAbjGa39gQQ57LqLG/GML7aeWYgrhvadZwpdLP0AZKdZoRh9ZdCA+6BBufJ3mYQ==} + discord.js@14.16.2: + resolution: {integrity: sha512-VGNi9WE2dZIxYM8/r/iatQQ+3LT8STW4hhczJOwm+DBeHq66vsKDCk8trChNCB01sMO9crslYuEMeZl2d7r3xw==} + engines: {node: '>=18'} + doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -5173,6 +5232,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -5610,6 +5672,9 @@ packages: picomatch: optional: true + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -5679,6 +5744,9 @@ packages: '@nuxt/kit': optional: true + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + focus-trap@7.5.4: resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} @@ -5826,6 +5894,11 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@11.0.0: + resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} + engines: {node: 20 || >=22} + hasBin: true + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -6359,6 +6432,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.0.2: + resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==} + engines: {node: 20 || >=22} + jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} @@ -6467,6 +6544,9 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + ky@1.7.2: resolution: {integrity: sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==} engines: {node: '>=18'} @@ -6613,6 +6693,10 @@ packages: resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} engines: {node: '>=10'} + logform@2.6.1: + resolution: {integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==} + engines: {node: '>= 12.0.0'} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -6633,6 +6717,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.0.1: + resolution: {integrity: sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -6787,6 +6875,10 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + minimatch@10.0.1: + resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -6894,8 +6986,8 @@ packages: socks: optional: true - mongoose@8.6.1: - resolution: {integrity: sha512-dppGcYqvsdg+VcnqXR5b467V4a+iNhmvkfYNpEPi6AjaUxnz6ioEDmrMLOi+sOWjvoHapuwPOigV4f2l7HC6ag==} + mongoose@8.6.3: + resolution: {integrity: sha512-++yRmm7hjMbqVA/8WeiygTnEfrFbiy+OBjQi49GFJIvCQuSYE56myyQWo4j5hbpcHjhHQU8NukMNGTwAWFWjIw==} engines: {node: '>=16.20.1'} mpath@0.9.0: @@ -6917,9 +7009,6 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -7152,6 +7241,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -7340,6 +7432,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -8178,6 +8274,9 @@ packages: stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -8393,6 +8492,9 @@ packages: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -8500,6 +8602,10 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -8512,6 +8618,9 @@ packages: ts-log@2.2.5: resolution: {integrity: sha512-PGcnJoTBnVGy6yYNFxWVNkdcAuAMstvutN9MgDJIV6L0oG8fB+ZNNy1T+wJzah8RPGor1mZuPQkVfXNDpy9eHA==} + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + tsconfck@3.1.3: resolution: {integrity: sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==} engines: {node: ^18 || >=20} @@ -9182,6 +9291,14 @@ packages: wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + winston-transport@4.7.1: + resolution: {integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==} + engines: {node: '>= 12.0.0'} + + winston@3.14.2: + resolution: {integrity: sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==} + engines: {node: '>= 12.0.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -10077,6 +10194,8 @@ snapshots: dependencies: mime: 3.0.0 + '@colors/colors@1.6.0': {} + '@commitlint/cli@19.5.0(@types/node@20.16.5)(typescript@5.6.2)': dependencies: '@commitlint/format': 19.5.0 @@ -10187,6 +10306,12 @@ snapshots: '@types/conventional-commits-parser': 5.0.0 chalk: 5.3.0 + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + '@discord-user-card/core@0.0.9': {} '@discord-user-card/vue@0.0.9(vue@3.5.4(typescript@5.5.4))': @@ -10194,8 +10319,24 @@ snapshots: discord-user-card: 0.0.9 vue: 3.5.4(typescript@5.5.4) + '@discordjs/builders@1.9.0': + dependencies: + '@discordjs/formatters': 0.5.0 + '@discordjs/util': 1.1.1 + '@sapphire/shapeshift': 4.0.0 + discord-api-types: 0.37.97 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.4 + tslib: 2.7.0 + + '@discordjs/collection@1.5.3': {} + '@discordjs/collection@2.1.1': {} + '@discordjs/formatters@0.5.0': + dependencies: + discord-api-types: 0.37.97 + '@discordjs/rest@2.4.0': dependencies: '@discordjs/collection': 2.1.1 @@ -10210,6 +10351,21 @@ snapshots: '@discordjs/util@1.1.1': {} + '@discordjs/ws@1.1.1': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/rest': 2.4.0 + '@discordjs/util': 1.1.1 + '@sapphire/async-queue': 1.5.3 + '@types/ws': 8.5.12 + '@vladfrangu/async_event_emitter': 2.4.6 + discord-api-types: 0.37.83 + tslib: 2.7.0 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@docsearch/css@3.6.1': {} '@docsearch/js@3.6.1(@algolia/client-search@5.4.0)(search-insights@2.17.2)': @@ -11110,7 +11266,7 @@ snapshots: '@graphql-tools/utils': 10.3.2(graphql@16.9.0) dataloader: 2.2.2 graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 value-or-promise: 1.0.12 '@graphql-tools/code-file-loader@8.1.2(graphql@16.9.0)': @@ -11132,13 +11288,13 @@ snapshots: '@graphql-tools/utils': 10.3.2(graphql@16.9.0) dataloader: 2.2.2 graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/documents@1.0.1(graphql@16.9.0)': dependencies: graphql: 16.9.0 lodash.sortby: 4.7.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/executor-graphql-ws@1.2.0(graphql@16.9.0)': dependencies: @@ -11147,7 +11303,7 @@ snapshots: graphql: 16.9.0 graphql-ws: 5.16.0(graphql@16.9.0) isomorphic-ws: 5.0.0(ws@8.18.0) - tslib: 2.6.3 + tslib: 2.7.0 ws: 8.18.0 transitivePeerDependencies: - bufferutil @@ -11161,7 +11317,7 @@ snapshots: extract-files: 11.0.0 graphql: 16.9.0 meros: 1.3.0(@types/node@22.6.1) - tslib: 2.6.3 + tslib: 2.7.0 value-or-promise: 1.0.12 transitivePeerDependencies: - '@types/node' @@ -11172,7 +11328,7 @@ snapshots: '@types/ws': 8.5.12 graphql: 16.9.0 isomorphic-ws: 5.0.0(ws@8.18.0) - tslib: 2.6.3 + tslib: 2.7.0 ws: 8.18.0 transitivePeerDependencies: - bufferutil @@ -11232,7 +11388,7 @@ snapshots: '@babel/types': 7.25.2 '@graphql-tools/utils': 10.3.2(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - supports-color @@ -11241,7 +11397,7 @@ snapshots: '@graphql-tools/utils': 10.3.2(graphql@16.9.0) graphql: 16.9.0 resolve-from: 5.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/json-file-loader@8.0.1(graphql@16.9.0)': dependencies: @@ -11263,17 +11419,17 @@ snapshots: dependencies: '@graphql-tools/utils': 10.3.2(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/optimize@1.4.0(graphql@16.9.0)': dependencies: graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/optimize@2.0.0(graphql@16.9.0)': dependencies: graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/prisma-loader@8.0.4(@types/node@22.6.1)(graphql@16.9.0)': dependencies: @@ -11306,7 +11462,7 @@ snapshots: '@ardatan/relay-compiler': 12.0.0(graphql@16.9.0) '@graphql-tools/utils': 9.2.1(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - encoding - supports-color @@ -11316,7 +11472,7 @@ snapshots: '@ardatan/relay-compiler': 12.0.0(graphql@16.9.0) '@graphql-tools/utils': 10.3.2(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - encoding - supports-color @@ -11362,13 +11518,13 @@ snapshots: '@graphql-tools/utils@8.13.1(graphql@16.9.0)': dependencies: graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/utils@9.2.1(graphql@16.9.0)': dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/wrap@10.0.5(graphql@16.9.0)': dependencies: @@ -11376,7 +11532,7 @@ snapshots: '@graphql-tools/schema': 10.0.4(graphql@16.9.0) '@graphql-tools/utils': 10.3.2(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 value-or-promise: 1.0.12 '@graphql-typed-document-node/core@3.2.0(graphql@16.9.0)': @@ -11397,7 +11553,7 @@ snapshots: '@graphql-yoga/typed-event-target@3.0.0': dependencies: '@repeaterjs/repeater': 3.0.6 - tslib: 2.6.3 + tslib: 2.7.0 '@humanwhocodes/module-importer@1.0.1': {} @@ -12893,18 +13049,18 @@ snapshots: dependencies: asn1js: 3.0.5 pvtsutils: 1.3.5 - tslib: 2.6.3 + tslib: 2.7.0 '@peculiar/json-schema@1.1.12': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@peculiar/webcrypto@1.5.0': dependencies: '@peculiar/asn1-schema': 2.3.8 '@peculiar/json-schema': 1.1.12 pvtsutils: 1.3.5 - tslib: 2.6.3 + tslib: 2.7.0 webcrypto-core: 1.8.0 '@pinia/nuxt@0.5.4(magicast@0.3.5)(rollup@4.21.2)(typescript@5.5.4)(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)': @@ -13224,6 +13380,11 @@ snapshots: '@sapphire/async-queue@1.5.3': {} + '@sapphire/shapeshift@4.0.0': + dependencies: + fast-deep-equal: 3.1.3 + lodash: 4.17.21 + '@sapphire/snowflake@3.5.3': {} '@sec-ant/readable-stream@0.4.1': {} @@ -13471,6 +13632,8 @@ snapshots: '@types/tinycolor2@1.4.6': {} + '@types/triple-beam@1.3.5': {} + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -14006,7 +14169,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.6 + debug: 4.3.7 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -14063,7 +14226,7 @@ snapshots: pathe: 1.1.2 sirv: 2.0.4 tinyrainbow: 1.2.0 - vitest: 2.0.5(@types/node@22.6.1)(@vitest/ui@2.0.5)(happy-dom@15.0.0)(sass@1.78.0)(terser@5.33.0) + vitest: 2.0.5(@types/node@20.16.5)(@vitest/ui@2.0.5)(happy-dom@15.7.4)(sass@1.78.0)(terser@5.33.0) '@vitest/utils@2.0.5': dependencies: @@ -14459,14 +14622,14 @@ snapshots: busboy: 1.6.0 fast-querystring: 1.1.2 fast-url-parser: 1.1.3 - tslib: 2.6.3 + tslib: 2.7.0 '@whatwg-node/node-fetch@0.5.19': dependencies: '@kamilkisiela/fast-url-parser': 1.1.4 busboy: 1.6.0 fast-querystring: 1.1.2 - tslib: 2.6.3 + tslib: 2.7.0 '@whatwg-node/server@0.9.45': dependencies: @@ -14634,7 +14797,7 @@ snapshots: dependencies: pvtsutils: 1.3.5 pvutils: 1.1.3 - tslib: 2.6.3 + tslib: 2.7.0 assertion-error@2.0.1: {} @@ -14921,7 +15084,7 @@ snapshots: camel-case@4.1.2: dependencies: pascal-case: 3.1.2 - tslib: 2.6.3 + tslib: 2.7.0 camelcase@5.3.1: {} @@ -14941,7 +15104,7 @@ snapshots: capital-case@1.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 upper-case-first: 2.0.2 ccount@2.0.1: {} @@ -15006,7 +15169,7 @@ snapshots: path-case: 3.0.4 sentence-case: 3.0.4 snake-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 changelogen@0.5.5(magicast@0.3.5): dependencies: @@ -15162,10 +15325,14 @@ snapshots: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - optional: true color-support@1.1.3: {} + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + color@4.2.3: dependencies: color-convert: 2.0.1 @@ -15176,6 +15343,11 @@ snapshots: colorette@2.0.20: {} + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -15224,7 +15396,7 @@ snapshots: constant-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 upper-case: 2.0.2 conventional-changelog-angular@7.0.0: @@ -15328,7 +15500,7 @@ snapshots: cross-inspect@1.0.0: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 cross-spawn@7.0.3: dependencies: @@ -15450,10 +15622,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.6: - dependencies: - ms: 2.1.2 - debug@4.3.7: dependencies: ms: 2.1.3 @@ -15544,12 +15712,32 @@ snapshots: discord-api-types@0.37.100: {} + discord-api-types@0.37.83: {} + discord-api-types@0.37.97: {} discord-user-card@0.0.9: dependencies: '@discord-user-card/core': 0.0.9 + discord.js@14.16.2: + dependencies: + '@discordjs/builders': 1.9.0 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.5.0 + '@discordjs/rest': 2.4.0 + '@discordjs/util': 1.1.1 + '@discordjs/ws': 1.1.1 + '@sapphire/snowflake': 3.5.3 + discord-api-types: 0.37.97 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + tslib: 2.7.0 + undici: 6.19.8 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + doctrine@3.0.0: dependencies: esutils: 2.0.3 @@ -15575,7 +15763,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 dot-prop@5.3.0: dependencies: @@ -15625,6 +15813,8 @@ snapshots: emoji-regex@9.2.2: {} + enabled@2.0.0: {} + encodeurl@1.0.2: {} encoding-sniffer@0.2.0: @@ -16419,6 +16609,8 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fecha@4.2.3: {} + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -16492,6 +16684,8 @@ snapshots: optionalDependencies: '@nuxt/kit': 3.13.1(magicast@0.3.5)(rollup@4.21.2)(webpack-sources@3.2.3) + fn.name@1.1.0: {} + focus-trap@7.5.4: dependencies: tabbable: 6.2.0 @@ -16656,6 +16850,15 @@ snapshots: package-json-from-dist: 1.0.0 path-scurry: 1.11.1 + glob@11.0.0: + dependencies: + foreground-child: 3.2.1 + jackspeak: 4.0.2 + minimatch: 10.0.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 2.0.0 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -16799,7 +17002,7 @@ snapshots: graphql-tag@2.12.6(graphql@16.9.0): dependencies: graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 graphql-ws@5.16.0(graphql@16.9.0): dependencies: @@ -16897,7 +17100,7 @@ snapshots: header-case@2.0.4: dependencies: capital-case: 1.0.4 - tslib: 2.6.3 + tslib: 2.7.0 helmet@7.1.0: {} @@ -17156,8 +17359,7 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.2: - optional: true + is-arrayish@0.3.2: {} is-binary-path@2.1.0: dependencies: @@ -17202,7 +17404,7 @@ snapshots: is-lower-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 is-module@1.0.0: {} @@ -17250,7 +17452,7 @@ snapshots: is-upper-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 is-what@4.1.16: {} @@ -17287,7 +17489,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.3.6 + debug: 4.3.7 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -17303,6 +17505,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.0.2: + dependencies: + '@isaacs/cliui': 8.0.2 + jest-worker@27.5.1: dependencies: '@types/node': 20.16.5 @@ -17385,6 +17591,8 @@ snapshots: kolorist@1.8.0: {} + kuler@2.0.0: {} + ky@1.7.2: {} launch-editor@2.9.1: @@ -17539,6 +17747,15 @@ snapshots: slice-ansi: 4.0.0 wrap-ansi: 6.2.0 + logform@2.6.1: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.4.3 + triple-beam: 1.4.1 + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -17549,16 +17766,18 @@ snapshots: lower-case-first@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 lower-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 lowercase-keys@3.0.0: {} lru-cache@10.4.3: {} + lru-cache@11.0.1: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -17713,6 +17932,10 @@ snapshots: min-indent@1.0.1: {} + minimatch@10.0.1: + dependencies: + brace-expansion: 2.0.1 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -17799,7 +18022,7 @@ snapshots: bson: 6.8.0 mongodb-connection-string-url: 3.0.1 - mongoose@8.6.1: + mongoose@8.6.3: dependencies: bson: 6.8.0 kareem: 2.6.3 @@ -17832,8 +18055,6 @@ snapshots: ms@2.0.0: {} - ms@2.1.2: {} - ms@2.1.3: {} muggle-string@0.4.1: {} @@ -17954,7 +18175,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.3 + tslib: 2.7.0 node-abi@3.67.0: dependencies: @@ -18491,6 +18712,10 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -18598,7 +18823,7 @@ snapshots: param-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 parent-module@1.0.1: dependencies: @@ -18671,14 +18896,14 @@ snapshots: pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 path-browserify@1.0.1: {} path-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 path-exists@4.0.0: {} @@ -18703,6 +18928,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-scurry@2.0.0: + dependencies: + lru-cache: 11.0.1 + minipass: 7.1.2 + path-type@4.0.0: {} path-type@5.0.0: {} @@ -19044,7 +19274,7 @@ snapshots: pvtsutils@1.3.5: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 pvutils@1.1.3: {} @@ -19295,7 +19525,7 @@ snapshots: rxjs@7.8.1: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 safe-buffer@5.1.2: {} @@ -19379,7 +19609,7 @@ snapshots: sentence-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 upper-case-first: 2.0.2 serialize-javascript@6.0.2: @@ -19481,7 +19711,6 @@ snapshots: simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 - optional: true sirv@2.0.4: dependencies: @@ -19521,7 +19750,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 sonic-boom@4.0.1: dependencies: @@ -19571,12 +19800,14 @@ snapshots: sponge-case@1.0.1: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 sprintf-js@1.1.3: {} stable-hash@0.0.4: {} + stack-trace@0.0.10: {} + stackback@0.0.2: {} standard-as-callback@2.1.0: {} @@ -19712,7 +19943,7 @@ snapshots: swap-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 synckit@0.6.2: dependencies: @@ -19811,6 +20042,8 @@ snapshots: text-extensions@2.4.0: {} + text-hex@1.0.0: {} + text-table@0.2.0: {} thenify-all@1.6.0: @@ -19859,7 +20092,7 @@ snapshots: title-case@3.0.3: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 titleize@3.0.0: {} @@ -19898,6 +20131,8 @@ snapshots: trim-lines@3.0.1: {} + triple-beam@1.4.1: {} + ts-api-utils@1.3.0(typescript@5.5.4): dependencies: typescript: 5.5.4 @@ -19910,6 +20145,8 @@ snapshots: ts-log@2.2.5: {} + ts-mixer@6.0.4: {} + tsconfck@3.1.3(typescript@5.5.4): optionalDependencies: typescript: 5.5.4 @@ -20275,11 +20512,11 @@ snapshots: upper-case-first@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 upper-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 uqr@0.1.2: {} @@ -20325,7 +20562,7 @@ snapshots: vite-node@2.0.5(@types/node@20.16.5)(sass@1.78.0)(terser@5.33.0): dependencies: cac: 6.7.14 - debug: 4.3.6 + debug: 4.3.7 pathe: 1.1.2 tinyrainbow: 1.2.0 vite: 5.3.4(@types/node@20.16.5)(sass@1.78.0)(terser@5.33.0) @@ -20342,7 +20579,7 @@ snapshots: vite-node@2.0.5(@types/node@22.6.1)(sass@1.78.0)(terser@5.33.0): dependencies: cac: 6.7.14 - debug: 4.3.6 + debug: 4.3.7 pathe: 1.1.2 tinyrainbow: 1.2.0 vite: 5.3.4(@types/node@22.6.1)(sass@1.78.0)(terser@5.33.0) @@ -20579,7 +20816,7 @@ snapshots: '@vitest/spy': 2.0.5 '@vitest/utils': 2.0.5 chai: 5.1.1 - debug: 4.3.6 + debug: 4.3.7 execa: 8.0.1 magic-string: 0.30.10 pathe: 1.1.2 @@ -20613,7 +20850,7 @@ snapshots: '@vitest/spy': 2.0.5 '@vitest/utils': 2.0.5 chai: 5.1.1 - debug: 4.3.6 + debug: 4.3.7 execa: 8.0.1 magic-string: 0.30.10 pathe: 1.1.2 @@ -20760,7 +20997,7 @@ snapshots: '@peculiar/json-schema': 1.1.12 asn1js: 3.0.5 pvtsutils: 1.3.5 - tslib: 2.6.3 + tslib: 2.7.0 webidl-conversions@3.0.1: {} @@ -20837,6 +21074,26 @@ snapshots: dependencies: string-width: 4.2.3 + winston-transport@4.7.1: + dependencies: + logform: 2.6.1 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.14.2: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.6.1 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.4.3 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.7.1 + word-wrap@1.2.5: {} wrap-ansi@6.2.0: diff --git a/tsconfig.app.json b/tsconfig.app.json index 9174b20..23eb5b4 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -14,6 +14,9 @@ }, { "path": "./apps/api-worker/tsconfig.app.json" + }, + { + "path": "./apps/discord-bot/tsconfig.app.json" } ], "files": []