diff --git a/package.json b/package.json index 760cfe3..5729558 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,10 @@ "main": "index.js", "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "lint": "eslint ./src --max-warnings 0", + "build": "prisma generate && tsc", + "start": "op run --env-file=./prod.env -- node ./prod/index.js", + "test": "echo 'No tests yet' && exit 0" }, "keywords": [], "author": "", diff --git a/src/commands/add.ts b/src/commands/add.ts index 0b17a3f..a257ce4 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -8,6 +8,7 @@ import { getEntitlement } from "../modules/getEntitlement.js"; import { getSkuLimit } from "../modules/getSkuLimit.js"; import { sendUnentitledResponse } from "../modules/sendUnintitledResponse.js"; import { errorHandler } from "../utils/errorHandler.js"; +import { logger } from "../utils/logger.js"; import type { Command } from "../interfaces/command.js"; /** @@ -31,9 +32,21 @@ export const add: Command = async(lynira, interaction) => { return; } const url = interaction.options.getString("url", true); - const slugExists = await lynira.db.links.findFirst({ where: { - slug, - } }); + const isValidUrl = /^https?:\/\/(?:[\da-z-]+\.)+[a-z]{2,}(?:\/\S*)?$/i.test( + url, + ); + if (!isValidUrl) { + await logger.log("debug", `Provided URL did not match expected pattern: ${url}`); + await interaction.editReply({ + content: "The provided URL is not valid. Please provide a valid URL.", + }); + return; + } + const slugExists = await lynira.db.links.findFirst({ + where: { + slug, + }, + }); if (slugExists) { await interaction.editReply({ content: "This slug is already in use. Please choose a different one.", @@ -63,6 +76,10 @@ export const add: Command = async(lynira, interaction) => { await interaction.editReply({ content: `Short URL created. Visit to access it. Please note that for the safety of our users, you will not be able to edit this short link.`, }); + await logger.log( + "info", + `User ${interaction.user.id} created a short URL with slug "${slug}" pointing to "${url}".`, + ); } catch (error) { await errorHandler(error, "add command"); await interaction.editReply({ diff --git a/src/commands/remove.ts b/src/commands/remove.ts index fa2458a..65a577e 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -6,6 +6,7 @@ import { getEntitlement } from "../modules/getEntitlement.js"; import { sendUnentitledResponse } from "../modules/sendUnintitledResponse.js"; import { errorHandler } from "../utils/errorHandler.js"; +import { logger } from "../utils/logger.js"; import type { Command } from "../interfaces/command.js"; /** @@ -40,6 +41,7 @@ export const remove: Command = async(lynira, interaction) => { await interaction.editReply({ content: `Short URL has been removed. Please note that for the safety of our users, this slug will not be available for reuse.`, }); + await logger.log("info", `User ${interaction.user.id} removed a short URL with slug "${slug}".`); } catch (error) { await errorHandler(error, "remove command"); await interaction.editReply({ diff --git a/src/index.ts b/src/index.ts index 1d34e92..fbd3e1c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,8 +5,8 @@ */ import { PrismaClient } from "@prisma/client"; -import { Client, Events, MessageFlags } from "discord.js"; -import { handlers } from "./config/handlers.js"; +import { Client, Events } from "discord.js"; +import { processCommand } from "./modules/processCommand.js"; import { instantiateServer } from "./server/serve.js"; import { logger } from "./utils/logger.js"; import type { Lynira } from "./interfaces/lynira.js"; @@ -26,16 +26,11 @@ lynira.discord.on("error", (error) => { lynira.discord.on(Events.InteractionCreate, (interaction) => { if (interaction.isChatInputCommand()) { - void interaction.deferReply({ - flags: [ MessageFlags.Ephemeral ], - }); - const { commandName } = interaction; - // eslint-disable-next-line no-underscore-dangle -- Accessing private property for command handler. - void (handlers[commandName] ?? handlers._default)(lynira, interaction); + void processCommand(lynira, interaction); } }); await lynira.db.$connect(); -await lynira.discord.login(process.env.DISCORD_TOKEN); +await lynira.discord.login(process.env.BOT_TOKEN); instantiateServer(lynira); diff --git a/src/interfaces/skus.ts b/src/interfaces/skus.ts index baea2b8..fed61a4 100644 --- a/src/interfaces/skus.ts +++ b/src/interfaces/skus.ts @@ -9,5 +9,6 @@ export enum SKU { PERSONAL = "1404602103434973186", PROFESSIONAL = "1404602584404328573", BUSINESS = "1404602909370613931", - ENTERPRISE = "1404603245963513898", + ENTERPRISE = "1404653451224416317", + ORGANISATION = "1404603245963513898", } diff --git a/src/modules/getEntitlement.ts b/src/modules/getEntitlement.ts index 39d80c3..2bd5fcf 100644 --- a/src/modules/getEntitlement.ts +++ b/src/modules/getEntitlement.ts @@ -4,8 +4,9 @@ * @author Naomi Carrigan */ +import { type Entitlement, EntitlementType } from "discord.js"; +import { SKU } from "../interfaces/skus.js"; import type { Lynira } from "../interfaces/lynira.js"; -import type { Entitlement } from "discord.js"; /** * Fetches an entitlement for a user. Only looks at the first ACTIVE entitlement. @@ -17,6 +18,27 @@ export const getEntitlement = async( lynira: Lynira, userId: string, ): Promise => { + if (userId === "465650873650118659") { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Mock entitlement for Naomi. + return { + applicationId: lynira.discord.application?.id ?? "", + consumed: false, + deleted: false, + endsAt: null, + endsTimestamp: null, + guild: null, + guildId: null, + id: "enterprise-entitlement-id", + isActive: () => { + return true; + }, + skuId: SKU.ENTERPRISE, + startsAt: null, + startsTimestamp: null, + type: EntitlementType.ApplicationSubscription, + userId: "465650873650118659", + } as Entitlement; + } const entitlements = await lynira.discord.application?.entitlements.fetch({ excludeDeleted: true, excludeEnded: true, diff --git a/src/modules/getSkuLimit.ts b/src/modules/getSkuLimit.ts index 7623137..8f3e4cf 100644 --- a/src/modules/getSkuLimit.ts +++ b/src/modules/getSkuLimit.ts @@ -17,11 +17,14 @@ export const getSkuLimit = (sku: SKU | string): number => { return 10; } if (sku === SKU.PROFESSIONAL) { - return 50; + return 25; } if (sku === SKU.BUSINESS) { return 100; } + if (sku === SKU.ORGANISATION) { + return 250; + } if (sku === SKU.ENTERPRISE) { return 1000; } diff --git a/src/modules/processCommand.ts b/src/modules/processCommand.ts new file mode 100644 index 0000000..ee1049b --- /dev/null +++ b/src/modules/processCommand.ts @@ -0,0 +1,23 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { MessageFlags } from "discord.js"; +import { handlers } from "../config/handlers.js"; +import type { Command } from "../interfaces/command.js"; + +/** + * Process a command interaction. + * @param lynira - The Lynira instance. + * @param interaction - The interaction to process. + */ +export const processCommand: Command = async(lynira, interaction) => { + await interaction.deferReply({ + flags: [ MessageFlags.Ephemeral ], + }); + const { commandName } = interaction; + // eslint-disable-next-line no-underscore-dangle -- Accessing private property for command handler. + await (handlers[commandName] ?? handlers._default)(lynira, interaction); +};