From d1dcffb6decb830c10f5a11283bcced7847c5099 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 3 Jul 2022 22:42:11 +0100 Subject: [PATCH] refactor: apply cadence to bot and make it compile --- bot/.eslintrc | 5 + bot/package.json | 10 +- bot/src/config.ts | 24 ++-- bot/src/database/documents.ts | 4 +- bot/src/database/mongo.ts | 4 +- bot/src/database/queries.ts | 6 +- .../interactionHandlers/handleIsCommand.ts | 5 +- .../interactionHandlers/handleIsSelectMenu.ts | 24 ++-- bot/src/main.ts | 5 +- bot/src/server/middleware.ts | 4 +- bot/src/server/server.ts | 29 ++-- bot/src/slashCommands/commands.ts | 11 +- bot/src/slashCommands/commands/chart.ts | 15 ++- bot/src/slashCommands/commands/faq.ts | 11 +- bot/src/slashCommands/commands/folder.ts | 11 +- bot/src/slashCommands/commands/invite.ts | 4 +- bot/src/slashCommands/commands/last_score.ts | 7 +- .../slashCommands/commands/last_session.ts | 7 +- bot/src/slashCommands/commands/ping.ts | 6 +- bot/src/slashCommands/commands/profile.ts | 7 +- bot/src/slashCommands/commands/quote.ts | 103 ++++++++------- bot/src/slashCommands/commands/sync.ts | 4 +- bot/src/slashCommands/commands/whois.ts | 6 +- bot/src/slashCommands/register.ts | 9 +- bot/src/slashCommands/types.ts | 12 +- bot/src/utils/apiRequests.ts | 45 +++---- bot/src/utils/argParsers.ts | 19 +-- bot/src/utils/constants.ts | 2 +- bot/src/utils/embeds.ts | 68 +++++----- bot/src/utils/fetchTachi.ts | 22 ++-- bot/src/utils/logger.ts | 9 +- bot/src/utils/misc.ts | 73 +++++++---- bot/src/utils/options.ts | 6 +- bot/src/utils/predicates.ts | 2 +- bot/src/utils/prudence.ts | 2 +- bot/src/utils/returnTypes.ts | 30 ++--- bot/src/webhookHandlers/classUpdate.ts | 9 +- bot/src/webhookHandlers/goalAchieved.ts | 8 +- bot/tsconfig.json | 26 ++-- common/tsconfig.json | 4 +- database-seeds/scripts/package.json | 1 - package.json | 5 +- pnpm-lock.yaml | 124 +++--------------- server/package.json | 1 - server/tsconfig.json | 6 +- tsconfig.json | 6 +- 46 files changed, 389 insertions(+), 412 deletions(-) create mode 100644 bot/.eslintrc diff --git a/bot/.eslintrc b/bot/.eslintrc new file mode 100644 index 000000000..906bd6e43 --- /dev/null +++ b/bot/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "@typescript-eslint/no-unsafe-assignment": "warn" + } +} \ No newline at end of file diff --git a/bot/package.json b/bot/package.json index ade49b4d1..5493b0ac5 100644 --- a/bot/package.json +++ b/bot/package.json @@ -1,11 +1,11 @@ { "name": "tachi-bot", - "version": "1.0.0", - "main": "src/index.ts", + "version": "0.0.0", + "main": "js/bot/src/main.js", "license": "MIT", "scripts": { "build": "tsc", - "start": "pnpm build && node js/main.js", + "start": "pnpm build && node -r ts-node/register -r tsconfig-paths/register js/bot/src/main.js", "lint": "eslint . --fix" }, "devDependencies": { @@ -34,8 +34,6 @@ "monk": "7.3.4", "node-fetch": "2.6.7", "prudence": "0.9.6", - "sync-fetch": "^0.3.1", - "tachi-common": "0.7.2", - "ts-node": "10.2.0" + "sync-fetch": "^0.3.1" } } \ No newline at end of file diff --git a/bot/src/config.ts b/bot/src/config.ts index a1bdaf66e..d037427d5 100644 --- a/bot/src/config.ts +++ b/bot/src/config.ts @@ -1,15 +1,16 @@ -import fs from "fs"; -import { config } from "dotenv"; -import JSON5 from "json5"; -import p from "prudence"; -// @ts-expect-error No types available... -import fetchSync from "sync-fetch"; -import { Game, integer } from "tachi-common"; import { LoggerLayers } from "./data/data"; import { CreateLayeredLogger } from "./utils/logger"; import { IsRecord } from "./utils/predicates"; import { FormatPrError } from "./utils/prudence"; -import { ServerConfig as ServerConfigType } from "./utils/returnTypes"; +import { config } from "dotenv"; +import JSON5 from "json5"; +import p from "prudence"; + +// @ts-expect-error No types available... +import fetchSync from "sync-fetch"; +import fs from "fs"; +import type { ServerConfig as ServerConfigType } from "./utils/returnTypes"; +import type { Game, integer } from "tachi-common"; // Initialise .env. config(); @@ -32,7 +33,7 @@ export interface BotConfig { TOKEN: string; SERVER_ID: string; GAME_CHANNELS: Partial>; - ADMIN_USERS: string[]; + ADMIN_USERS: Array; }; } @@ -41,6 +42,7 @@ function ParseBotConfig(fileLoc = "conf.json5"): BotConfig { try { const contents = fs.readFileSync(fileLoc, "utf-8"); + data = JSON5.parse(contents); } catch (err) { logger.error("Failed to find/parse a valid conf.json5 file. Cannot boot.", { err }); @@ -76,6 +78,7 @@ function ParseBotConfig(fileLoc = "conf.json5"): BotConfig { return true; }, + // A list of users that are allowed to do powerful stuff. ADMIN_USERS: ["string"], }, @@ -91,7 +94,7 @@ function ParseBotConfig(fileLoc = "conf.json5"): BotConfig { } export interface ProcessEnvironment { - nodeEnv: "production" | "dev" | "staging" | "test"; + nodeEnv: "dev" | "production" | "staging" | "test"; mongoUrl: string; port: integer; } @@ -101,6 +104,7 @@ function ParseEnvVars() { process.env, { NODE_ENV: p.isIn("production", "dev", "staging", "test"), + // mei implicitly reads this. LOG_LEVEL: p.optional( p.isIn("debug", "verbose", "info", "warn", "error", "severe", "crit") diff --git a/bot/src/database/documents.ts b/bot/src/database/documents.ts index 573e654f6..7c7374301 100644 --- a/bot/src/database/documents.ts +++ b/bot/src/database/documents.ts @@ -1,5 +1,5 @@ -import { Snowflake } from "discord.js"; -import { integer } from "tachi-common"; +import type { Snowflake } from "discord.js"; +import type { integer } from "tachi-common"; export interface DiscordUserMapDocument { userID: integer; diff --git a/bot/src/database/mongo.ts b/bot/src/database/mongo.ts index f8d3be4e7..f2f876f8e 100644 --- a/bot/src/database/mongo.ts +++ b/bot/src/database/mongo.ts @@ -1,8 +1,8 @@ -import monk from "monk"; import { ProcessEnv } from "../config"; import { LoggerLayers } from "../data/data"; import { CreateLayeredLogger } from "../utils/logger"; -import { DiscordUserMapDocument, QuoteDocument } from "./documents"; +import monk from "monk"; +import type { DiscordUserMapDocument, QuoteDocument } from "./documents"; const logger = CreateLayeredLogger(LoggerLayers.database); diff --git a/bot/src/database/queries.ts b/bot/src/database/queries.ts index a3af36ff4..e89cb2957 100644 --- a/bot/src/database/queries.ts +++ b/bot/src/database/queries.ts @@ -1,8 +1,8 @@ -import { integer } from "tachi-common"; +import db from "./mongo"; import { LoggerLayers } from "../data/data"; import { CreateLayeredLogger } from "../utils/logger"; -import { DiscordUserMapDocument, QuoteDocument } from "./documents"; -import db from "./mongo"; +import type { DiscordUserMapDocument, QuoteDocument } from "./documents"; +import type { integer } from "tachi-common"; const logger = CreateLayeredLogger(LoggerLayers.databaseQuery); diff --git a/bot/src/interactionHandlers/handleIsCommand.ts b/bot/src/interactionHandlers/handleIsCommand.ts index 747a626b5..b15c04787 100644 --- a/bot/src/interactionHandlers/handleIsCommand.ts +++ b/bot/src/interactionHandlers/handleIsCommand.ts @@ -1,8 +1,9 @@ -import { CommandInteraction, MessageEmbed } from "discord.js"; import { LoggerLayers } from "../data/data"; -import { DiscordUserMapDocument } from "../database/documents"; import { SLASH_COMMANDS } from "../slashCommands/commands"; import { CreateLayeredLogger } from "../utils/logger"; +import { MessageEmbed } from "discord.js"; +import type { DiscordUserMapDocument } from "../database/documents"; +import type { CommandInteraction } from "discord.js"; const logger = CreateLayeredLogger(LoggerLayers.slashCommands); diff --git a/bot/src/interactionHandlers/handleIsSelectMenu.ts b/bot/src/interactionHandlers/handleIsSelectMenu.ts index 9b12a5df5..8e3c23b7a 100644 --- a/bot/src/interactionHandlers/handleIsSelectMenu.ts +++ b/bot/src/interactionHandlers/handleIsSelectMenu.ts @@ -1,7 +1,4 @@ -import { SelectMenuInteraction } from "discord.js"; -import { Game, Playtype } from "tachi-common"; import { LoggerLayers } from "../data/data"; -import { DiscordUserMapDocument } from "../database/documents"; import { GetUserInfo } from "../utils/apiRequests"; import { CreateChartScoresEmbed, @@ -9,6 +6,9 @@ import { CreateFolderTimelineEmbed, } from "../utils/embeds"; import { CreateLayeredLogger } from "../utils/logger"; +import type { DiscordUserMapDocument } from "../database/documents"; +import type { SelectMenuInteraction } from "discord.js"; +import type { Game, Playtype } from "tachi-common"; const logger = CreateLayeredLogger(LoggerLayers.slashCommands); @@ -34,8 +34,8 @@ export async function handleIsSelectMenu( if (interaction.customId.startsWith("chart!")) { // horrendous hackery, but we only have 100 chars to store selector metadata. - const [, game, playtype, userID] = interaction.customId.match( - /!(.*):(.*):(.*)$/u + const [, game, playtype, userID] = /!(.*):(.*):(.*)$/u.exec( + interaction.customId ) as unknown as [string, Game, Playtype, string]; const userDoc = await GetUserInfo(userID); @@ -44,32 +44,32 @@ export async function handleIsSelectMenu( userDoc, game, playtype, - interaction.values[0], + interaction.values[0]!, null ); await interaction.update({ embeds: [embed] }); } else if (interaction.customId.startsWith("folder!")) { - const [, game, playtype, username] = interaction.customId.match( - /!(.*):(.*):(.*)$/u + const [, game, playtype, username] = /!(.*):(.*):(.*)$/u.exec( + interaction.customId ) as unknown as [string, Game, Playtype, string]; const embed = await CreateFolderStatsEmbed( game, playtype, username, - interaction.values[0] + interaction.values[0]! ); await interaction.update({ embeds: [embed] }); } else if (interaction.customId.startsWith("ftl!")) { const [, game, playtype, username, formatMethod, rawTarget] = - interaction.customId.match(/!(.*):(.*):(.*):(.*):(.*)$/u) as unknown as [ + /!(.*):(.*):(.*):(.*):(.*)$/u.exec(interaction.customId) as unknown as [ string, Game, Playtype, string, - "recent" | "first", + "first" | "recent", string ]; @@ -77,7 +77,7 @@ export async function handleIsSelectMenu( game, playtype, username, - interaction.values[0], + interaction.values[0]!, formatMethod, rawTarget ); diff --git a/bot/src/main.ts b/bot/src/main.ts index 5cc16a2ff..206850111 100644 --- a/bot/src/main.ts +++ b/bot/src/main.ts @@ -1,15 +1,17 @@ -import { Client, CommandInteraction, Intents, SelectMenuInteraction } from "discord.js"; import { BotConfig, ProcessEnv, ServerConfig } from "./config"; import { LoggerLayers, METALLIC_MIND_SPLASHES } from "./data/data"; import { GetUserAndTokenForDiscordID } from "./database/queries"; import { handleIsCommand } from "./interactionHandlers/handleIsCommand"; import { handleIsSelectMenu } from "./interactionHandlers/handleIsSelectMenu"; + // import { handleIsSelectMenu } from "./interactionHandlers/handleIsSelectMenu"; import { app } from "./server/server"; import { RegisterSlashCommands } from "./slashCommands/register"; import { CreateLayeredLogger } from "./utils/logger"; import { RFA, TruncateString } from "./utils/misc"; import { VERSION_PRETTY } from "./version"; +import { Client, Intents } from "discord.js"; +import type { CommandInteraction, SelectMenuInteraction } from "discord.js"; const logger = CreateLayeredLogger(LoggerLayers.client); @@ -57,6 +59,7 @@ async function RequireUserAuth(interaction: CommandInteraction | SelectMenuInter const oAuthLink = `${BotConfig.TACHI_SERVER_LOCATION}/oauth/request-auth?clientID=${BotConfig.OAUTH.CLIENT_ID}&context=${interaction.user.id}`; const dmChannel = await interaction.user.createDM(); + await dmChannel.send(`Click this link to authenticate with ${ServerConfig.name}: ${oAuthLink}`); return interaction.reply({ content: `To use the bot, your discord account must be linked to ${ServerConfig.name}. diff --git a/bot/src/server/middleware.ts b/bot/src/server/middleware.ts index 757d56d4e..dcd2e34ef 100644 --- a/bot/src/server/middleware.ts +++ b/bot/src/server/middleware.ts @@ -1,7 +1,7 @@ -import { RequestHandler } from "express"; import { BotConfig } from "../config"; import { LoggerLayers } from "../data/data"; import { CreateLayeredLogger } from "../utils/logger"; +import type { RequestHandler } from "express"; const logger = CreateLayeredLogger(LoggerLayers.serverAuth); @@ -45,5 +45,5 @@ export const ValidateWebhookRequest: RequestHandler = (req, res, next) => { logger.debug("Webhook authorisation successful."); - return next(); + next(); }; diff --git a/bot/src/server/server.ts b/bot/src/server/server.ts index 270f26759..0ebbc25e4 100644 --- a/bot/src/server/server.ts +++ b/bot/src/server/server.ts @@ -1,6 +1,4 @@ -import path from "path"; -import express, { Express } from "express"; -import { APITokenDocument, PublicUserDocument, WebhookEvents } from "tachi-common"; +import { ValidateWebhookRequest } from "./middleware"; import { BotConfig, ProcessEnv } from "../config"; import { LoggerLayers } from "../data/data"; import db from "../database/mongo"; @@ -9,7 +7,10 @@ import { CreateLayeredLogger } from "../utils/logger"; import { VERSION_PRETTY } from "../version"; import { HandleClassUpdateV1 } from "../webhookHandlers/classUpdate"; import { HandleGoalAchievedV1 } from "../webhookHandlers/goalAchieved"; -import { ValidateWebhookRequest } from "./middleware"; +import express from "express"; +import path from "path"; +import type { Express } from "express"; +import type { APITokenDocument, PublicUserDocument, WebhookEvents } from "tachi-common"; export const app: Express = express(); @@ -123,7 +124,7 @@ app.get("/oauth/callback", async (req, res) => { }); } - return res.sendFile(path.join(__dirname, "../../pages/account-linked.html")); + res.sendFile(path.join(__dirname, "../../pages/account-linked.html")); }); /** @@ -139,18 +140,22 @@ app.post("/webhook", ValidateWebhookRequest, async (req, res) => { let statusCode = 200; switch (webhookEvent.type) { - case "class-update/v1": + case "class-update/v1": { statusCode = await HandleClassUpdateV1(webhookEvent.content); break; - case "goals-achieved/v1": + } + + case "goals-achieved/v1": { statusCode = await HandleGoalAchievedV1(webhookEvent.content); break; + } + case "milestone-achieved/v1": - default: - // According to the types, this should never happen. - // However, tachi-(server/common) may recieve an update - // to define new webhooks, and the bot might not + default: { // get around to updating in time. + // to define new webhooks, and the bot might not + // However, tachi-(server/common) may recieve an update + // According to the types, this should never happen. logger.warn( `Received unknown webhook event ${ (webhookEvent as WebhookEvents).type @@ -160,6 +165,7 @@ app.post("/webhook", ValidateWebhookRequest, async (req, res) => { success: false, description: `The type ${(webhookEvent as WebhookEvents).type} is unsupported.`, }); + } } return res.sendStatus(statusCode); @@ -195,6 +201,7 @@ interface ExpressJSONErr extends SyntaxError { const MainExpressErrorHandler: express.ErrorRequestHandler = (err, req, res, _next) => { if (err instanceof SyntaxError) { const expErr: ExpressJSONErr = err as ExpressJSONErr; + if (expErr.status === 400 && "body" in expErr) { logger.info(`Error in parsing JSON in request body from ${req.url}`, { url: req.originalUrl, diff --git a/bot/src/slashCommands/commands.ts b/bot/src/slashCommands/commands.ts index 142e39a3a..d387bb726 100644 --- a/bot/src/slashCommands/commands.ts +++ b/bot/src/slashCommands/commands.ts @@ -1,16 +1,16 @@ -import { ServerConfig } from "../config"; import chart from "./commands/chart"; import faq from "./commands/faq"; import folder from "./commands/folder"; +import invite from "./commands/invite"; +import last_score from "./commands/last_score"; +import last_session from "./commands/last_session"; import ping from "./commands/ping"; import profile from "./commands/profile"; import quote from "./commands/quote"; import sync from "./commands/sync"; import whois from "./commands/whois"; -import invite from "./commands/invite"; -import last_score from "./commands/last_score"; -import last_session from "./commands/last_session"; -import { SlashCommand } from "./types"; +import { ServerConfig } from "../config"; +import type { SlashCommand } from "./types"; export const SLASH_COMMANDS: Map = new Map( Object.entries({ @@ -34,5 +34,6 @@ if (ServerConfig.type !== "btchi") { // btchi or omni specific commands if (ServerConfig.type !== "ktchi") { SLASH_COMMANDS.set("invite", invite); + // None. Yet! } diff --git a/bot/src/slashCommands/commands/chart.ts b/bot/src/slashCommands/commands/chart.ts index 0165d9b50..c694a3e08 100644 --- a/bot/src/slashCommands/commands/chart.ts +++ b/bot/src/slashCommands/commands/chart.ts @@ -1,12 +1,13 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { MessageActionRow, MessageSelectMenu, Util } from "discord.js"; -import { Difficulties, FormatChart, IDStrings, integer, SongDocument } from "tachi-common"; import { GetGPTAndUser, ParseDifficulty } from "../../utils/argParsers"; import { CreateChartScoresEmbed } from "../../utils/embeds"; import { TachiServerV1Get } from "../../utils/fetchTachi"; import { GPTOptions, MakeRequired, OtherUserOption } from "../../utils/options"; -import { ChartQueryReturns } from "../../utils/returnTypes"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import { MessageActionRow, MessageSelectMenu, Util } from "discord.js"; +import { FormatChart } from "tachi-common"; +import type { ChartQueryReturns } from "../../utils/returnTypes"; +import type { SlashCommand } from "../types"; +import type { Difficulties, IDStrings, integer, SongDocument } from "tachi-common"; const command: SlashCommand = { info: new SlashCommandBuilder() @@ -52,7 +53,7 @@ const command: SlashCommand = { // As such, we just treat a difficulty (if it exists) as being part of the // song title. if ((game === "bms" || game === "pms") && difficulty) { - search += ` ${difficulty}`; + search = `${search} ${difficulty}`; } const chartsRes = await TachiServerV1Get( @@ -80,7 +81,7 @@ const command: SlashCommand = { chartsRes.body.charts = chartsRes.body.charts.slice(0, 25); } - const firstChart = chartsRes.body.charts[0]; + const firstChart = chartsRes.body.charts[0]!; // length is asserted as non-zero const songMap = new Map(); diff --git a/bot/src/slashCommands/commands/faq.ts b/bot/src/slashCommands/commands/faq.ts index 44259e824..32443b372 100644 --- a/bot/src/slashCommands/commands/faq.ts +++ b/bot/src/slashCommands/commands/faq.ts @@ -1,8 +1,8 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import deepmerge from "deepmerge"; import { BotConfig, ServerConfig } from "../../config"; import { CreateEmbed } from "../../utils/embeds"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import deepmerge from "deepmerge"; +import type { SlashCommand } from "../types"; const NEUTRAL_FAQ_ENTRIES: Record = { duplicates: `Scores on ${ServerConfig.name} are deduplicated based on your score and lamp (and some other things). @@ -111,8 +111,7 @@ Since I don't play a lot of the the games that ${ServerConfig.name} supports, we const KTCHI_FAQ_ENTRIES: Record = { kai_support: `Support for KAI based networks (FLO, EAG, MIN) is available, but we are waiting on APIs for more games on their end. At the moment, only IIDX and SDVX are supported.`, bokutachi: `Bokutachi is our public sister website for home games and simulators. Feel free to check out [the discord](${ - // Note: obfuscating this for obvious reasons so we don't get garbage bot spam. - Buffer.from("aHR0cHM6Ly9kaXNjb3JkLmdnL3N3VkJUanhtUFk=", "base64") + Buffer.from("aHR0cHM6Ly9kaXNjb3JkLmdnL3N3VkJUanhtUFk=", "base64") // Note: obfuscating this for obvious reasons so we don't get garbage bot spam. })`, sdvx_1259: `Automation Paradise is song ID 1259. It is not a real song, and it is not supported by ${ServerConfig.name}. Related autopara-only songs are not supported, either.`, invites: `To invite your friends, go to your profile and select 'Invites'. From there, you can create an invite code. @@ -125,7 +124,7 @@ const BTCHI_FAQ_ENTRIES: Record = { (This is because putting your real password in there is a security nightmare.)`, lr2: `Support for LR2 (the LR2Hook) are **not officially maintained** and therefore **not managed by bokutachi developers**. We urge you to use a more modern BMS client like lr2oraja, unless you have very good reason to stay on LR2. -Beatoraja is actively developed and integrates properly with the Bokutachi IR, it is also significantly more stable and works on modern hardware without encoding hacks.` +Beatoraja is actively developed and integrates properly with the Bokutachi IR, it is also significantly more stable and works on modern hardware without encoding hacks.`, }; let faqEntries = NEUTRAL_FAQ_ENTRIES; diff --git a/bot/src/slashCommands/commands/folder.ts b/bot/src/slashCommands/commands/folder.ts index d2128cd20..6184dadff 100644 --- a/bot/src/slashCommands/commands/folder.ts +++ b/bot/src/slashCommands/commands/folder.ts @@ -1,10 +1,11 @@ -import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { MessageActionRow, MessageSelectMenu, Util } from "discord.js"; import { FindFolders } from "../../utils/apiRequests"; import { GetGPTAndUser } from "../../utils/argParsers"; import { CreateFolderStatsEmbed, CreateFolderTimelineEmbed } from "../../utils/embeds"; import { GPTOptions, MakeRequired, OtherUserOption } from "../../utils/options"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import { MessageActionRow, MessageSelectMenu, Util } from "discord.js"; +import type { SlashCommand } from "../types"; +import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; function ApplyCommonOptions(sub: SlashCommandSubcommandBuilder) { return sub @@ -82,7 +83,7 @@ const command: SlashCommand = { } // pick an initial folder - const folder = folders[0]; + const folder = folders[0]!; // length is asserted as non-zero // with all of the common info, we now need to chalk up what to actually do. @@ -117,7 +118,7 @@ const command: SlashCommand = { const rawTarget = interaction.options.getString("target", true); - const formatMethod = interaction.options.getString("method", true) as "recent" | "first"; + const formatMethod = interaction.options.getString("method", true) as "first" | "recent"; const select = new MessageActionRow().addComponents( new MessageSelectMenu() diff --git a/bot/src/slashCommands/commands/invite.ts b/bot/src/slashCommands/commands/invite.ts index 9c86bdbe0..eb3cf68c3 100644 --- a/bot/src/slashCommands/commands/invite.ts +++ b/bot/src/slashCommands/commands/invite.ts @@ -1,6 +1,6 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; import { client } from "../../main"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import type { SlashCommand } from "../types"; const command: SlashCommand = { info: new SlashCommandBuilder() diff --git a/bot/src/slashCommands/commands/last_score.ts b/bot/src/slashCommands/commands/last_score.ts index ef4308fbc..45ea56839 100644 --- a/bot/src/slashCommands/commands/last_score.ts +++ b/bot/src/slashCommands/commands/last_score.ts @@ -1,11 +1,11 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { FormatGame } from "tachi-common"; import { GetUGPTStats } from "../../utils/apiRequests"; import { GetGPTAndUser } from "../../utils/argParsers"; import { CreateChartScoresEmbed } from "../../utils/embeds"; import logger from "../../utils/logger"; import { GPTOptions, MakeRequired, OtherUserOption } from "../../utils/options"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import { FormatGame } from "tachi-common"; +import type { SlashCommand } from "../types"; const command: SlashCommand = { info: new SlashCommandBuilder() @@ -24,6 +24,7 @@ const command: SlashCommand = { const { userDoc, game, playtype } = gptUserInfo.content; let mostRecentScore; + try { ({ mostRecentScore } = await GetUGPTStats(userDoc.id, game, playtype)); } catch (err) { diff --git a/bot/src/slashCommands/commands/last_session.ts b/bot/src/slashCommands/commands/last_session.ts index 9f14ed8a4..6691e661b 100644 --- a/bot/src/slashCommands/commands/last_session.ts +++ b/bot/src/slashCommands/commands/last_session.ts @@ -1,11 +1,11 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { FormatGame } from "tachi-common"; import { GetMostRecentSession } from "../../utils/apiRequests"; import { GetGPTAndUser } from "../../utils/argParsers"; import { CreateSessionEmbed } from "../../utils/embeds"; import logger from "../../utils/logger"; import { GPTOptions, MakeRequired, OtherUserOption } from "../../utils/options"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import { FormatGame } from "tachi-common"; +import type { SlashCommand } from "../types"; const command: SlashCommand = { info: new SlashCommandBuilder() @@ -24,6 +24,7 @@ const command: SlashCommand = { const { userDoc, game, playtype } = gptUserInfo.content; let session; + try { session = await GetMostRecentSession(userDoc.id, game, playtype); } catch (err) { diff --git a/bot/src/slashCommands/commands/ping.ts b/bot/src/slashCommands/commands/ping.ts index 3a9ac256b..b63fb7473 100644 --- a/bot/src/slashCommands/commands/ping.ts +++ b/bot/src/slashCommands/commands/ping.ts @@ -1,9 +1,9 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; import { ServerConfig } from "../../config"; import { TachiServerV1Get } from "../../utils/fetchTachi"; -import { ServerStatus } from "../../utils/returnTypes"; import { VERSION_PRETTY } from "../../version"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import type { ServerStatus } from "../../utils/returnTypes"; +import type { SlashCommand } from "../types"; const command: SlashCommand = { info: new SlashCommandBuilder() diff --git a/bot/src/slashCommands/commands/profile.ts b/bot/src/slashCommands/commands/profile.ts index 6e508c6a8..f11ae25b2 100644 --- a/bot/src/slashCommands/commands/profile.ts +++ b/bot/src/slashCommands/commands/profile.ts @@ -1,10 +1,10 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { FormatGame } from "tachi-common"; import { GetUGPTStats } from "../../utils/apiRequests"; import { GetGPTAndUser } from "../../utils/argParsers"; import { CreateGameProfileEmbed } from "../../utils/embeds"; import { GPTOptions, MakeRequired, OtherUserOption } from "../../utils/options"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import { FormatGame } from "tachi-common"; +import type { SlashCommand } from "../types"; const command: SlashCommand = { info: new SlashCommandBuilder() @@ -23,6 +23,7 @@ const command: SlashCommand = { const { userDoc, game, playtype } = gptUserInfo.content; let ugptStats; + try { ugptStats = await GetUGPTStats(userDoc.id, game, playtype); } catch (err) { diff --git a/bot/src/slashCommands/commands/quote.ts b/bot/src/slashCommands/commands/quote.ts index b10ea5083..59b9e189c 100644 --- a/bot/src/slashCommands/commands/quote.ts +++ b/bot/src/slashCommands/commands/quote.ts @@ -1,12 +1,12 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { Util } from "discord.js"; -import { escapeRegExp } from "lodash"; import db from "../../database/mongo"; import { GetQuoteWithID } from "../../database/queries"; import { GetUserInfo } from "../../utils/apiRequests"; import { CreateEmbed } from "../../utils/embeds"; import { FormatDate, IsAdmin, Pluralise, TruncateString } from "../../utils/misc"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import { Util } from "discord.js"; +import { escapeRegExp } from "lodash"; +import type { SlashCommand } from "../types"; const command: SlashCommand = { info: new SlashCommandBuilder() @@ -61,11 +61,11 @@ const command: SlashCommand = { .toJSON(), exec: async (interaction, requestingUser) => { const subCommand = interaction.options.getSubcommand() as - | "read" - | "write" | "delete" + | "read" + | "search_id" | "search_text" - | "search_id"; + | "write"; if (subCommand === "search_id" || subCommand === "search_text") { const query = interaction.options.getString("query", true).trim(); @@ -121,58 +121,65 @@ const command: SlashCommand = { const quote = await GetQuoteWithID(quoteID); - if (subCommand === "read") { - if (!quote) { - return `No quote exists for '${Util.escapeMarkdown(quoteID)}'.`; + switch (subCommand) { + case "read": { + if (!quote) { + return `No quote exists for '${Util.escapeMarkdown(quoteID)}'.`; + } + + await db.quotes.update({ quoteID }, { $inc: { hits: 1 } }); + + const authorInfo = await GetUserInfo(quote.quotedBy); + + return CreateEmbed() + .setTitle(`Quote: ${quoteID}`) + .setDescription(`\`\`\`${Util.escapeMarkdown(quote.text)}\`\`\``) + .setFooter({ + text: `Quoted by ${authorInfo.username} on ${FormatDate( + quote.quotedAt + )}. This quote has been read ${quote.hits + 1} times.`, + }); } - await db.quotes.update({ quoteID }, { $inc: { hits: 1 } }); + case "write": { + const text = interaction.options.getString("text", true); - const authorInfo = await GetUserInfo(quote.quotedBy); + if (quote) { + return `A quote exists for '${Util.escapeMarkdown(quoteID)}' already.`; + } - return CreateEmbed() - .setTitle(`Quote: ${quoteID}`) - .setDescription(`\`\`\`${Util.escapeMarkdown(quote.text)}\`\`\``) - .setFooter({ - text: `Quoted by ${authorInfo.username} on ${FormatDate( - quote.quotedAt - )}. This quote has been read ${quote.hits + 1} times.`, + if (text.length > 480) { + return `Quotes are capped at 480 characters. Not quoting this.`; + } + + await db.quotes.insert({ + quoteID, + text, + hits: 0, + quotedAt: Date.now(), + quotedBy: requestingUser.userID, }); - } else if (subCommand === "write") { - const text = interaction.options.getString("text", true); - if (quote) { - return `A quote exists for '${Util.escapeMarkdown(quoteID)}' already.`; + return `Quote '${Util.escapeMarkdown(quoteID)}' has been saved.`; } - if (text.length > 480) { - return `Quotes are capped at 480 characters. Not quoting this.`; + case "delete": { + if (!IsAdmin(requestingUser.discordID)) { + return `You are not authorised to delete quotes.`; + } + + if (!quote) { + return `No quote exists for '${Util.escapeMarkdown(quoteID)}'.`; + } + + await db.quotes.remove({ quoteID: quote.quoteID }); + + return `Quote '${Util.escapeMarkdown(quoteID)}' has been deleted.`; } - await db.quotes.insert({ - quoteID, - text, - hits: 0, - quotedAt: Date.now(), - quotedBy: requestingUser.userID, - }); - - return `Quote '${Util.escapeMarkdown(quoteID)}' has been saved.`; - } else if (subCommand === "delete") { - if (!IsAdmin(requestingUser.discordID)) { - return `You are not authorised to delete quotes.`; - } - - if (!quote) { - return `No quote exists for '${Util.escapeMarkdown(quoteID)}'.`; - } - - await db.quotes.remove({ quoteID: quote.quoteID }); - - return `Quote '${Util.escapeMarkdown(quoteID)}' has been deleted.`; + default: + throw new Error(`Unknown subcommand '${subCommand}'. How did this happen?`); } - - throw new Error(`Unknown subcommand '${subCommand}'. How did this happen?`); }, }; diff --git a/bot/src/slashCommands/commands/sync.ts b/bot/src/slashCommands/commands/sync.ts index 16c961117..c159c1b86 100644 --- a/bot/src/slashCommands/commands/sync.ts +++ b/bot/src/slashCommands/commands/sync.ts @@ -1,7 +1,7 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; import { PerformScoreImport } from "../../utils/apiRequests"; import { CreateImportEmbed } from "../../utils/embeds"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import type { SlashCommand } from "../types"; const command: SlashCommand = { info: new SlashCommandBuilder() diff --git a/bot/src/slashCommands/commands/whois.ts b/bot/src/slashCommands/commands/whois.ts index 049ae2c5f..abf5707c8 100644 --- a/bot/src/slashCommands/commands/whois.ts +++ b/bot/src/slashCommands/commands/whois.ts @@ -1,9 +1,9 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; import { ServerConfig } from "../../config"; import { GetUserIDForDiscordID } from "../../database/queries"; import { GetUserInfo } from "../../utils/apiRequests"; import { CreateUserEmbed } from "../../utils/embeds"; -import { SlashCommand } from "../types"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import type { SlashCommand } from "../types"; const command: SlashCommand = { info: new SlashCommandBuilder() @@ -18,7 +18,7 @@ const command: SlashCommand = { const userID = await GetUserIDForDiscordID(discordUser.id); - if (!userID) { + if (userID === null) { return `This user is not linked with the bot.`; } diff --git a/bot/src/slashCommands/register.ts b/bot/src/slashCommands/register.ts index 5219a2de0..771531638 100644 --- a/bot/src/slashCommands/register.ts +++ b/bot/src/slashCommands/register.ts @@ -1,10 +1,10 @@ -import { REST } from "@discordjs/rest"; -import { Routes } from "discord-api-types/v9"; -import { Client } from "discord.js"; +import { SLASH_COMMANDS } from "./commands"; import { BotConfig, ProcessEnv } from "../config"; import { LoggerLayers } from "../data/data"; import { CreateLayeredLogger } from "../utils/logger"; -import { SLASH_COMMANDS } from "./commands"; +import { REST } from "@discordjs/rest"; +import { Routes } from "discord-api-types/v9"; +import type { Client } from "discord.js"; const logger = CreateLayeredLogger(LoggerLayers.slashCommands); @@ -65,6 +65,7 @@ export async function UnregisterAllCommands(client: Client): Promise { // They use collections, which inherit from ES6's Map. Ah well. const promises = []; + for (const [, guild] of guilds) { promises.push(guild.commands.set([])); } diff --git a/bot/src/slashCommands/types.ts b/bot/src/slashCommands/types.ts index e5293f08c..452d27efb 100644 --- a/bot/src/slashCommands/types.ts +++ b/bot/src/slashCommands/types.ts @@ -1,24 +1,24 @@ -import { APIApplicationCommandOption } from "discord-api-types"; -import { +import type { DiscordUserMapDocument } from "../database/documents"; +import type { APIApplicationCommandOption } from "discord-api-types"; +import type { CommandInteraction, InteractionReplyOptions, MessageEmbed, MessagePayload, } from "discord.js"; -import { DiscordUserMapDocument } from "../database/documents"; -export type Emittable = MessagePayload | string | MessageEmbed | InteractionReplyOptions; +export type Emittable = InteractionReplyOptions | MessageEmbed | MessagePayload | string; export type Command = ( interaction: CommandInteraction, requestingUser: DiscordUserMapDocument -) => Promise | Emittable; +) => Emittable | Promise; export interface SlashCommand { info: { name: string; description: string; - options: APIApplicationCommandOption[]; + options: Array; }; exec: Command; } diff --git a/bot/src/utils/apiRequests.ts b/bot/src/utils/apiRequests.ts index b7d4bb0f2..019d8b772 100644 --- a/bot/src/utils/apiRequests.ts +++ b/bot/src/utils/apiRequests.ts @@ -1,5 +1,11 @@ -import { CommandInteraction } from "discord.js"; -import { +import { RequestTypes, TachiServerV1Get, TachiServerV1Request } from "./fetchTachi"; +import { CreateLayeredLogger } from "./logger"; +import { Sleep } from "./misc"; +import { LoggerLayers } from "../data/data"; +import { ScoreDocument } from "tachi-common"; +import type { ImportDeferred, ImportPollStatus, SessionInfo, UGPTStats } from "./returnTypes"; +import type { CommandInteraction } from "discord.js"; +import type { ChartDocument, FolderDocument, Game, @@ -9,15 +15,9 @@ import { PBScoreDocument, Playtype, PublicUserDocument, - ScoreDocument, SessionDocument, SongDocument, } from "tachi-common"; -import { LoggerLayers } from "../data/data"; -import { RequestTypes, TachiServerV1Get, TachiServerV1Request } from "./fetchTachi"; -import { CreateLayeredLogger } from "./logger"; -import { Sleep } from "./misc"; -import { ImportDeferred, ImportPollStatus, SessionInfo, UGPTStats } from "./returnTypes"; const logger = CreateLayeredLogger(LoggerLayers.apiRequests); @@ -92,7 +92,7 @@ export async function PerformScoreImport( body: Record, interaction?: CommandInteraction ) { - const initRes = await TachiServerV1Request( + const initRes = await TachiServerV1Request( RequestTypes.POST, url, authToken, @@ -122,23 +122,24 @@ export async function PerformScoreImport( if (pollRes.success) { if (pollRes.body.importStatus === "completed") { + // is there even a nice way around this -- + // why *are* we nested so deeply? + // eslint-disable-next-line max-depth if (interaction) { - interaction.editReply(`Import finished!`); + void interaction.editReply(`Import finished!`); } return pollRes.body.import; - } else { - if (interaction) { - interaction.editReply( - `Importing Scores: ${ - pollRes.body.progress.description ?? "Importing." - }..` - ); - } - - // eslint-disable-next-line no-await-in-loop - await Sleep(1000); } + + if (interaction) { + void interaction.editReply( + `Importing Scores: ${pollRes.body.progress.description}..` + ); + } + + // eslint-disable-next-line no-await-in-loop + await Sleep(1000); } else { throw new Error(`Failed to import scores. ${pollRes.description}.`); } @@ -151,7 +152,7 @@ export async function PerformScoreImport( } export async function FindFolders(game: Game, playtype: Playtype, folderName: string) { - const res = await TachiServerV1Get( + const res = await TachiServerV1Get>( `/games/${game}/${playtype}/folders`, null, { diff --git a/bot/src/utils/argParsers.ts b/bot/src/utils/argParsers.ts index c9e7f8c5c..2ca53b62c 100644 --- a/bot/src/utils/argParsers.ts +++ b/bot/src/utils/argParsers.ts @@ -1,16 +1,10 @@ -import { CommandInteraction } from "discord.js"; -import { - Difficulties, - Game, - GetGamePTConfig, - IDStrings, - Playtype, - PublicUserDocument, -} from "tachi-common"; -import { DiscordUserMapDocument } from "../database/documents"; -import { Emittable } from "../slashCommands/types"; import { GetUserInfo } from "./apiRequests"; import { ConvertInputIntoGenerousRegex, ParseGPT } from "./misc"; +import { GetGamePTConfig } from "tachi-common"; +import type { DiscordUserMapDocument } from "../database/documents"; +import type { Emittable } from "../slashCommands/types"; +import type { CommandInteraction } from "discord.js"; +import type { Difficulties, Game, IDStrings, Playtype, PublicUserDocument } from "tachi-common"; /** * Utility parser for getting the game, playtype and requesting user, since this is @@ -20,8 +14,8 @@ export async function GetGPTAndUser( interaction: CommandInteraction, requestingUser: DiscordUserMapDocument ): Promise< - | { error: null; content: { userDoc: PublicUserDocument; game: Game; playtype: Playtype } } | { error: Emittable } + | { error: null; content: { userDoc: PublicUserDocument; game: Game; playtype: Playtype } } > { const userID = interaction.options.getString("other_user") ?? requestingUser.userID.toString(); @@ -30,6 +24,7 @@ export async function GetGPTAndUser( } let userDoc; + try { userDoc = await GetUserInfo(userID); } catch (err) { diff --git a/bot/src/utils/constants.ts b/bot/src/utils/constants.ts index 5fbb911a7..e6c11d297 100644 --- a/bot/src/utils/constants.ts +++ b/bot/src/utils/constants.ts @@ -50,4 +50,4 @@ export enum POPN_CLASSES { GENERAL, HERMIT, GOD, -} \ No newline at end of file +} diff --git a/bot/src/utils/embeds.ts b/bot/src/utils/embeds.ts index e7ddf3e15..9a466378d 100644 --- a/bot/src/utils/embeds.ts +++ b/bot/src/utils/embeds.ts @@ -1,21 +1,3 @@ -import { EmbedFieldData, MessageEmbed, Util } from "discord.js"; -import { - ChartDocument, - FolderDocument, - FormatChart, - FormatGame, - Game, - GetGamePTConfig, - ImportDocument, - integer, - PBScoreDocument, - Playtype, - PublicUserDocument, - ScoreDocument, - SessionDocument, - SongDocument, -} from "tachi-common"; -import { BotConfig, ServerConfig } from "../config"; import { GetChartInfoForUser, GetSessionInfo } from "./apiRequests"; import { ParseTimelineTarget } from "./argParsers"; import { PrependTachiUrl, TachiServerV1Get } from "./fetchTachi"; @@ -37,7 +19,24 @@ import { UniqueOnKey, UppercaseFirst, } from "./misc"; -import { UGPTFolderStat, UGPTFolderTimeline, UGPTStats } from "./returnTypes"; +import { BotConfig, ServerConfig } from "../config"; +import { MessageEmbed, Util } from "discord.js"; +import { FormatChart, FormatGame, GetGamePTConfig } from "tachi-common"; +import type { UGPTFolderStat, UGPTFolderTimeline, UGPTStats } from "./returnTypes"; +import type { EmbedFieldData } from "discord.js"; +import type { + ChartDocument, + FolderDocument, + Game, + ImportDocument, + integer, + PBScoreDocument, + Playtype, + PublicUserDocument, + ScoreDocument, + SessionDocument, + SongDocument, +} from "tachi-common"; export function CreateEmbed() { return new MessageEmbed() @@ -99,7 +98,7 @@ export function CreateGameProfileEmbed(userDoc: PublicUserDocument, ugptStats: U Entries(ugptStats.gameStats.classes) .map( ([k, v]) => - `**${UppercaseFirst(k)}**: ${gptConfig.classHumanisedFormat[k][v].display}` + `**${UppercaseFirst(k)}**: ${gptConfig.classHumanisedFormat[k][v]?.display}` ) .join("\n") || "No Classes", true @@ -107,7 +106,7 @@ export function CreateGameProfileEmbed(userDoc: PublicUserDocument, ugptStats: U .addField("Playcount", ugptStats.totalScores.toString()) .addField( "First Play", - ugptStats.firstScore.timeAchieved + ugptStats.firstScore.timeAchieved !== null ? `${FormatDate(ugptStats.firstScore.timeAchieved)} (${MillisToSince(ugptStats.firstScore.timeAchieved)})` : "N/A", @@ -115,7 +114,7 @@ export function CreateGameProfileEmbed(userDoc: PublicUserDocument, ugptStats: U ) .addField( "Last Played", - ugptStats.mostRecentScore.timeAchieved + ugptStats.mostRecentScore.timeAchieved !== null ? `${FormatDate(ugptStats.mostRecentScore.timeAchieved)} (${MillisToSince(ugptStats.mostRecentScore.timeAchieved)})` : "N/A", @@ -135,7 +134,7 @@ export async function CreateChartScoresEmbed( ) { const { song, chart, pb } = await GetChartInfoForUser(userDoc.id, chartID, game, playtype); - let score: ScoreDocument | PBScoreDocument | null = renderThisScore; + let score: PBScoreDocument | ScoreDocument | null = renderThisScore; // if the user didn't pass a score to be rendered, render their PB. if (renderThisScore === null) { @@ -173,7 +172,7 @@ export async function CreateChartScoresEmbed( ) .addField( "Last Raised", - score.timeAchieved + score.timeAchieved !== null ? `${FormatDate(score.timeAchieved)} (${MillisToSince(score.timeAchieved)})` : "N/A", true @@ -182,7 +181,7 @@ export async function CreateChartScoresEmbed( // if score is a PB if ("rankingData" in score) { embed.addField("Ranking", `#**${score.rankingData.rank}**/${score.rankingData.outOf}`); - } else if (pb) { + } else { const { scoreStr, lampStr } = FormatScoreData(pb); if (pb.scoreData.score !== score.scoreData.score) { @@ -260,7 +259,7 @@ export async function CreateFolderTimelineEmbed( playtype: Playtype, username: string, folderID: string, - formatMethod: "recent" | "first", + formatMethod: "first" | "recent", rawTarget: string ) { const gptConfig = GetGamePTConfig(game, playtype); @@ -289,15 +288,17 @@ export async function CreateFolderTimelineEmbed( const songMap = CreateSongMap(songs); const chartMap = CreateChartMap(charts); - let fields: EmbedFieldData[] = []; + let fields: Array = []; fields = scores .filter((e) => e.timeAchieved !== null) - .sort((a, b) => - formatMethod === "first" - ? a.timeAchieved! - b.timeAchieved! - : b.timeAchieved! - a.timeAchieved! - ) + .sort((a, b) => { + if (formatMethod === "first") { + return a.timeAchieved! - b.timeAchieved!; + } + + return b.timeAchieved! - a.timeAchieved!; + }) .slice(0, 5) .map((sc) => ScoreToEmbed(sc, songMap, chartMap)); @@ -353,8 +354,10 @@ export async function CreateSessionEmbed(session: SessionDocument) { embed.addFields( scores + // don't mutate original array .slice(0) + // sort on default rating alg // @todo Honour the user's preferred rating alg? .sort( @@ -364,6 +367,7 @@ export async function CreateSessionEmbed(session: SessionDocument) { ) ) .filter(UniqueOnKey("chartID")) + // pick best 5 .slice(0, 5) .map((sc) => ScoreToEmbed(sc, songMap, chartMap)) diff --git a/bot/src/utils/fetchTachi.ts b/bot/src/utils/fetchTachi.ts index db7667708..725fc61ae 100644 --- a/bot/src/utils/fetchTachi.ts +++ b/bot/src/utils/fetchTachi.ts @@ -1,18 +1,16 @@ -import fetch from "node-fetch"; -import { integer, SuccessfulAPIResponse, UnsuccessfulAPIResponse } from "tachi-common"; +import { CreateLayeredLogger } from "./logger"; import { BotConfig } from "../config"; import { LoggerLayers } from "../data/data"; import { VERSION_STR } from "../version"; -import { CreateLayeredLogger } from "./logger"; +import fetch from "node-fetch"; +import { URLSearchParams } from "url"; +import type { integer, SuccessfulAPIResponse, UnsuccessfulAPIResponse } from "tachi-common"; const logger = CreateLayeredLogger(LoggerLayers.tachiFetch); -export type APIResponse = ( - | SuccessfulAPIResponse - | (UnsuccessfulAPIResponse & { body: null }) -) & { +export type APIResponse = { statusCode: integer; -}; +} & (SuccessfulAPIResponse | (UnsuccessfulAPIResponse & { body: null })); export enum RequestTypes { GET = "GET", @@ -20,6 +18,7 @@ export enum RequestTypes { PATCH = "PATCH", PUT = "PUT", DELETE = "DELETE", + // HEAD, OPTIONS not used by tachi-server anywhere. } @@ -55,7 +54,7 @@ export async function TachiServerV1Request( body: JSON.stringify(body), }); - const json: APIResponse = await res.json(); + const json: APIResponse = (await res.json()) as unknown as APIResponse; const contents = { ...json, statusCode: res.status }; @@ -84,6 +83,7 @@ export async function TachiServerV1Get( ): Promise> { try { let authHeader = ""; + if (authToken) { authHeader = `Bearer ${authToken}`; } @@ -102,7 +102,7 @@ export async function TachiServerV1Get( }, }); - const json: APIResponse = await res.json(); + const json: APIResponse = (await res.json()) as unknown as APIResponse; const contents = { ...json, statusCode: res.status }; LogRequestResult(`GET ${realUrl}`, contents); @@ -119,7 +119,7 @@ export async function TachiServerV1Get( * Takes a url like "/hello" or "bar" and converts it to "https://tachi-server.com/api/v1/hello" or "https://tachi-server.com/api/v1/bar". */ export function PrependTachiUrl(url: string, version: "1" = "1"): string { - if (url[0] !== "/") { + if (!url.startsWith("/")) { // eslint-disable-next-line no-param-reassign url = `/${url}`; } diff --git a/bot/src/utils/logger.ts b/bot/src/utils/logger.ts index 40a80effb..f427f8b51 100644 --- a/bot/src/utils/logger.ts +++ b/bot/src/utils/logger.ts @@ -1,5 +1,6 @@ -import { CreateLogger, MeiLogger } from "mei-logger"; -import { LoggerLayers } from "../data/data"; +import { CreateLogger } from "mei-logger"; +import type { LoggerLayers } from "../data/data"; +import type { MeiLogger } from "mei-logger"; const logger = CreateLogger(`tachi-bot`); @@ -10,9 +11,7 @@ export function CreateLayeredLogger(layerName: LoggerLayers) { context: [layerName], }); - lg.defaultMeta = Object.assign({}, lg.defaultMeta ?? {}, { - context: [layerName], - }); + lg.defaultMeta = { ...(lg.defaultMeta ?? {}), context: [layerName] }; return lg as MeiLogger; } diff --git a/bot/src/utils/misc.ts b/bot/src/utils/misc.ts index ca1da407b..58de2e4d3 100644 --- a/bot/src/utils/misc.ts +++ b/bot/src/utils/misc.ts @@ -1,13 +1,12 @@ -import { Client } from "discord.js"; +import { BotConfig } from "../config"; import humaniseDuration from "humanize-duration"; import _ from "lodash"; import { DateTime } from "luxon"; -import { +import { GenericFormatGradeDelta, GetGameConfig, GetGamePTConfig } from "tachi-common"; +import type { Client } from "discord.js"; +import type { ChartDocument, Game, - GenericFormatGradeDelta, - GetGameConfig, - GetGamePTConfig, GoalDocument, Grades, IDStrings, @@ -20,15 +19,15 @@ import { ScoreDocument, SongDocument, UGSRatingsLookup, + GameConfig, } from "tachi-common"; -import { GameClassSets } from "tachi-common/game-classes"; -import { BotConfig } from "../config"; +import type { GameClassSets } from "tachi-common/game-classes"; /** * Random From Array - Selects a random value from an array. */ -export function RFA(arr: T[]): T { - return arr[Math.floor(Math.random() * arr.length)]; +export function RFA(arr: Array): T { + return arr[Math.floor(Math.random() * arr.length)] as T; } export function TruncateString(string: string, len = 30) { @@ -48,7 +47,11 @@ export function IsAdmin(discordID: string) { } export function Sleep(ms: number) { - return new Promise((resolve) => setTimeout(() => resolve(), ms)); + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, ms); + }); } export function Pluralise(int: integer, str: string) { @@ -100,8 +103,8 @@ export function FormatScoreRating( /** * Typesafe asserted version of Object.entries. */ -export function Entries(rec: Partial>): [K, V][] { - return Object.entries(rec) as [K, V][]; +export function Entries(rec: Partial>): Array<[K, V]> { + return Object.entries(rec) as Array<[K, V]>; } export function ParseGPT(str: string) { @@ -110,7 +113,8 @@ export function ParseGPT(str: string) { const game = spl[0] as Game; const playtype = spl[1] as Playtype; - const gameConfig = GetGameConfig(game); + // this is an interesting way of game checking noah + const gameConfig = GetGameConfig(game) as GameConfig | undefined; if (!gameConfig || !gameConfig.validPlaytypes.includes(playtype)) { throw new Error(`Invalid GPT Combination '${str}'.`); @@ -120,6 +124,10 @@ export function ParseGPT(str: string) { } export function UppercaseFirst(str: string) { + if (!str[0]) { + return ""; + } + return str[0].toUpperCase() + str.substring(1); } @@ -154,7 +162,15 @@ export function FormatClass( ) { const gptConfig = GetGamePTConfig(game, playtype); - return gptConfig.classHumanisedFormat[classSet][classValue].display; + const classInfo = gptConfig.classHumanisedFormat[classSet][classValue]; + + if (!classInfo) { + throw new Error( + `Couldn't find a class at index ${classValue} for ${game} ${playtype} ${classSet}?` + ); + } + + return classInfo.display; } /** @@ -197,7 +213,7 @@ export function CreateChartLink(chart: ChartDocument, game: Game) { return `${BotConfig.TACHI_SERVER_LOCATION}/dashboard/games/${game}/${chart.playtype}/songs/${chart.songID}/${chart.chartID}`; } -type ScOrPBDoc = ScoreDocument | PBScoreDocument; +type ScOrPBDoc = PBScoreDocument | ScoreDocument; export function FormatScoreData(score: ScOrPBDoc) { const game = score.game; @@ -210,7 +226,7 @@ export function FormatScoreData(score: ScOrPBDo if (game === "iidx" || game === "bms" || game === "pms") { const bp = ( score as ScOrPBDoc< - "iidx:SP" | "iidx:DP" | "pms:Controller" | "pms:Keyboard" | "bms:14K" | "bms:7K" + "bms:7K" | "bms:14K" | "iidx:DP" | "iidx:SP" | "pms:Controller" | "pms:Keyboard" > ).scoreData.hitMeta.bp; @@ -252,7 +268,7 @@ export function GetGradeFromPercent( // (hey, this for loop is backwards!) for (let i = boundaries.length; i >= 0; i--) { - if (percent + Number.EPSILON >= boundaries[i]) { + if (percent + Number.EPSILON >= boundaries[i]!) { return grades[i] as Grades[I]; } } @@ -282,7 +298,7 @@ export function GetChartPertinentInfo(game: Game, chart: Ch if (game === "iidx") { const ch = chart as ChartDocument<"iidx:DP" | "iidx:SP">; - if (!ch.data.kaidenAverage || !ch.data.worldRecord) { + if (ch.data.kaidenAverage === null || ch.data.worldRecord === null) { return null; } @@ -305,6 +321,7 @@ export function FormatChartTierlistInfo(game: Game, chart: ChartDocument) { } const fmts = []; + for (const tierlist of gptConfig.tierlists) { const data = chart.tierlistInfo[tierlist]; @@ -313,7 +330,9 @@ export function FormatChartTierlistInfo(game: Game, chart: ChartDocument) { } fmts.push( - `${tierlist}: ${data.text} (${data.value}${data.individualDifference ? " ⚖️" : ""})` + `${tierlist}: ${data.text} (${data.value}${ + data.individualDifference === true ? " ⚖️" : "" + })` ); } @@ -337,7 +356,7 @@ export function ConvertInputIntoGenerousRegex(input: string) { return regex; } -export function CreateSongMap(songs: SongDocument[]) { +export function CreateSongMap(songs: Array>) { const songMap = new Map>(); for (const song of songs) { @@ -347,7 +366,7 @@ export function CreateSongMap(songs: SongDocument[]) { return songMap; } -export function CreateUserMap(users: PublicUserDocument[]) { +export function CreateUserMap(users: Array) { const userMap = new Map(); for (const user of users) { @@ -357,7 +376,7 @@ export function CreateUserMap(users: PublicUserDocument[]) { return userMap; } -export function CreateGoalMap(goals: GoalDocument[]) { +export function CreateGoalMap(goals: Array) { const goalMap = new Map(); for (const goal of goals) { @@ -367,8 +386,8 @@ export function CreateGoalMap(goals: GoalDocument[]) { return goalMap; } -export function CreateChartIDMap(arr: T[]): Map { - const map = new Map(); +export function CreateChartIDMap(arr: Array): Map { + const map = new Map(); for (const t of arr) { map.set(t.chartID, t); @@ -377,7 +396,7 @@ export function CreateChartIDMap(arr: T[]): Map(charts: ChartDocument[]) { +export function CreateChartMap(charts: Array>) { const chartMap = new Map>(); for (const chart of charts) { @@ -387,7 +406,7 @@ export function CreateChartMap(charts: ChartDoc return chartMap; } -export function CreateScoreIDMap(scores: ScoreDocument[]) { +export function CreateScoreIDMap(scores: Array>) { const scoreMap = new Map>(); for (const score of scores) { @@ -405,6 +424,7 @@ export function NumericSOV(getValueFn: (data: T) => number, reverse = false) if (reverse) { return (a: T, b: T) => getValueFn(b) - getValueFn(a); } + return (a: T, b: T) => getValueFn(a) - getValueFn(b); } @@ -423,6 +443,7 @@ export function UniqueOnKey(prop: keyof T) { if (seenBefore.has(item[prop])) { return false; } + seenBefore.add(item[prop]); return true; diff --git a/bot/src/utils/options.ts b/bot/src/utils/options.ts index 401c004ed..41e42fefe 100644 --- a/bot/src/utils/options.ts +++ b/bot/src/utils/options.ts @@ -1,12 +1,12 @@ -import { SlashCommandStringOption } from "@discordjs/builders"; -import { FormatGame, GetGameConfig } from "tachi-common"; import { ServerConfig } from "../config"; +import { FormatGame, GetGameConfig } from "tachi-common"; +import type { SlashCommandStringOption } from "@discordjs/builders"; /** * Game Playtype options. Frequently used by things that might need * game specific listening. */ -const GPTChoices: [string, string][] = []; +const GPTChoices: Array<[string, string]> = []; for (const game of ServerConfig.games) { const gameConfig = GetGameConfig(game); diff --git a/bot/src/utils/predicates.ts b/bot/src/utils/predicates.ts index 82978c8c0..d58c6fcc9 100644 --- a/bot/src/utils/predicates.ts +++ b/bot/src/utils/predicates.ts @@ -2,5 +2,5 @@ * Determines whether an input is a record or not. */ export function IsRecord(v: unknown): v is Record { - return !!v && typeof v === "object" && !Array.isArray(v); + return v !== null && typeof v === "object" && !Array.isArray(v); } diff --git a/bot/src/utils/prudence.ts b/bot/src/utils/prudence.ts index c7aadbd00..381c1a6f6 100644 --- a/bot/src/utils/prudence.ts +++ b/bot/src/utils/prudence.ts @@ -1,4 +1,4 @@ -import { PrudenceError } from "prudence"; +import type { PrudenceError } from "prudence"; /** * Formats a PrudenceError into something a little more readable. diff --git a/bot/src/utils/returnTypes.ts b/bot/src/utils/returnTypes.ts index fa5f1803e..7725b656a 100644 --- a/bot/src/utils/returnTypes.ts +++ b/bot/src/utils/returnTypes.ts @@ -1,4 +1,4 @@ -import { +import type { APIPermissions, ChartDocument, FolderDocument, @@ -19,10 +19,10 @@ import { } from "tachi-common"; export interface ServerConfig { - games: Game[]; - importTypes: ImportTypes[]; + games: Array; + importTypes: Array; name: string; - type: "ktchi" | "btchi" | "omni"; + type: "btchi" | "ktchi" | "omni"; } export interface ServerStatus { @@ -30,7 +30,7 @@ export interface ServerStatus { startTime: number; version: string; whoami: integer | null; - permissions: APIPermissions[]; + permissions: Array; } export interface ImportDeferred { @@ -60,28 +60,28 @@ export interface UGPTStats { } export interface ChartQueryReturns { - charts: (ChartDocument & { __playcount: integer })[]; - songs: SongDocument[]; + charts: Array; + songs: Array; } export interface UGPTFolderStat { folderID: string; - grades: Record; - lamps: Record; + grades: Partial>; + lamps: Partial>; chartCount: integer; } export interface UGPTFolderTimeline { - songs: SongDocument[]; - charts: ChartDocument[]; - scores: ScoreDocument[]; + songs: Array>; + charts: Array>; + scores: Array>; folder: FolderDocument; } export interface SessionInfo { session: SessionDocument; - songs: SongDocument[]; - charts: ChartDocument[]; - scores: ScoreDocument[]; + songs: Array; + charts: Array; + scores: Array; user: PublicUserDocument; } diff --git a/bot/src/webhookHandlers/classUpdate.ts b/bot/src/webhookHandlers/classUpdate.ts index 2308dfec8..6332de2f1 100644 --- a/bot/src/webhookHandlers/classUpdate.ts +++ b/bot/src/webhookHandlers/classUpdate.ts @@ -1,5 +1,3 @@ -import { FormatGame, Game, integer, Playtype, WebhookEventClassUpdateV1 } from "tachi-common"; -import { AllClassSets } from "tachi-common/game-classes"; import { BotConfig } from "../config"; import { client } from "../main"; import { GetUGPTStats, GetUserInfo } from "../utils/apiRequests"; @@ -8,6 +6,9 @@ import { CreateEmbed } from "../utils/embeds"; import { PrependTachiUrl } from "../utils/fetchTachi"; import logger from "../utils/logger"; import { FormatClass, GetGameChannel } from "../utils/misc"; +import { FormatGame } from "tachi-common"; +import type { Game, integer, Playtype, WebhookEventClassUpdateV1 } from "tachi-common"; +import type { AllClassSets } from "tachi-common/game-classes"; export async function HandleClassUpdateV1( event: WebhookEventClassUpdateV1["content"] @@ -15,10 +16,12 @@ export async function HandleClassUpdateV1( const { game, playtype } = event; let channel; + try { channel = GetGameChannel(client, game); } catch (e) { const err = e as Error; + logger.error(`ClassUpdate handler failed: ${err.message}`); return 500; } @@ -35,7 +38,7 @@ export async function HandleClassUpdateV1( // atleast 10 of the volforce ranks, which will just result in channel spam. const minimumNecessaryScores = GetMinimumScores(game, playtype, event.set); - if (minimumNecessaryScores) { + if (minimumNecessaryScores !== null) { const { totalScores } = await GetUGPTStats(userDoc.id, game, playtype); // Do not render if the user hasn't hit the score cap. diff --git a/bot/src/webhookHandlers/goalAchieved.ts b/bot/src/webhookHandlers/goalAchieved.ts index 239997fa8..e3a05cb80 100644 --- a/bot/src/webhookHandlers/goalAchieved.ts +++ b/bot/src/webhookHandlers/goalAchieved.ts @@ -1,9 +1,10 @@ -import { GetGameConfig, GoalDocument, integer, WebhookEventGoalAchievedV1 } from "tachi-common"; import { client } from "../main"; import { GetGoalWithID, GetUserInfo } from "../utils/apiRequests"; import { CreateEmbed } from "../utils/embeds"; import logger from "../utils/logger"; import { GetGameChannel, Pluralise } from "../utils/misc"; +import { GetGameConfig } from "tachi-common"; +import type { GoalDocument, integer, WebhookEventGoalAchievedV1 } from "tachi-common"; export async function HandleGoalAchievedV1( event: WebhookEventGoalAchievedV1["content"] @@ -11,10 +12,12 @@ export async function HandleGoalAchievedV1( const { game } = event; let channel; + try { channel = GetGameChannel(client, game); } catch (e) { const err = e as Error; + logger.error(`ClassUpdate handler failed: ${err.message}`); return 500; } @@ -26,6 +29,7 @@ export async function HandleGoalAchievedV1( ); const goalMap = new Map(); + for (const goalDoc of goalDocuments) { goalMap.set(goalDoc.goalID, goalDoc); } @@ -53,7 +57,7 @@ export async function HandleGoalAchievedV1( : `${e.old.progressHuman}/${e.old.outOfHuman} -> ${e.new.progressHuman}/${e.new.outOfHuman}`; return { - name: `${goal.title}${shouldShowPlaytype ? ` (${e.playtype})` : ""}`, + name: `${goal.name}${shouldShowPlaytype ? ` (${e.playtype})` : ""}`, value, }; }) diff --git a/bot/tsconfig.json b/bot/tsconfig.json index 57210fbab..e7070d818 100644 --- a/bot/tsconfig.json +++ b/bot/tsconfig.json @@ -1,21 +1,31 @@ { + "extends": "../tsconfig.json", "compilerOptions": { - "module": "CommonJS", - "noImplicitAny": true, - "strict": true, - "target": "ES2020", + "baseUrl": "src", + "outDir": "./js", + "lib": [ + "DOM" // hack workaround. URLSearchParams must be imported, yet + // discordJS carelessly uses it without being imported + // as if it were a DOM type. + // Sad! + ], "types": [ "node", - "jest" ], - "outDir": "js/", - "esModuleInterop": true, // lets us use module.exports stuff as if they were ES6 modules. + "paths": { + "tachi-common": [ + "../../common/src" + ], + "tachi-common/*": [ + "../../common/src/*" + ] + }, }, "include": [ "src/**/*" ], "exclude": [ "node_modules", - "**/*.spec.ts" + "**/*.test.ts" ] } \ No newline at end of file diff --git a/common/tsconfig.json b/common/tsconfig.json index 672179812..110cd9b01 100644 --- a/common/tsconfig.json +++ b/common/tsconfig.json @@ -1,6 +1,8 @@ { "extends": "../tsconfig.json", - "compilerOptions": {}, + "compilerOptions": { + "outDir": "./js", + }, "include": [ "src/**/*.ts", "src/**/*.test.ts" diff --git a/database-seeds/scripts/package.json b/database-seeds/scripts/package.json index 51d8badcc..aa81ba71d 100644 --- a/database-seeds/scripts/package.json +++ b/database-seeds/scripts/package.json @@ -39,7 +39,6 @@ "monk": "^7.3.4", "node-fetch": "2.6.7", "prudence": "^0.9.7", - "ts-node": "^10.4.0", "xml2js": "^0.4.23" }, "devDependencies": {} diff --git a/package.json b/package.json index ebdfa7ebb..77fc2c813 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,13 @@ "@types/node": "17.0.8", "@types/tap": "15.0.3", "eslint": "8.18.0", - "eslint-plugin-cadence": "0.0.3", + "eslint-plugin-cadence": "0.0.4", "tap": "15.0.9", "ts-node": "10.0.0", "typescript": "4.6.3", "@typescript-eslint/eslint-plugin": "5.22.0", - "@typescript-eslint/parser": "5.22.0" + "@typescript-eslint/parser": "5.22.0", + "tsconfig-paths": "3.12.0" }, "peerDependencies": { "@types/mongodb": "3.6.18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70ad53ee8..8170fb564 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,9 +9,10 @@ importers: '@typescript-eslint/eslint-plugin': 5.22.0 '@typescript-eslint/parser': 5.22.0 eslint: 8.18.0 - eslint-plugin-cadence: 0.0.3 + eslint-plugin-cadence: 0.0.4 tap: 15.0.9 ts-node: 10.0.0 + tsconfig-paths: 3.12.0 typescript: 4.6.3 devDependencies: '@types/node': 17.0.8 @@ -19,9 +20,10 @@ importers: '@typescript-eslint/eslint-plugin': 5.22.0_3692bfa38c76dfb409a7f758aafdbd9d '@typescript-eslint/parser': 5.22.0_eslint@8.18.0+typescript@4.6.3 eslint: 8.18.0 - eslint-plugin-cadence: 0.0.3_eslint@8.18.0+typescript@4.6.3 + eslint-plugin-cadence: 0.0.4_eslint@8.18.0+typescript@4.6.3 tap: 15.0.9_ts-node@10.0.0+typescript@4.6.3 ts-node: 10.0.0_22ec373cc6e5c139fea80a5e1bbc5c61 + tsconfig-paths: 3.12.0 typescript: 4.6.3 bot: @@ -50,8 +52,6 @@ importers: node-fetch: 2.6.7 prudence: 0.9.6 sync-fetch: ^0.3.1 - tachi-common: 0.7.2 - ts-node: 10.2.0 dependencies: '@discordjs/builders': 0.5.0 '@discordjs/rest': 0.1.0-canary.0 @@ -72,8 +72,6 @@ importers: node-fetch: 2.6.7 prudence: 0.9.6 sync-fetch: 0.3.1 - tachi-common: 0.7.2 - ts-node: 10.2.0_22ec373cc6e5c139fea80a5e1bbc5c61 devDependencies: '@types/express': 4.17.13 '@types/express-serve-static-core': 4.17.24 @@ -340,7 +338,6 @@ importers: seq-logging: 1.1.1 supertest: 6.2.2 tap: 15.1.6 - ts-node: 10.4.0 tsconfig-paths: 3.12.0 typescript: 4.5.5 why-is-node-running: 2.2.1 @@ -386,7 +383,6 @@ importers: safe-json-stringify: 1.2.0 semver: 7.3.7 seq-logging: 1.1.1 - ts-node: 10.4.0_367793418afa917fddeb69e0b1aa8aae tsconfig-paths: 3.12.0 typescript: 4.5.5 winston: 3.5.1 @@ -414,7 +410,7 @@ importers: node-mocks-http: 1.11.0 prettier: 2.5.1 supertest: 6.2.2 - tap: 15.1.6_ts-node@10.4.0+typescript@4.5.5 + tap: 15.1.6_ts-node@10.0.0+typescript@4.5.5 why-is-node-running: 2.2.1 sieglinde: @@ -3373,25 +3369,6 @@ packages: engines: {node: '>=0.1.90'} dev: false - /@cspotcode/source-map-consumer/0.8.0: - resolution: {integrity: sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==} - engines: {node: '>= 12'} - dev: false - - /@cspotcode/source-map-support/0.6.1: - resolution: {integrity: sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==} - engines: {node: '>=12'} - dependencies: - '@cspotcode/source-map-consumer': 0.8.0 - dev: false - - /@cspotcode/source-map-support/0.7.0: - resolution: {integrity: sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==} - engines: {node: '>=12'} - dependencies: - '@cspotcode/source-map-consumer': 0.8.0 - dev: false - /@csstools/convert-colors/1.4.0: resolution: {integrity: sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==} engines: {node: '>=4.0.0'} @@ -4244,15 +4221,19 @@ packages: /@tsconfig/node10/1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true /@tsconfig/node12/1.0.11: resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true /@tsconfig/node14/1.0.3: resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true /@tsconfig/node16/1.0.3: resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + dev: true /@types/aes-js/3.1.1: resolution: {integrity: sha512-SDSGgXT3LRCH6qMWk8OHT1vLSVNuHNvCpKCx2/TYtQMbMGGgxJC9fspwSkQjqzRagrWnCrxuLL3jMNXLXHHvSw==} @@ -4966,11 +4947,6 @@ packages: acorn: 8.7.1 dev: true - /acorn-walk/8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} - engines: {node: '>=0.4.0'} - dev: false - /acorn/6.4.2: resolution: {integrity: sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==} engines: {node: '>=0.4.0'} @@ -4981,6 +4957,7 @@ packages: resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==} engines: {node: '>=0.4.0'} hasBin: true + dev: true /address/1.1.2: resolution: {integrity: sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==} @@ -5168,6 +5145,7 @@ packages: /arg/4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true /argparse/1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -6711,6 +6689,7 @@ packages: /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true /cron-parser/2.18.0: resolution: {integrity: sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==} @@ -7315,6 +7294,7 @@ packages: /diff/4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} + dev: true /diff/5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} @@ -7787,8 +7767,8 @@ packages: find-up: 2.1.0 dev: true - /eslint-plugin-cadence/0.0.3_eslint@8.18.0+typescript@4.6.3: - resolution: {integrity: sha512-RQz1d4wU2UZe3Ztt8UPCZffvxZFuL6WkleIxcR5yW3KTwilXOePjpmJR69989L2IIlN+JcKlPjtPPairUV1E9g==} + /eslint-plugin-cadence/0.0.4_eslint@8.18.0+typescript@4.6.3: + resolution: {integrity: sha512-uodA7IZ5KebQSx+r0DVz0j4hqBBpqq8GwW3kweiO7kDrNrDIkP/NL2T2bz7qQ6V4PpzJoi2PY6i1f5NTzZwQxQ==} engines: {node: 12.x || 14.x || >= 16} peerDependencies: eslint: '>=8' @@ -10606,6 +10586,7 @@ packages: /make-error/1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true /mamacro/0.0.3: resolution: {integrity: sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==} @@ -14887,12 +14868,6 @@ packages: prudence: 0.9.8 dev: false - /tachi-common/0.7.2: - resolution: {integrity: sha512-VBCrWWtQRa2FcVEcc7JNKBS7g9ZX0+wYbcCJKGsuSsPGWrv9wRVLwxc5Oo4xKEs12Lm93t7e2ywEvLV7mmEfCQ==} - dependencies: - '@types/mongodb': 3.6.18 - dev: false - /tap-mocha-reporter/5.0.3: resolution: {integrity: sha512-6zlGkaV4J+XMRFkN0X+yuw6xHbE9jyCZ3WUKfw4KxMyRGOpYSRuuQTRJyWX88WWuLdVTuFbxzwXhXuS2XE6o0g==} engines: {node: '>= 8'} @@ -14986,7 +14961,7 @@ packages: - treport - '@types/react' - /tap/15.1.6_ts-node@10.4.0+typescript@4.5.5: + /tap/15.1.6_ts-node@10.0.0+typescript@4.5.5: resolution: {integrity: sha512-TN7xH6Q2tUPTd6qwmkhuFJcx1vUR8e4dDUpBKc61G0krOzne7Ia6aKIFb8O/0kVazachSSuVME1V8nQ2xwWL8w==} engines: {node: '>=10'} hasBin: true @@ -15023,7 +14998,7 @@ packages: tap-parser: 10.1.0 tap-yaml: 1.0.0 tcompare: 5.0.7 - ts-node: 10.4.0_367793418afa917fddeb69e0b1aa8aae + ts-node: 10.0.0_22ec373cc6e5c139fea80a5e1bbc5c61 typescript: 4.5.5 which: 2.0.2 transitivePeerDependencies: @@ -15278,67 +15253,6 @@ packages: yn: 3.1.1 dev: true - /ts-node/10.2.0_22ec373cc6e5c139fea80a5e1bbc5c61: - resolution: {integrity: sha512-FstYHtQz6isj8rBtYMN4bZdnXN1vq4HCbqn9vdNQcInRqtB86PePJQIxE6es0PhxKWhj2PHuwbG40H+bxkZPmg==} - engines: {node: '>=12.0.0'} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.6.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.3 - '@types/node': 17.0.8 - acorn: 8.7.1 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 4.6.3 - yn: 3.1.1 - dev: false - - /ts-node/10.4.0_367793418afa917fddeb69e0b1aa8aae: - resolution: {integrity: sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.7.0 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.3 - '@types/node': 17.0.8 - acorn: 8.7.1 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 4.5.5 - yn: 3.1.1 - dev: false - /ts-pnp/1.1.4_typescript@4.3.5: resolution: {integrity: sha512-1J/vefLC+BWSo+qe8OnJQfWTYRS6ingxjwqmHMqaMxXMj7kFtKLgAaYW3JeX3mktjgUL+etlU8/B4VUAUI9QGw==} engines: {node: '>=6'} @@ -15357,7 +15271,6 @@ packages: json5: 1.0.1 minimist: 1.2.6 strip-bom: 3.0.0 - dev: false /tsconfig-paths/3.14.1: resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} @@ -16484,6 +16397,7 @@ packages: /yn/3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} + dev: true /zod/3.17.3: resolution: {integrity: sha512-4oKP5zvG6GGbMlqBkI5FESOAweldEhSOZ6LI6cG+JzUT7ofj1ZOC0PJudpQOpT1iqOFpYYtX5Pw0+o403y4bcg==} diff --git a/server/package.json b/server/package.json index ca4ef3940..ead3b6da0 100644 --- a/server/package.json +++ b/server/package.json @@ -81,7 +81,6 @@ "safe-json-stringify": "1.2.0", "semver": "^7.3.7", "seq-logging": "1.1.1", - "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", "winston": "3.5.1", diff --git a/server/tsconfig.json b/server/tsconfig.json index 56188e0b4..1873281d1 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -7,6 +7,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "baseUrl": "src", + "outDir": "./js", "lib": [ "ES2019", // This is necessary because -- for some unknown reason -- the AWS SDK @@ -27,11 +28,6 @@ ] } }, - "ts-node": { - "require": [ - "tsconfig-paths/register" - ] - }, "include": [ "src/**/*.ts" ], diff --git a/tsconfig.json b/tsconfig.json index 17f116952..01c749dc2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,15 +3,13 @@ "target": "ES2020", "module": "CommonJS", "lib": [ - "ES2019" + "ES2019", ], "declaration": true, "strict": true, "allowJs": false, "esModuleInterop": true, - "outDir": "./js", "noUncheckedIndexedAccess": true, - "sourceMap": true, - "noEmit": true + "sourceMap": true }, } \ No newline at end of file