refactor: apply cadence to bot and make it compile

This commit is contained in:
zkldi
2022-07-04 02:16:10 +01:00
parent 8e9256a226
commit d1dcffb6de
46 changed files with 389 additions and 412 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"rules": {
"@typescript-eslint/no-unsafe-assignment": "warn"
}
}
+4 -6
View File
@@ -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"
}
}
+14 -10
View File
@@ -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<Record<Game, string>>;
ADMIN_USERS: string[];
ADMIN_USERS: Array<string>;
};
}
@@ -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")
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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);
+3 -3
View File
@@ -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);
@@ -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);
@@ -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
);
+4 -1
View File
@@ -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}.
+2 -2
View File
@@ -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();
};
+18 -11
View File
@@ -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,
+6 -5
View File
@@ -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<string, SlashCommand> = 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!
}
+8 -7
View File
@@ -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<ChartQueryReturns>(
@@ -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<integer, SongDocument>();
+5 -6
View File
@@ -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<string, string> = {
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<string, string> = {
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<string, string> = {
(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;
+6 -5
View File
@@ -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()
+2 -2
View File
@@ -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()
+4 -3
View File
@@ -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) {
@@ -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) {
+3 -3
View File
@@ -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()
+4 -3
View File
@@ -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) {
+55 -48
View File
@@ -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?`);
},
};
+2 -2
View File
@@ -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()
+3 -3
View File
@@ -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.`;
}
+5 -4
View File
@@ -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<void> {
// They use collections, which inherit from ES6's Map. Ah well.
const promises = [];
for (const [, guild] of guilds) {
promises.push(guild.commands.set([]));
}
+6 -6
View File
@@ -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;
) => Emittable | Promise<Emittable>;
export interface SlashCommand {
info: {
name: string;
description: string;
options: APIApplicationCommandOption[];
options: Array<APIApplicationCommandOption>;
};
exec: Command;
}
+23 -22
View File
@@ -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<string, unknown>,
interaction?: CommandInteraction
) {
const initRes = await TachiServerV1Request<ImportDocument | ImportDeferred>(
const initRes = await TachiServerV1Request<ImportDeferred | ImportDocument>(
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<FolderDocument[]>(
const res = await TachiServerV1Get<Array<FolderDocument>>(
`/games/${game}/${playtype}/folders`,
null,
{
+7 -12
View File
@@ -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) {
+1 -1
View File
@@ -50,4 +50,4 @@ export enum POPN_CLASSES {
GENERAL,
HERMIT,
GOD,
}
}
+36 -32
View File
@@ -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<EmbedFieldData> = [];
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))
+11 -11
View File
@@ -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<T> = (
| SuccessfulAPIResponse<T>
| (UnsuccessfulAPIResponse & { body: null })
) & {
export type APIResponse<T> = {
statusCode: integer;
};
} & (SuccessfulAPIResponse<T> | (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<T>(
body: JSON.stringify(body),
});
const json: APIResponse<T> = await res.json();
const json: APIResponse<T> = (await res.json()) as unknown as APIResponse<T>;
const contents = { ...json, statusCode: res.status };
@@ -84,6 +83,7 @@ export async function TachiServerV1Get<T = unknown>(
): Promise<APIResponse<T>> {
try {
let authHeader = "";
if (authToken) {
authHeader = `Bearer ${authToken}`;
}
@@ -102,7 +102,7 @@ export async function TachiServerV1Get<T = unknown>(
},
});
const json: APIResponse<T> = await res.json();
const json: APIResponse<T> = (await res.json()) as unknown as APIResponse<T>;
const contents = { ...json, statusCode: res.status };
LogRequestResult(`GET ${realUrl}`, contents);
@@ -119,7 +119,7 @@ export async function TachiServerV1Get<T = unknown>(
* 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}`;
}
+4 -5
View File
@@ -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;
}
+47 -26
View File
@@ -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<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
export function RFA<T>(arr: Array<T>): 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<void>((resolve) => setTimeout(() => resolve(), ms));
return new Promise<void>((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<K extends string, V>(rec: Partial<Record<K, V>>): [K, V][] {
return Object.entries(rec) as [K, V][];
export function Entries<K extends string, V>(rec: Partial<Record<K, V>>): 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<I extends IDStrings> = ScoreDocument<I> | PBScoreDocument<I>;
type ScOrPBDoc<I extends IDStrings> = PBScoreDocument<I> | ScoreDocument<I>;
export function FormatScoreData<I extends IDStrings = IDStrings>(score: ScOrPBDoc<I>) {
const game = score.game;
@@ -210,7 +226,7 @@ export function FormatScoreData<I extends IDStrings = IDStrings>(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<I extends IDStrings = IDStrings>(
// (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<I extends IDStrings>(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<G extends Game = Game>(songs: SongDocument<G>[]) {
export function CreateSongMap<G extends Game = Game>(songs: Array<SongDocument<G>>) {
const songMap = new Map<integer, SongDocument<G>>();
for (const song of songs) {
@@ -347,7 +366,7 @@ export function CreateSongMap<G extends Game = Game>(songs: SongDocument<G>[]) {
return songMap;
}
export function CreateUserMap(users: PublicUserDocument[]) {
export function CreateUserMap(users: Array<PublicUserDocument>) {
const userMap = new Map<integer, PublicUserDocument>();
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<GoalDocument>) {
const goalMap = new Map<string, GoalDocument>();
for (const goal of goals) {
@@ -367,8 +386,8 @@ export function CreateGoalMap(goals: GoalDocument[]) {
return goalMap;
}
export function CreateChartIDMap<T extends { chartID: string }>(arr: T[]): Map<string, T> {
const map = new Map();
export function CreateChartIDMap<T extends { chartID: string }>(arr: Array<T>): Map<string, T> {
const map = new Map<string, T>();
for (const t of arr) {
map.set(t.chartID, t);
@@ -377,7 +396,7 @@ export function CreateChartIDMap<T extends { chartID: string }>(arr: T[]): Map<s
return map;
}
export function CreateChartMap<I extends IDStrings = IDStrings>(charts: ChartDocument<I>[]) {
export function CreateChartMap<I extends IDStrings = IDStrings>(charts: Array<ChartDocument<I>>) {
const chartMap = new Map<string, ChartDocument<I>>();
for (const chart of charts) {
@@ -387,7 +406,7 @@ export function CreateChartMap<I extends IDStrings = IDStrings>(charts: ChartDoc
return chartMap;
}
export function CreateScoreIDMap<I extends IDStrings = IDStrings>(scores: ScoreDocument<I>[]) {
export function CreateScoreIDMap<I extends IDStrings = IDStrings>(scores: Array<ScoreDocument<I>>) {
const scoreMap = new Map<string, ScoreDocument<I>>();
for (const score of scores) {
@@ -405,6 +424,7 @@ export function NumericSOV<T>(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<T>(prop: keyof T) {
if (seenBefore.has(item[prop])) {
return false;
}
seenBefore.add(item[prop]);
return true;
+3 -3
View File
@@ -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);
+1 -1
View File
@@ -2,5 +2,5 @@
* Determines whether an input is a record or not.
*/
export function IsRecord<T = string>(v: unknown): v is Record<string, T> {
return !!v && typeof v === "object" && !Array.isArray(v);
return v !== null && typeof v === "object" && !Array.isArray(v);
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { PrudenceError } from "prudence";
import type { PrudenceError } from "prudence";
/**
* Formats a PrudenceError into something a little more readable.
+15 -15
View File
@@ -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<Game>;
importTypes: Array<ImportTypes>;
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<APIPermissions>;
}
export interface ImportDeferred {
@@ -60,28 +60,28 @@ export interface UGPTStats<I extends IDStrings = IDStrings> {
}
export interface ChartQueryReturns {
charts: (ChartDocument & { __playcount: integer })[];
songs: SongDocument[];
charts: Array<ChartDocument & { __playcount: integer }>;
songs: Array<SongDocument>;
}
export interface UGPTFolderStat<I extends IDStrings = IDStrings> {
folderID: string;
grades: Record<Grades[I], integer>;
lamps: Record<Lamps[I], integer>;
grades: Partial<Record<Grades[I], integer>>;
lamps: Partial<Record<Lamps[I], integer>>;
chartCount: integer;
}
export interface UGPTFolderTimeline<I extends IDStrings = IDStrings> {
songs: SongDocument<IDStringToGame[I]>[];
charts: ChartDocument<I>[];
scores: ScoreDocument<I>[];
songs: Array<SongDocument<IDStringToGame[I]>>;
charts: Array<ChartDocument<I>>;
scores: Array<ScoreDocument<I>>;
folder: FolderDocument;
}
export interface SessionInfo {
session: SessionDocument;
songs: SongDocument[];
charts: ChartDocument[];
scores: ScoreDocument[];
songs: Array<SongDocument>;
charts: Array<ChartDocument>;
scores: Array<ScoreDocument>;
user: PublicUserDocument;
}
+6 -3
View File
@@ -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.
+6 -2
View File
@@ -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<string, GoalDocument>();
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,
};
})
+18 -8
View File
@@ -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"
]
}
+3 -1
View File
@@ -1,6 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {},
"compilerOptions": {
"outDir": "./js",
},
"include": [
"src/**/*.ts",
"src/**/*.test.ts"
-1
View File
@@ -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": {}
+3 -2
View File
@@ -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",
+19 -105
View File
@@ -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==}
-1
View File
@@ -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",
+1 -5
View File
@@ -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"
],
+2 -4
View File
@@ -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
},
}