Migrate Prettier/Eslint/Stylelint setup to Biome (#1772)

* Initial

* CSS lint

* Test CI

* Add 1v1, 2v2, and 3v3 Tags (#1771)

* Initial

* CSS lint

* Test CI

* Rename step

---------

Co-authored-by: xi <104683822+ximk@users.noreply.github.com>
This commit is contained in:
Kalle
2024-06-24 13:07:17 +03:00
committed by GitHub
parent 73e9fca492
commit fd48bced91
1070 changed files with 219485 additions and 224310 deletions

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const badgeId = process.argv[2]?.trim();
const discordIds = process.argv[3]?.trim();
@@ -9,27 +9,27 @@ const discordIds = process.argv[3]?.trim();
invariant(discordIds, "id list of discord ids required (argument 1)");
invariant(badgeId, "display name of badge is required (argument 2)");
invariant(
discordIds.includes(","),
"discordIds must be a comma separated list of discord ids",
discordIds.includes(","),
"discordIds must be a comma separated list of discord ids",
);
const stm = sql.prepare(
/* sql */ `insert into "TournamentBadgeOwner" ("badgeId", "userId") values (@badgeId, (select "id" from "User" where "discordId" = @userId))`,
/* sql */ `insert into "TournamentBadgeOwner" ("badgeId", "userId") values (@badgeId, (select "id" from "User" where "discordId" = @userId))`,
);
const userStm = sql.prepare(
/* sql */ `select "id" from "User" where "discordId" = @discordId`,
/* sql */ `select "id" from "User" where "discordId" = @discordId`,
);
const users = discordIds.split(",");
for (const userId of users) {
const user = userStm.get({ discordId: userId });
if (!user) {
console.log(`User with discord id ${userId} not found`);
continue;
}
stm.run({ badgeId: Number(badgeId), userId });
const user = userStm.get({ discordId: userId });
if (!user) {
logger.info(`User with discord id ${userId} not found`);
continue;
}
stm.run({ badgeId: Number(badgeId), userId });
}
console.log(`Added ${users.length} owners to the badge`);
logger.info(`Added ${users.length} owners to the badge`);

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const code = process.argv[2]?.trim();
const displayName = process.argv[3]?.trim();
@@ -10,12 +10,12 @@ invariant(code, "code of badge is required (argument 1)");
invariant(displayName, "display name of badge is required (argument 2)");
invariant(code === code.toLocaleLowerCase(), "code of badge must be lowercase");
invariant(
displayName !== displayName.toLocaleLowerCase(),
"displayName of badge must have at least one uppercase letter",
displayName !== displayName.toLocaleLowerCase(),
"displayName of badge must have at least one uppercase letter",
);
sql
.prepare("insert into badge (code, displayName) values ($code, $displayName)")
.run({ code, displayName });
.prepare("insert into badge (code, displayName) values ($code, $displayName)")
.run({ code, displayName });
console.log(`Added new badge: ${displayName}`);
logger.info(`Added new badge: ${displayName}`);

View File

@@ -1,8 +1,8 @@
import fs from "fs";
import fs from "node:fs";
import prettier from "prettier";
import path from "path";
import { fileURLToPath } from "url";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -12,31 +12,31 @@ const dontWrite = process.argv.includes(NO_WRITE_KEY);
const KNOWN_SUFFIXES = ["_zero", "_one", "_two", "_few", "_many", "_other"];
const REPO_TRANSLATIONS_INFO_URL =
"https://github.com/Sendouc/sendou.ink#translations";
"https://github.com/Sendouc/sendou.ink#translations";
const MD = {
inlineCode: (s: string) => `\`${s}\``,
strong: (s: string) => `**${s}**`,
h2: (s: string) => `## ${s}`,
li: (s: string) => `- ${s}`,
ticked: (s: string) => `- [x] ${s}`,
unticked: (s: string) => `- [ ] ${s}`,
inlineCode: (s: string) => `\`${s}\``,
strong: (s: string) => `**${s}**`,
h2: (s: string) => `## ${s}`,
li: (s: string) => `- ${s}`,
ticked: (s: string) => `- [x] ${s}`,
unticked: (s: string) => `- [ ] ${s}`,
};
const otherLanguageTranslationPath = (code?: string, fileName?: string) =>
path.join(
...[__dirname, "..", "locales", code, fileName].filter(
(val): val is string => !!val,
),
);
path.join(
...[__dirname, "..", "locales", code, fileName].filter(
(val): val is string => !!val,
),
);
const allOtherLanguages = fs
.readdirSync(otherLanguageTranslationPath())
.filter((lang) => lang !== "en");
.readdirSync(otherLanguageTranslationPath())
.filter((lang) => lang !== "en");
const missingTranslations: Record<
string,
Record<string, Array<string>>
string,
Record<string, Array<string>>
> = Object.fromEntries(allOtherLanguages.map((lang) => [lang, {}]));
const totalTranslationCounts: Record<string, number> = {};
@@ -44,368 +44,368 @@ const totalTranslationCounts: Record<string, number> = {};
const fileNames: string[] = fs.readdirSync(otherLanguageTranslationPath("en"));
for (const file of fileNames) {
const englishContent = JSON.parse(
fs.readFileSync(otherLanguageTranslationPath("en", file), "utf8").trim(),
) as Record<string, string>;
const key = file.replace(".json", "");
const englishContentKeys = getKeysWithoutSuffix(englishContent, "en", file);
const englishContent = JSON.parse(
fs.readFileSync(otherLanguageTranslationPath("en", file), "utf8").trim(),
) as Record<string, string>;
const key = file.replace(".json", "");
const englishContentKeys = getKeysWithoutSuffix(englishContent, "en", file);
if (file !== "gear.json" && file !== "weapons.json") {
totalTranslationCounts[key] = englishContentKeys.length;
}
if (file !== "gear.json" && file !== "weapons.json") {
totalTranslationCounts[key] = englishContentKeys.length;
}
for (const lang of allOtherLanguages) {
try {
const otherRawContent = fs
.readFileSync(otherLanguageTranslationPath(lang, file), "utf8")
.trim();
let otherLanguageContent: Record<string, string>;
try {
otherLanguageContent = JSON.parse(otherRawContent);
} catch (e) {
throw new Error(`failed to parse ${lang}/${file}`);
}
for (const lang of allOtherLanguages) {
try {
const otherRawContent = fs
.readFileSync(otherLanguageTranslationPath(lang, file), "utf8")
.trim();
let otherLanguageContent: Record<string, string>;
try {
otherLanguageContent = JSON.parse(otherRawContent);
} catch (e) {
throw new Error(`failed to parse ${lang}/${file}`);
}
const otherLanguageContentKeys = getKeysWithoutSuffix(
otherLanguageContent,
lang,
file,
);
const otherLanguageContentKeys = getKeysWithoutSuffix(
otherLanguageContent,
lang,
file,
);
validateNoExtraKeysInOther({
english: otherLanguageContentKeys,
other: otherLanguageContentKeys,
lang,
file,
});
validateVariables({
english: englishContent,
other: otherLanguageContent,
lang,
file,
});
validateNoDuplicateKeys({
otherRawContent,
file,
lang,
});
validateNoExtraKeysInOther({
english: otherLanguageContentKeys,
other: otherLanguageContentKeys,
lang,
file,
});
validateVariables({
english: englishContent,
other: otherLanguageContent,
lang,
file,
});
validateNoDuplicateKeys({
otherRawContent,
file,
lang,
});
const missingKeys = englishContentKeys.filter(
(key) => !otherLanguageContentKeys.includes(key),
);
const missingKeys = englishContentKeys.filter(
(key) => !otherLanguageContentKeys.includes(key),
);
if (key === "weapons" || key === "gear") {
if (missingKeys.length > 0) {
throw new Error(`missing keys in ${lang}/${file}`);
}
} else {
missingTranslations[lang][key] = missingKeys;
}
} catch (e) {
if ((e as { code: string }).code !== "ENOENT") throw e;
if (key === "weapons" || key === "gear") {
if (missingKeys.length > 0) {
throw new Error(`missing keys in ${lang}/${file}`);
}
} else {
missingTranslations[lang][key] = missingKeys;
}
} catch (e) {
if ((e as { code: string }).code !== "ENOENT") throw e;
missingTranslations[lang][key] = englishContentKeys;
}
}
missingTranslations[lang][key] = englishContentKeys;
}
}
}
// eslint-disable-next-line no-console
console.log("no issues found inside translation files");
console.info("no issues found inside translation files");
if (dontWrite) {
process.exit(0);
process.exit(0);
}
const markdown = createTranslationProgessMarkdown({
missingTranslations,
totalTranslationCounts,
missingTranslations,
totalTranslationCounts,
});
// TODO: migrate to biome
void prettier.format(markdown, { parser: "markdown" }).then((markdown) => {
const translationProgressPath = path.join(
__dirname,
"..",
"translation-progress.md",
);
const translationProgressPath = path.join(
__dirname,
"..",
"translation-progress.md",
);
fs.writeFileSync(translationProgressPath, markdown);
fs.writeFileSync(translationProgressPath, markdown);
});
function validateNoExtraKeysInOther({
english,
other,
lang,
file,
english,
other,
lang,
file,
}: {
english: string[];
other: string[];
lang: string;
file: string;
english: string[];
other: string[];
lang: string;
file: string;
}) {
const validKeys = english;
const validKeys = english;
for (const key of other) {
if (validKeys.includes(key)) continue;
for (const key of other) {
if (validKeys.includes(key)) continue;
throw new Error(`unknown key in ${lang}/${file}: ${key}`);
}
throw new Error(`unknown key in ${lang}/${file}: ${key}`);
}
}
function validateVariables({
english,
other,
lang,
file,
english,
other,
lang,
file,
}: {
english: Record<string, string>;
other: Record<string, string>;
lang: string;
file: string;
english: Record<string, string>;
other: Record<string, string>;
lang: string;
file: string;
}) {
for (const [key, value] of Object.entries(english)) {
const otherValue = other[key];
if (!otherValue) continue;
for (const [key, value] of Object.entries(english)) {
const otherValue = other[key];
if (!otherValue) continue;
const englishMatches = value.match(/{{(.*?)}}/g);
const otherMatches = otherValue.match(/{{(.*?)}}/g);
const englishMatches = value.match(/{{(.*?)}}/g);
const otherMatches = otherValue.match(/{{(.*?)}}/g);
if (!englishMatches && !otherMatches) continue;
if (!englishMatches && !otherMatches) continue;
for (const englishVar of englishMatches ?? []) {
if (!otherMatches?.includes(englishVar)) {
throw new Error(
`variable mismatch in ${lang}/${file}: ${englishVar} is missing in ${otherValue}`,
);
}
}
}
for (const englishVar of englishMatches ?? []) {
if (!otherMatches?.includes(englishVar)) {
throw new Error(
`variable mismatch in ${lang}/${file}: ${englishVar} is missing in ${otherValue}`,
);
}
}
}
}
function validateNoDuplicateKeys({
otherRawContent,
lang,
file,
otherRawContent,
lang,
file,
}: {
otherRawContent: string;
lang: string;
file: string;
otherRawContent: string;
lang: string;
file: string;
}) {
const keys = new Set<string>();
const duplicateKeys = new Set<string>();
for (const line of otherRawContent.split("\n")) {
const key = line.trim().split(":")[0];
if (!key) continue;
const keys = new Set<string>();
const duplicateKeys = new Set<string>();
for (const line of otherRawContent.split("\n")) {
const key = line.trim().split(":")[0];
if (!key) continue;
if (keys.has(key)) {
duplicateKeys.add(key);
}
keys.add(key);
}
if (keys.has(key)) {
duplicateKeys.add(key);
}
keys.add(key);
}
if (duplicateKeys.size > 0) {
throw new Error(
`duplicate key(s) in ${lang}/${file}: ${Array.from(duplicateKeys).join(
", ",
)}`,
);
}
if (duplicateKeys.size > 0) {
throw new Error(
`duplicate key(s) in ${lang}/${file}: ${Array.from(duplicateKeys).join(
", ",
)}`,
);
}
}
// get keys while respecting different plural/context key suffixes in different languages.
function getKeysWithoutSuffix(
translations: Record<string, string>,
lang: string,
file: string,
translations: Record<string, string>,
lang: string,
file: string,
): string[] {
const foundSuffixKeys = new Set<string>();
const keys = [];
const foundSuffixKeys = new Set<string>();
const keys = [];
for (const key of Object.keys(translations)) {
const suffix = KNOWN_SUFFIXES.find((sfx) => key.endsWith(sfx));
if (!suffix) {
if (foundSuffixKeys.has(key)) {
throw new Error(
`Found same key with and without suffixes in ${lang}/${file}: ${key}`,
);
}
keys.push(key);
continue;
}
for (const key of Object.keys(translations)) {
const suffix = KNOWN_SUFFIXES.find((sfx) => key.endsWith(sfx));
if (!suffix) {
if (foundSuffixKeys.has(key)) {
throw new Error(
`Found same key with and without suffixes in ${lang}/${file}: ${key}`,
);
}
keys.push(key);
continue;
}
const baseKey = key.replace(suffix, "");
const baseKey = key.replace(suffix, "");
if (foundSuffixKeys.has(baseKey)) {
// Already found this key with a suffix. Duplicates are handled elsewhere.
continue;
}
if (foundSuffixKeys.has(baseKey)) {
// Already found this key with a suffix. Duplicates are handled elsewhere.
continue;
}
if (keys.includes(baseKey)) {
throw new Error(
`Found same key with and without suffixes in ${lang}/${file}: ${baseKey}`,
);
}
if (keys.includes(baseKey)) {
throw new Error(
`Found same key with and without suffixes in ${lang}/${file}: ${baseKey}`,
);
}
keys.push(baseKey);
foundSuffixKeys.add(baseKey);
}
keys.push(baseKey);
foundSuffixKeys.add(baseKey);
}
return keys;
return keys;
}
type StatusProps = {
totalCount: number;
missingCount: number;
percentage?: boolean;
totalCount: number;
missingCount: number;
percentage?: boolean;
};
function MDCompletionStatus({
totalCount,
missingCount,
percentage,
totalCount,
missingCount,
percentage,
}: StatusProps) {
const circle =
missingCount === 0 ? "🟢" : missingCount === totalCount ? "🔴" : "🟡";
const circle =
missingCount === 0 ? "🟢" : missingCount === totalCount ? "🔴" : "🟡";
const nonMissingCount = totalCount - missingCount;
const nonMissingCount = totalCount - missingCount;
if (!percentage) {
return `${circle} ${nonMissingCount}/${totalCount}`;
}
if (!percentage) {
return `${circle} ${nonMissingCount}/${totalCount}`;
}
const percent =
totalCount === 0 ? 100 : Math.floor((nonMissingCount / totalCount) * 100);
const percent =
totalCount === 0 ? 100 : Math.floor((nonMissingCount / totalCount) * 100);
return `${circle} ${percent}%`;
return `${circle} ${percent}%`;
}
function MDOverviewTable({
totalTranslationCounts,
totalTranslationCounts,
}: {
totalTranslationCounts: Record<string, number>;
totalTranslationCounts: Record<string, number>;
}) {
const totalKeysCount = Object.values(totalTranslationCounts).reduce(
(a, b) => a + b,
0,
);
const relevantFiles = fileNames.filter(
(name) => name !== "weapons.json" && name !== "gear.json",
);
const totalKeysCount = Object.values(totalTranslationCounts).reduce(
(a, b) => a + b,
0,
);
const relevantFiles = fileNames.filter(
(name) => name !== "weapons.json" && name !== "gear.json",
);
const rows = [];
const rows = [];
rows.push(
`| Language | Total | ${relevantFiles.map(MD.inlineCode).join(" | ")} |`,
);
rows.push(
`| Language | Total | ${relevantFiles.map(MD.inlineCode).join(" | ")} |`,
);
rows.push(`| :-- | :-: | ${relevantFiles.map(() => ":-:").join(" | ")} |`);
rows.push(`| :-- | :-: | ${relevantFiles.map(() => ":-:").join(" | ")} |`);
for (const [lang, missingKeysObj] of Object.entries(missingTranslations)) {
const cells = [];
for (const [lang, missingKeysObj] of Object.entries(missingTranslations)) {
const cells = [];
cells.push(MD.strong(lang));
cells.push(MD.strong(lang));
const totalAmountOfMissingKeys = Object.values(missingKeysObj).reduce(
(a, b) => a + b.length,
0,
);
const totalAmountOfMissingKeys = Object.values(missingKeysObj).reduce(
(a, b) => a + b.length,
0,
);
cells.push(
MD.strong(
MDCompletionStatus({
totalCount: totalKeysCount,
missingCount: totalAmountOfMissingKeys,
percentage: true,
}),
),
);
cells.push(
MD.strong(
MDCompletionStatus({
totalCount: totalKeysCount,
missingCount: totalAmountOfMissingKeys,
percentage: true,
}),
),
);
for (const file of relevantFiles) {
const fileKey = file.replace(".json", "");
const missingKeysInFile = missingKeysObj[fileKey];
if (!missingKeysInFile) {
return "";
}
for (const file of relevantFiles) {
const fileKey = file.replace(".json", "");
const missingKeysInFile = missingKeysObj[fileKey];
if (!missingKeysInFile) {
return "";
}
cells.push(
MDCompletionStatus({
totalCount: totalTranslationCounts[fileKey],
missingCount: missingKeysInFile.length,
}),
);
}
cells.push(
MDCompletionStatus({
totalCount: totalTranslationCounts[fileKey],
missingCount: missingKeysInFile.length,
}),
);
}
rows.push(`| ${cells.join(" | ")} |`);
}
rows.push(`| ${cells.join(" | ")} |`);
}
return rows.join("\n");
return rows.join("\n");
}
function MDDetailsList({
summary,
content,
summary,
content,
}: {
summary: string;
content: string[];
summary: string;
content: string[];
}) {
return `<details><summary>${summary}</summary><ul>${content
.map((c) => `<li>${c}</li>`)
.join("")}</ul></details>`;
return `<details><summary>${summary}</summary><ul>${content
.map((c) => `<li>${c}</li>`)
.join("")}</ul></details>`;
}
function MDMissingKeysList({
missingTranslations,
missingTranslations,
}: {
missingTranslations: Record<string, Record<string, Array<string>>>;
missingTranslations: Record<string, Record<string, Array<string>>>;
}) {
const blocks = [];
const blocks = [];
for (const [lang, missingKeysObj] of Object.entries(missingTranslations)) {
const parts = [];
for (const [lang, missingKeysObj] of Object.entries(missingTranslations)) {
const parts = [];
parts.push(MD.h2(lang));
parts.push(MD.h2(lang));
for (const [fileKey, missingKeys] of Object.entries(missingKeysObj)) {
const noneMissing = missingKeys.length === 0;
const checkbox = noneMissing ? MD.ticked : MD.unticked;
const fileEntry = checkbox(MD.inlineCode(`${fileKey}.json`));
for (const [fileKey, missingKeys] of Object.entries(missingKeysObj)) {
const noneMissing = missingKeys.length === 0;
const checkbox = noneMissing ? MD.ticked : MD.unticked;
const fileEntry = checkbox(MD.inlineCode(`${fileKey}.json`));
const allMissing = missingKeys.length === totalTranslationCounts[fileKey];
const allMissing = missingKeys.length === totalTranslationCounts[fileKey];
const keysLabel = allMissing
? "All keys"
: missingKeys.length === 1
? "1 key"
: `${missingKeys.length} keys`;
const keysLabel = allMissing
? "All keys"
: missingKeys.length === 1
? "1 key"
: `${missingKeys.length} keys`;
const details = noneMissing
? ""
: MDDetailsList({
summary: `${keysLabel} missing`,
content: allMissing
? [
`Create a fresh copy of ${MD.inlineCode(
`en/${fileKey}.json`,
)} to get started.`,
]
: missingKeys,
});
const details = noneMissing
? ""
: MDDetailsList({
summary: `${keysLabel} missing`,
content: allMissing
? [
`Create a fresh copy of ${MD.inlineCode(
`en/${fileKey}.json`,
)} to get started.`,
]
: missingKeys,
});
parts.push(`${fileEntry} ${details}`);
}
parts.push(`${fileEntry} ${details}`);
}
blocks.push(parts.join("\n"));
}
blocks.push(parts.join("\n"));
}
return blocks.join("\n\n");
return blocks.join("\n\n");
}
function createTranslationProgessMarkdown({
missingTranslations,
totalTranslationCounts,
missingTranslations,
totalTranslationCounts,
}: {
missingTranslations: Record<string, Record<string, Array<string>>>;
totalTranslationCounts: Record<string, number>;
missingTranslations: Record<string, Record<string, Array<string>>>;
totalTranslationCounts: Record<string, number>;
}) {
return `
return `
> 🤖 This issue is fully automated, it should always be up-to-date.
# Translation Progress

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,19 @@
/* eslint-disable */
// @ts-nocheck
import head from "./dicts/GearInfoHead.json";
import clothes from "./dicts/GearInfoClothes.json";
import head from "./dicts/GearInfoHead.json";
import shoes from "./dicts/GearInfoShoes.json";
import fs from "node:fs";
import invariant from "~/utils/invariant";
import {
LANG_JSONS_TO_CREATE,
loadLangDicts,
translationJsonFolderName,
LANG_JSONS_TO_CREATE,
loadLangDicts,
translationJsonFolderName,
} from "./utils";
import path from "path";
import { fileURLToPath } from "url";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -28,114 +27,114 @@ const LEAN_SHOES_CODE = "Shs";
// some items have duplicate ID so redundant to have them here many times
// but it's just for clarity
const AVAILABLE_SR_GEAR = [
21010, 21011, 21015, 21013, 21012, 21014, 21012, 21000, 21001, 21002, 21001,
21002, 21001, 21016, 21017, 21018, 21019, 21004, 21002, 21005, 21003, 21002,
21005, 21008, 21020, 21015, 21007, 21021, 21006, 21009,
21010, 21011, 21015, 21013, 21012, 21014, 21012, 21000, 21001, 21002, 21001,
21002, 21001, 21016, 21017, 21018, 21019, 21004, 21002, 21005, 21003, 21002,
21005, 21008, 21020, 21015, 21007, 21021, 21006, 21009,
];
async function main() {
const allGear: Array<{
id: number;
internalName: string;
type: string;
translations: Array<{ language: string; name: string }>;
}> = [];
const langDicts = await loadLangDicts();
const allGear: Array<{
id: number;
internalName: string;
type: string;
translations: Array<{ language: string; name: string }>;
}> = [];
const langDicts = await loadLangDicts();
for (const gear of [...head, ...clothes, ...shoes]) {
if (gear.Season > CURRENT_SEASON || gear.HowToGet === "Impossible") {
continue;
}
for (const gear of [...head, ...clothes, ...shoes]) {
if (gear.Season > CURRENT_SEASON || gear.HowToGet === "Impossible") {
continue;
}
if (gear.__RowId.includes("COP") && !AVAILABLE_SR_GEAR.includes(gear.Id)) {
continue;
}
if (gear.__RowId.includes("COP") && !AVAILABLE_SR_GEAR.includes(gear.Id)) {
continue;
}
const [type, internalName] = gear.__RowId.split("_");
invariant(type);
invariant(internalName);
const [type, internalName] = gear.__RowId.split("_");
invariant(type);
invariant(internalName);
const categoryKey = `CommonMsg/Gear/GearName_${
type === LEAN_CLOTHES_CODE
? "Clothes"
: type === LEAN_SHOES_CODE
? "Shoes"
: "Head"
}`;
const categoryKey = `CommonMsg/Gear/GearName_${
type === LEAN_CLOTHES_CODE
? "Clothes"
: type === LEAN_SHOES_CODE
? "Shoes"
: "Head"
}`;
allGear.push({
id: gear.Id,
type,
internalName,
translations: langDicts.map(([langCode, translations]) => {
const name = translations[categoryKey]?.[internalName];
invariant(name, `Missing translation for ${internalName}`);
allGear.push({
id: gear.Id,
type,
internalName,
translations: langDicts.map(([langCode, translations]) => {
const name = translations[categoryKey]?.[internalName];
invariant(name, `Missing translation for ${internalName}`);
return {
language: langCode,
name,
};
}),
});
}
return {
language: langCode,
name,
};
}),
});
}
allGear.sort((a, b) => a.id - b.id);
allGear.sort((a, b) => a.id - b.id);
fs.writeFileSync(
path.join(OUTPUT_DIR_PATH, "gear.json"),
JSON.stringify(allGear, null, 2),
);
fs.writeFileSync(
path.join(OUTPUT_DIR_PATH, "gear.json"),
JSON.stringify(allGear, null, 2),
);
const headGear = allGear.filter((g) => g.type === LEAN_HEAD_CODE);
const clothesGear = allGear.filter((g) => g.type === LEAN_CLOTHES_CODE);
const shoesGear = allGear.filter((g) => g.type === LEAN_SHOES_CODE);
invariant(headGear.length);
invariant(clothesGear.length);
invariant(shoesGear.length);
const headGear = allGear.filter((g) => g.type === LEAN_HEAD_CODE);
const clothesGear = allGear.filter((g) => g.type === LEAN_CLOTHES_CODE);
const shoesGear = allGear.filter((g) => g.type === LEAN_SHOES_CODE);
invariant(headGear.length);
invariant(clothesGear.length);
invariant(shoesGear.length);
const headIds = headGear.map((w) => w.id);
const clothesIds = clothesGear.map((w) => w.id);
const shoesIds = shoesGear.map((w) => w.id);
const headIds = headGear.map((w) => w.id);
const clothesIds = clothesGear.map((w) => w.id);
const shoesIds = shoesGear.map((w) => w.id);
fs.writeFileSync(
path.join(OUTPUT_DIR_PATH, "head-ids.json"),
JSON.stringify(headIds, null, 2),
);
fs.writeFileSync(
path.join(OUTPUT_DIR_PATH, "clothes-ids.json"),
JSON.stringify(clothesIds, null, 2),
);
fs.writeFileSync(
path.join(OUTPUT_DIR_PATH, "shoes-ids.json"),
JSON.stringify(shoesIds, null, 2),
);
fs.writeFileSync(
path.join(OUTPUT_DIR_PATH, "head-ids.json"),
JSON.stringify(headIds, null, 2),
);
fs.writeFileSync(
path.join(OUTPUT_DIR_PATH, "clothes-ids.json"),
JSON.stringify(clothesIds, null, 2),
);
fs.writeFileSync(
path.join(OUTPUT_DIR_PATH, "shoes-ids.json"),
JSON.stringify(shoesIds, null, 2),
);
for (const langCode of LANG_JSONS_TO_CREATE) {
const translationsMap = Object.fromEntries(
allGear.map((gear) => {
const translation = gear.translations.find(
(t) => t.language === langCode,
)?.name;
invariant(
translation,
`No translation for ${gear.internalName} in ${langCode}`,
);
for (const langCode of LANG_JSONS_TO_CREATE) {
const translationsMap = Object.fromEntries(
allGear.map((gear) => {
const translation = gear.translations.find(
(t) => t.language === langCode,
)?.name;
invariant(
translation,
`No translation for ${gear.internalName} in ${langCode}`,
);
return [`${gear.type.charAt(0).toUpperCase()}_${gear.id}`, translation];
}),
);
return [`${gear.type.charAt(0).toUpperCase()}_${gear.id}`, translation];
}),
);
fs.writeFileSync(
path.join(
__dirname,
"..",
"locales",
translationJsonFolderName(langCode),
`gear.json`,
),
JSON.stringify(translationsMap, null, 2) + "\n",
);
}
fs.writeFileSync(
path.join(
__dirname,
"..",
"locales",
translationJsonFolderName(langCode),
"gear.json",
),
`${JSON.stringify(translationsMap, null, 2)}\n`,
);
}
}
void main();

View File

@@ -1,132 +1,131 @@
/* eslint-disable */
// @ts-nocheck
import {
LANG_JSONS_TO_CREATE,
loadLangDicts,
translationJsonFolderName,
} from "./utils";
import fs from "fs";
import invariant from "~/utils/invariant";
import fs from "node:fs";
import { abilitiesShort } from "~/modules/in-game-lists";
import invariant from "~/utils/invariant";
import {
LANG_JSONS_TO_CREATE,
loadLangDicts,
translationJsonFolderName,
} from "./utils";
import path from "path";
import { fileURLToPath } from "url";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// ⚠️ keep same order as https://github.com/IPLSplatoon/IPLMapGen2/blob/main/data.js
const stages = [
"Scorch Gorge",
"Eeltail Alley",
"Hagglefish Market",
"Undertow Spillway",
"Mincemeat Metalworks",
"Hammerhead Bridge",
"Museum d'Alfonsino",
"Mahi-Mahi Resort",
"Inkblot Art Academy",
"Sturgeon Shipyard",
"MakoMart",
"Wahoo World",
"Flounder Heights",
"Brinewater Springs",
"Manta Maria",
"Um'ami Ruins",
"Humpback Pump Track",
"Barnacle & Dime",
"Crableg Capital",
"Shipshape Cargo Co.",
"Bluefin Depot",
"Robo ROM-en",
"Marlin Airport",
"Lemuria Hub",
"Scorch Gorge",
"Eeltail Alley",
"Hagglefish Market",
"Undertow Spillway",
"Mincemeat Metalworks",
"Hammerhead Bridge",
"Museum d'Alfonsino",
"Mahi-Mahi Resort",
"Inkblot Art Academy",
"Sturgeon Shipyard",
"MakoMart",
"Wahoo World",
"Flounder Heights",
"Brinewater Springs",
"Manta Maria",
"Um'ami Ruins",
"Humpback Pump Track",
"Barnacle & Dime",
"Crableg Capital",
"Shipshape Cargo Co.",
"Bluefin Depot",
"Robo ROM-en",
"Marlin Airport",
"Lemuria Hub",
] as const;
const abilityShortToInternalName = new Map([
["ISM", "MainInk_Save"],
["ISS", "SubInk_Save"],
["IRU", "InkRecovery_Up"],
["RSU", "HumanMove_Up"],
["SSU", "SquidMove_Up"],
["SCU", "SpecialIncrease_Up"],
["SS", "RespawnSpecialGauge_Save"],
["SPU", "SpecialSpec_Up"],
["QR", "RespawnTime_Save"],
["QSJ", "JumpTime_Save"],
["BRU", "SubSpec_Up"],
["RES", "OpInkEffect_Reduction"],
["SRU", "SubEffect_Reduction"],
["IA", "Action_Up"],
["OG", "StartAllUp"],
["LDE", "EndAllUp"],
["T", "MinorityUp"],
["CB", "ComeBack"],
["NS", "SquidMoveSpatter_Reduction"],
["H", "DeathMarking"],
["TI", "ThermalInk"],
["RP", "Exorcist"],
["AD", "ExSkillDouble"],
["SJ", "SuperJumpSign_Hide"],
["OS", "ObjectEffect_Up"],
["DR", "SomersaultLanding"],
["ISM", "MainInk_Save"],
["ISS", "SubInk_Save"],
["IRU", "InkRecovery_Up"],
["RSU", "HumanMove_Up"],
["SSU", "SquidMove_Up"],
["SCU", "SpecialIncrease_Up"],
["SS", "RespawnSpecialGauge_Save"],
["SPU", "SpecialSpec_Up"],
["QR", "RespawnTime_Save"],
["QSJ", "JumpTime_Save"],
["BRU", "SubSpec_Up"],
["RES", "OpInkEffect_Reduction"],
["SRU", "SubEffect_Reduction"],
["IA", "Action_Up"],
["OG", "StartAllUp"],
["LDE", "EndAllUp"],
["T", "MinorityUp"],
["CB", "ComeBack"],
["NS", "SquidMoveSpatter_Reduction"],
["H", "DeathMarking"],
["TI", "ThermalInk"],
["RP", "Exorcist"],
["AD", "ExSkillDouble"],
["SJ", "SuperJumpSign_Hide"],
["OS", "ObjectEffect_Up"],
["DR", "SomersaultLanding"],
]);
async function main() {
const langDicts = await loadLangDicts();
const langDicts = await loadLangDicts();
const englishLangDict = langDicts.find(
([langCode]) => langCode === "EUen",
)?.[1];
invariant(englishLangDict);
const englishLangDict = langDicts.find(
([langCode]) => langCode === "EUen",
)?.[1];
invariant(englishLangDict);
const codeNames = stages.map((stage) => {
const codeName = Object.entries(
englishLangDict["CommonMsg/VS/VSStageName"],
).find(([_key, value]) => value === stage)?.[0];
const codeNames = stages.map((stage) => {
const codeName = Object.entries(
englishLangDict["CommonMsg/VS/VSStageName"],
).find(([_key, value]) => value === stage)?.[0];
invariant(codeName, `Could not find code name for stage ${stage}`);
invariant(codeName, `Could not find code name for stage ${stage}`);
return codeName;
});
return codeName;
});
for (const langCode of LANG_JSONS_TO_CREATE) {
const langDict = langDicts.find(([code]) => code === langCode)?.[1];
invariant(langDict, `Missing translations for ${langCode}`);
for (const langCode of LANG_JSONS_TO_CREATE) {
const langDict = langDicts.find(([code]) => code === langCode)?.[1];
invariant(langDict, `Missing translations for ${langCode}`);
const translationsMap = Object.fromEntries(
stages.map((_, i) => {
const codeName = codeNames[
i
] as keyof (typeof langDict)["CommonMsg/VS/VSStageName"];
invariant(codeName);
const translationsMap = Object.fromEntries(
stages.map((_, i) => {
const codeName = codeNames[
i
] as keyof (typeof langDict)["CommonMsg/VS/VSStageName"];
invariant(codeName);
return [`STAGE_${i}`, langDict["CommonMsg/VS/VSStageName"][codeName]];
}),
);
return [`STAGE_${i}`, langDict["CommonMsg/VS/VSStageName"][codeName]];
}),
);
for (const ability of abilitiesShort) {
const internalName = abilityShortToInternalName.get(ability);
invariant(internalName, `Missing internal name for ${ability}`);
for (const ability of abilitiesShort) {
const internalName = abilityShortToInternalName.get(ability);
invariant(internalName, `Missing internal name for ${ability}`);
const translation = decodeURIComponent(
langDict["CommonMsg/Gear/GearPowerName"][internalName],
);
const translation = decodeURIComponent(
langDict["CommonMsg/Gear/GearPowerName"][internalName],
);
translationsMap[`ABILITY_${ability}`] = translation;
}
translationsMap[`ABILITY_${ability}`] = translation;
}
fs.writeFileSync(
path.join(
__dirname,
"..",
"locales",
translationJsonFolderName(langCode),
`game-misc.json`,
),
JSON.stringify(translationsMap, null, 2) + "\n",
);
}
fs.writeFileSync(
path.join(
__dirname,
"..",
"locales",
translationJsonFolderName(langCode),
"game-misc.json",
),
`${JSON.stringify(translationsMap, null, 2)}\n`,
);
}
}
void main();

View File

@@ -1,92 +1,91 @@
/* eslint-disable */
// @ts-nocheck
import fs from "node:fs";
import { DAMAGE_RECEIVERS } from "~/features/object-damage-calculator/calculator-constants";
import {
mainWeaponIds,
specialWeaponIds,
subWeaponIds,
} from "~/modules/in-game-lists";
import weapons from "./dicts/WeaponInfoMain.json";
import specialWeapons from "./dicts/WeaponInfoSpecial.json";
import subWeapons from "./dicts/WeaponInfoSub.json";
// 1) WeaponInfoMain.json inside dicts
// 2) WeaponInfoSub.json inside dicts
// 3) WeaponInfoSpecial.json inside dicts
// 4) misc/spl__DamageRateInfoConfig.pp__CombinationDataTableData.json
import params from "./dicts/spl__DamageRateInfoConfig.pp__CombinationDataTableData.json";
import weapons from "./dicts/WeaponInfoMain.json";
import subWeapons from "./dicts/WeaponInfoSub.json";
import specialWeapons from "./dicts/WeaponInfoSpecial.json";
import fs from "node:fs";
import {
mainWeaponIds,
specialWeaponIds,
subWeaponIds,
} from "~/modules/in-game-lists";
import { DAMAGE_RECEIVERS } from "~/features/object-damage-calculator/calculator-constants";
import path from "path";
import { fileURLToPath } from "url";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const OUTPUT_DIR_PATH = path.join(__dirname, "output");
const weaponParamsToWeaponIds = (
params: typeof weapons | typeof subWeapons | typeof specialWeapons,
key: string,
params: typeof weapons | typeof subWeapons | typeof specialWeapons,
key: string,
) => {
return params
.filter((param) => {
return (
param.DefaultDamageRateInfoRow === key ||
param.ExtraDamageRateInfoRowSet?.some(
(row) => row.DamageRateInfoRow === key,
)
);
})
.map((weapon) => weapon.Id);
return params
.filter((param) => {
return (
param.DefaultDamageRateInfoRow === key ||
param.ExtraDamageRateInfoRowSet?.some(
(row) => row.DamageRateInfoRow === key,
)
);
})
.map((weapon) => weapon.Id);
};
const result = {};
for (const cell of Object.values(params.CellList)) {
if (!DAMAGE_RECEIVERS.includes(cell.ColumnKey)) continue;
if (!cell.DamageRate) continue;
if (!DAMAGE_RECEIVERS.includes(cell.ColumnKey)) continue;
if (!cell.DamageRate) continue;
if (!result[cell.RowKey]) {
result[cell.RowKey] = {
mainWeaponIds: weaponParamsToWeaponIds(weapons, cell.RowKey).filter(
(id) => mainWeaponIds.includes(id),
),
subWeaponIds: weaponParamsToWeaponIds(subWeapons, cell.RowKey).filter(
(id) => subWeaponIds.includes(id),
),
specialWeaponIds: weaponParamsToWeaponIds(
specialWeapons,
cell.RowKey,
).filter((id) => specialWeaponIds.includes(id)),
rates: [],
};
}
if (!result[cell.RowKey]) {
result[cell.RowKey] = {
mainWeaponIds: weaponParamsToWeaponIds(weapons, cell.RowKey).filter(
(id) => mainWeaponIds.includes(id),
),
subWeaponIds: weaponParamsToWeaponIds(subWeapons, cell.RowKey).filter(
(id) => subWeaponIds.includes(id),
),
specialWeaponIds: weaponParamsToWeaponIds(
specialWeapons,
cell.RowKey,
).filter((id) => specialWeaponIds.includes(id)),
rates: [],
};
}
// if it has applies to no PvP weapons, we don't care about it
if (
result[cell.RowKey].mainWeaponIds.length === 0 &&
result[cell.RowKey].subWeaponIds.length === 0 &&
result[cell.RowKey].specialWeaponIds.length === 0 &&
cell.RowKey !== "ObjectEffect_Up"
) {
result[cell.RowKey] = undefined;
continue;
}
// if it has applies to no PvP weapons, we don't care about it
if (
result[cell.RowKey].mainWeaponIds.length === 0 &&
result[cell.RowKey].subWeaponIds.length === 0 &&
result[cell.RowKey].specialWeaponIds.length === 0 &&
cell.RowKey !== "ObjectEffect_Up"
) {
result[cell.RowKey] = undefined;
continue;
}
result[cell.RowKey].rates.push({
target: cell.ColumnKey,
rate: cell.DamageRate,
});
result[cell.RowKey].rates.push({
target: cell.ColumnKey,
rate: cell.DamageRate,
});
// if it has special damage rates for Splat Brella, add the same value for Recycled Brella
if (cell.ColumnKey === "BulletUmbrellaCanopyNormal") {
result[cell.RowKey].rates.push({
target: "BulletShelterCanopyFocus",
rate: cell.DamageRate,
});
}
// if it has special damage rates for Splat Brella, add the same value for Recycled Brella
if (cell.ColumnKey === "BulletUmbrellaCanopyNormal") {
result[cell.RowKey].rates.push({
target: "BulletShelterCanopyFocus",
rate: cell.DamageRate,
});
}
}
fs.writeFileSync(
path.join(OUTPUT_DIR_PATH, "object-dmg.json"),
JSON.stringify(result, null, 2),
path.join(OUTPUT_DIR_PATH, "object-dmg.json"),
JSON.stringify(result, null, 2),
);

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { db } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const rawId = process.argv[2]?.trim();
@@ -12,17 +12,17 @@ const id = Number(rawId);
invariant(!Number.isNaN(id), "id must be a number");
async function main() {
const owners = await db
.selectFrom("BadgeOwner")
.select(["badgeId"])
.where("BadgeOwner.badgeId", "=", id)
.execute();
const owners = await db
.selectFrom("BadgeOwner")
.select(["badgeId"])
.where("BadgeOwner.badgeId", "=", id)
.execute();
invariant(owners.length === 0, "Badge is still owned by someone");
invariant(owners.length === 0, "Badge is still owned by someone");
await db.deleteFrom("Badge").where("id", "=", id).execute();
await db.deleteFrom("Badge").where("id", "=", id).execute();
console.log("done with deleting the badge");
logger.info("done with deleting the badge");
}
void main();

View File

@@ -1,6 +1,6 @@
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -8,15 +8,15 @@ const __dirname = path.dirname(__filename);
const pathToDbFile = (file) => path.resolve(__dirname, "..", file);
const filesToDeleteIfExists = [
"db.sqlite3",
"db.sqlite3-shm",
"db.sqlite3-wal",
"db.sqlite3",
"db.sqlite3-shm",
"db.sqlite3-wal",
];
for (const file of filesToDeleteIfExists) {
try {
fs.unlinkSync(pathToDbFile(file));
} catch (err) {
// if file doesn't exist err.code = ENOENT is thrown
if (err.code !== "ENOENT") throw err;
}
try {
fs.unlinkSync(pathToDbFile(file));
} catch (err) {
// if file doesn't exist err.code = ENOENT is thrown
if (err.code !== "ENOENT") throw err;
}
}

View File

@@ -1,16 +1,16 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const discordId = process.argv[2]?.trim();
invariant(discordId, "discord id is required (argument 1)");
sql
.prepare(
'delete from "Skill" where "userId" = (select id from "User" where discordId = @discordId)',
)
.run({ discordId });
.prepare(
'delete from "Skill" where "userId" = (select id from "User" where discordId = @discordId)',
)
.run({ discordId });
console.log(`Deleted skill of user with discord id: ${discordId}`);
logger.info(`Deleted skill of user with discord id: ${discordId}`);

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const rawTournamentTeamId = process.argv[2]?.trim();
@@ -10,8 +10,8 @@ invariant(rawTournamentTeamId, "tournament team is required (argument 1)");
const tournamentTeamId = Number(rawTournamentTeamId);
invariant(
!Number.isNaN(tournamentTeamId),
"tournament team id must be a number",
!Number.isNaN(tournamentTeamId),
"tournament team id must be a number",
);
const deleteMapPoolStm = sql.prepare(/*sql*/ `
@@ -21,4 +21,4 @@ const deleteMapPoolStm = sql.prepare(/*sql*/ `
deleteMapPoolStm.run({ tournamentTeamId });
console.log(`Deleted map pool of tournament team with id: ${tournamentTeamId}`);
logger.info(`Deleted map pool of tournament team with id: ${tournamentTeamId}`);

View File

@@ -1,14 +1,14 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const discordId = process.argv[2]?.trim();
invariant(discordId, "discord id is required (argument 1)");
sql
.prepare('delete from "User" where discordId = @discordId')
.run({ discordId });
.prepare('delete from "User" where discordId = @discordId')
.run({ discordId });
console.log(`Deleted user with discord id: ${discordId}`);
logger.info(`Deleted user with discord id: ${discordId}`);

View File

@@ -1,7 +1,7 @@
import "dotenv/config";
import invariant from "~/utils/invariant";
import { db } from "~/db/sql";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const id = Number(process.argv[2]?.trim());
@@ -10,15 +10,15 @@ invariant(id, "team id is required (argument 1)");
invariant(Number.isInteger(id), "team id must be an integer");
async function main() {
await db
.updateTable("AllTeam")
.set({
deletedAt: dateToDatabaseTimestamp(new Date()),
})
.where("id", "=", id)
.execute();
await db
.updateTable("AllTeam")
.set({
deletedAt: dateToDatabaseTimestamp(new Date()),
})
.where("id", "=", id)
.execute();
logger.info(`Disbanded team with id: ${id}`);
logger.info(`Disbanded team with id: ${id}`);
}
void main();

View File

@@ -1,6 +1,6 @@
import "dotenv/config";
import invariant from "~/utils/invariant";
import { db } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const friendCode = process.argv[2]?.trim();
@@ -8,31 +8,31 @@ const friendCode = process.argv[2]?.trim();
invariant(friendCode, "friend code is required (argument 1)");
async function main() {
const allFcs = await db
.selectFrom("UserFriendCode")
.innerJoin("User", "User.id", "UserFriendCode.userId")
.select([
"UserFriendCode.friendCode",
"User.id as userId",
"User.discordId",
"User.discordUniqueName",
])
.orderBy("UserFriendCode.createdAt", "asc")
.whereRef("User.id", "=", "UserFriendCode.submitterUserId")
.execute();
const allFcs = await db
.selectFrom("UserFriendCode")
.innerJoin("User", "User.id", "UserFriendCode.userId")
.select([
"UserFriendCode.friendCode",
"User.id as userId",
"User.discordId",
"User.discordUniqueName",
])
.orderBy("UserFriendCode.createdAt", "asc")
.whereRef("User.id", "=", "UserFriendCode.submitterUserId")
.execute();
const matches = allFcs.filter((fc) => fc.friendCode === friendCode);
const matches = allFcs.filter((fc) => fc.friendCode === friendCode);
if (matches.length === 0) {
logger.info("No matches found");
return;
}
if (matches.length === 0) {
logger.info("No matches found");
return;
}
for (const match of matches) {
logger.info(
`${match.friendCode} - ${match.discordUniqueName} - ${match.discordId}`,
);
}
for (const match of matches) {
logger.info(
`${match.friendCode} - ${match.discordUniqueName} - ${match.discordId}`,
);
}
}
void main();

View File

@@ -1,55 +0,0 @@
/* eslint-disable */
// @ts-nocheck
// This script generates a CSV file for main weapons and some of their attributes.
// To run the script, run "node scripts/generate-weapon-csv.js" from the root of the repository folder
const weaponParams = require("../app/modules/analyzer/weapon-params.json");
const weaponsJsonEn = require("../public/locales/en/weapons.json");
const fs = require("fs");
const outFilePath = "output/main-weapon-table.csv";
function main() {
// Create data structure where we can search by weaponId as the key
const mainWeaponsJson = Object.keys(weaponsJsonEn)
.filter((key) => key.includes("MAIN"))
.reduce((obj, key) => {
weaponId = key.replace("MAIN_", "");
obj[weaponId] = weaponsJsonEn[key];
return obj;
}, {});
const mainWeaponsParams = weaponParams.mainWeapons;
const columnNames = [
"Weapon Name",
"Special Points Required",
"Weapon Speed Type",
"Move Speed",
];
const columnNamesForCsv = columnNames.join(",") + "\r\n";
fs.writeFileSync(outFilePath, columnNamesForCsv);
const columnAttributes = ["SpecialPoint", "WeaponSpeedType", "MoveSpeed"];
// Construct each row data string for each main weapon, then append them to the CSV file
Object.entries(mainWeaponsParams).forEach((mainWeapon) => {
const [weaponId, weaponAttributes] = mainWeapon;
const weaponName = mainWeaponsJson[weaponId];
let rowData = weaponName + ",";
for (const attrKey of columnAttributes) {
const attribute = weaponAttributes[attrKey] ?? "";
rowData += attribute + ",";
}
rowData += "\r\n";
fs.appendFileSync(outFilePath, rowData, (err) => {
if (err) throw err;
});
});
console.log(`Table was generated in file '${outFilePath}'`);
}
void main();

View File

@@ -1,353 +1,362 @@
/* eslint-disable */
// @ts-nocheck
export {};
import { logger } from "~/utils/logger";
interface HSL {
h: number;
s: number;
l: number;
h: number;
s: number;
l: number;
}
class Color {
public r: number;
public g: number;
public b: number;
constructor(r: number, g: number, b: number) {
this.r = this.clamp(r);
this.g = this.clamp(g);
this.b = this.clamp(b);
}
public r: number;
public g: number;
public b: number;
constructor(r: number, g: number, b: number) {
this.r = this.clamp(r);
this.g = this.clamp(g);
this.b = this.clamp(b);
}
toString() {
return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(
this.b,
)})`;
}
toString() {
return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(
this.b,
)})`;
}
set(r: number, g: number, b: number) {
this.r = this.clamp(r);
this.g = this.clamp(g);
this.b = this.clamp(b);
}
set(r: number, g: number, b: number) {
this.r = this.clamp(r);
this.g = this.clamp(g);
this.b = this.clamp(b);
}
hueRotate(angle = 0) {
angle = (angle / 180) * Math.PI;
const sin = Math.sin(angle);
const cos = Math.cos(angle);
hueRotate(angle = 0) {
// biome-ignore lint/style/noParameterAssign: biome migration
angle = (angle / 180) * Math.PI;
const sin = Math.sin(angle);
const cos = Math.cos(angle);
this.multiply([
0.213 + cos * 0.787 - sin * 0.213,
0.715 - cos * 0.715 - sin * 0.715,
0.072 - cos * 0.072 + sin * 0.928,
0.213 - cos * 0.213 + sin * 0.143,
0.715 + cos * 0.285 + sin * 0.14,
0.072 - cos * 0.072 - sin * 0.283,
0.213 - cos * 0.213 - sin * 0.787,
0.715 - cos * 0.715 + sin * 0.715,
0.072 + cos * 0.928 + sin * 0.072,
]);
}
this.multiply([
0.213 + cos * 0.787 - sin * 0.213,
0.715 - cos * 0.715 - sin * 0.715,
0.072 - cos * 0.072 + sin * 0.928,
0.213 - cos * 0.213 + sin * 0.143,
0.715 + cos * 0.285 + sin * 0.14,
0.072 - cos * 0.072 - sin * 0.283,
0.213 - cos * 0.213 - sin * 0.787,
0.715 - cos * 0.715 + sin * 0.715,
0.072 + cos * 0.928 + sin * 0.072,
]);
}
grayscale(value = 1) {
this.multiply([
0.2126 + 0.7874 * (1 - value),
0.7152 - 0.7152 * (1 - value),
0.0722 - 0.0722 * (1 - value),
0.2126 - 0.2126 * (1 - value),
0.7152 + 0.2848 * (1 - value),
0.0722 - 0.0722 * (1 - value),
0.2126 - 0.2126 * (1 - value),
0.7152 - 0.7152 * (1 - value),
0.0722 + 0.9278 * (1 - value),
]);
}
grayscale(value = 1) {
this.multiply([
0.2126 + 0.7874 * (1 - value),
0.7152 - 0.7152 * (1 - value),
0.0722 - 0.0722 * (1 - value),
0.2126 - 0.2126 * (1 - value),
0.7152 + 0.2848 * (1 - value),
0.0722 - 0.0722 * (1 - value),
0.2126 - 0.2126 * (1 - value),
0.7152 - 0.7152 * (1 - value),
0.0722 + 0.9278 * (1 - value),
]);
}
sepia(value = 1) {
this.multiply([
0.393 + 0.607 * (1 - value),
0.769 - 0.769 * (1 - value),
0.189 - 0.189 * (1 - value),
0.349 - 0.349 * (1 - value),
0.686 + 0.314 * (1 - value),
0.168 - 0.168 * (1 - value),
0.272 - 0.272 * (1 - value),
0.534 - 0.534 * (1 - value),
0.131 + 0.869 * (1 - value),
]);
}
sepia(value = 1) {
this.multiply([
0.393 + 0.607 * (1 - value),
0.769 - 0.769 * (1 - value),
0.189 - 0.189 * (1 - value),
0.349 - 0.349 * (1 - value),
0.686 + 0.314 * (1 - value),
0.168 - 0.168 * (1 - value),
0.272 - 0.272 * (1 - value),
0.534 - 0.534 * (1 - value),
0.131 + 0.869 * (1 - value),
]);
}
saturate(value = 1) {
this.multiply([
0.213 + 0.787 * value,
0.715 - 0.715 * value,
0.072 - 0.072 * value,
0.213 - 0.213 * value,
0.715 + 0.285 * value,
0.072 - 0.072 * value,
0.213 - 0.213 * value,
0.715 - 0.715 * value,
0.072 + 0.928 * value,
]);
}
saturate(value = 1) {
this.multiply([
0.213 + 0.787 * value,
0.715 - 0.715 * value,
0.072 - 0.072 * value,
0.213 - 0.213 * value,
0.715 + 0.285 * value,
0.072 - 0.072 * value,
0.213 - 0.213 * value,
0.715 - 0.715 * value,
0.072 + 0.928 * value,
]);
}
multiply(matrix: any) {
const newR = this.clamp(
this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2],
);
const newG = this.clamp(
this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5],
);
const newB = this.clamp(
this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8],
);
this.r = newR;
this.g = newG;
this.b = newB;
}
multiply(matrix: any) {
const newR = this.clamp(
this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2],
);
const newG = this.clamp(
this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5],
);
const newB = this.clamp(
this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8],
);
this.r = newR;
this.g = newG;
this.b = newB;
}
brightness(value = 1) {
this.linear(value);
}
contrast(value = 1) {
this.linear(value, -(0.5 * value) + 0.5);
}
brightness(value = 1) {
this.linear(value);
}
contrast(value = 1) {
this.linear(value, -(0.5 * value) + 0.5);
}
linear(slope = 1, intercept = 0) {
this.r = this.clamp(this.r * slope + intercept * 255);
this.g = this.clamp(this.g * slope + intercept * 255);
this.b = this.clamp(this.b * slope + intercept * 255);
}
linear(slope = 1, intercept = 0) {
this.r = this.clamp(this.r * slope + intercept * 255);
this.g = this.clamp(this.g * slope + intercept * 255);
this.b = this.clamp(this.b * slope + intercept * 255);
}
invert(value = 1) {
this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255);
this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255);
this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255);
}
invert(value = 1) {
this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255);
this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255);
this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255);
}
hsl(): HSL {
// Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA.
const r = this.r / 255;
const g = this.g / 255;
const b = this.b / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
hsl(): HSL {
// Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA.
const r = this.r / 255;
const g = this.g / 255;
const b = this.b / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
let s = 0;
let l = (max + min) / 2;
let h = 0;
let s = 0;
const l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return {
h: h * 100,
s: s * 100,
l: l * 100,
};
}
return {
h: h * 100,
s: s * 100,
l: l * 100,
};
}
clamp(value: number): number {
if (value > 255) {
value = 255;
} else if (value < 0) {
value = 0;
}
return value;
}
clamp(value: number): number {
if (value > 255) {
// biome-ignore lint/style/noParameterAssign: biome migration
value = 255;
} else if (value < 0) {
// biome-ignore lint/style/noParameterAssign: biome migration
value = 0;
}
return value;
}
}
interface Solution {
loss: number;
values: number[];
loss: number;
values: number[];
}
class Solver {
private target: Color;
private targetHSL: HSL;
private reusedColor: Color;
constructor(target: Color) {
this.target = target;
this.targetHSL = target.hsl();
this.reusedColor = new Color(0, 0, 0);
}
private target: Color;
private targetHSL: HSL;
private reusedColor: Color;
constructor(target: Color) {
this.target = target;
this.targetHSL = target.hsl();
this.reusedColor = new Color(0, 0, 0);
}
solve() {
const result = this.solveNarrow(this.solveWide());
return {
values: result.values,
loss: result.loss,
filter: this.css(result.values),
};
}
solve() {
const result = this.solveNarrow(this.solveWide());
return {
values: result.values,
loss: result.loss,
filter: this.css(result.values),
};
}
solveWide(): Solution {
const A = 5;
const c = 15;
const a = [60, 180, 18000, 600, 1.2, 1.2];
solveWide(): Solution {
const A = 5;
const c = 15;
const a = [60, 180, 18000, 600, 1.2, 1.2];
let best = { loss: Infinity, values: [] as number[] };
for (let i = 0; best.loss > 25 && i < 3; i++) {
const initial = [50, 20, 3750, 50, 100, 100];
const result = this.spsa(A, a, c, initial, 1000);
if (result.loss < best.loss) {
best = result;
}
}
return best;
}
let best = { loss: Number.POSITIVE_INFINITY, values: [] as number[] };
for (let i = 0; best.loss > 25 && i < 3; i++) {
const initial = [50, 20, 3750, 50, 100, 100];
const result = this.spsa(A, a, c, initial, 1000);
if (result.loss < best.loss) {
best = result;
}
}
return best;
}
solveNarrow(wide: Solution) {
const A = wide.loss;
const c = 2;
const A1 = A + 1;
const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1];
return this.spsa(A, a, c, wide.values, 500);
}
solveNarrow(wide: Solution) {
const A = wide.loss;
const c = 2;
const A1 = A + 1;
const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1];
return this.spsa(A, a, c, wide.values, 500);
}
spsa(
A: number,
a: number[],
c: number,
values: number[],
iters: number,
): Solution {
const alpha = 1;
const gamma = 0.16666666666666666;
spsa(
A: number,
a: number[],
c: number,
values: number[],
iters: number,
): Solution {
const alpha = 1;
const gamma = 0.16666666666666666;
let best = [] as number[];
let bestLoss = Infinity;
const deltas = new Array(6);
const highArgs = new Array(6);
const lowArgs = new Array(6);
let best = [] as number[];
let bestLoss = Number.POSITIVE_INFINITY;
const deltas = new Array(6);
const highArgs = new Array(6);
const lowArgs = new Array(6);
for (let k = 0; k < iters; k++) {
const ck = c / Math.pow(k + 1, gamma);
for (let i = 0; i < 6; i++) {
deltas[i] = Math.random() > 0.5 ? 1 : -1;
highArgs[i] = values[i] + ck * deltas[i];
lowArgs[i] = values[i] - ck * deltas[i];
}
for (let k = 0; k < iters; k++) {
// biome-ignore lint/style/useExponentiationOperator: biome migration
const ck = c / Math.pow(k + 1, gamma);
for (let i = 0; i < 6; i++) {
deltas[i] = Math.random() > 0.5 ? 1 : -1;
highArgs[i] = values[i] + ck * deltas[i];
lowArgs[i] = values[i] - ck * deltas[i];
}
const lossDiff = this.loss(highArgs) - this.loss(lowArgs);
for (let i = 0; i < 6; i++) {
const g = (lossDiff / (2 * ck)) * deltas[i];
const ak = a[i] / Math.pow(A + k + 1, alpha);
values[i] = fix(values[i] - ak * g, i);
}
const lossDiff = this.loss(highArgs) - this.loss(lowArgs);
for (let i = 0; i < 6; i++) {
const g = (lossDiff / (2 * ck)) * deltas[i];
// biome-ignore lint/style/useExponentiationOperator: biome migration
const ak = a[i] / Math.pow(A + k + 1, alpha);
values[i] = fix(values[i] - ak * g, i);
}
const loss = this.loss(values);
if (loss < bestLoss) {
best = values.slice(0);
bestLoss = loss;
}
}
return { values: best, loss: bestLoss };
const loss = this.loss(values);
if (loss < bestLoss) {
best = values.slice(0);
bestLoss = loss;
}
}
return { values: best, loss: bestLoss };
function fix(value: number, idx: number): number {
let max = 100;
if (idx === 2 /* saturate */) {
max = 7500;
} else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) {
max = 200;
}
function fix(value: number, idx: number): number {
let max = 100;
if (idx === 2 /* saturate */) {
max = 7500;
} else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) {
max = 200;
}
if (idx === 3 /* hue-rotate */) {
if (value > max) {
value %= max;
} else if (value < 0) {
value = max + (value % max);
}
} else if (value < 0) {
value = 0;
} else if (value > max) {
value = max;
}
return value;
}
}
if (idx === 3 /* hue-rotate */) {
if (value > max) {
// biome-ignore lint/style/noParameterAssign: biome migration
value %= max;
} else if (value < 0) {
// biome-ignore lint/style/noParameterAssign: biome migration
value = max + (value % max);
}
} else if (value < 0) {
// biome-ignore lint/style/noParameterAssign: biome migration
value = 0;
} else if (value > max) {
// biome-ignore lint/style/noParameterAssign: biome migration
value = max;
}
return value;
}
}
loss(filters: number[]) {
// Argument is array of percentages.
const color = this.reusedColor;
color.set(0, 0, 0);
loss(filters: number[]) {
// Argument is array of percentages.
const color = this.reusedColor;
color.set(0, 0, 0);
color.invert(filters[0] / 100);
color.sepia(filters[1] / 100);
color.saturate(filters[2] / 100);
color.hueRotate(filters[3] * 3.6);
color.brightness(filters[4] / 100);
color.contrast(filters[5] / 100);
color.invert(filters[0] / 100);
color.sepia(filters[1] / 100);
color.saturate(filters[2] / 100);
color.hueRotate(filters[3] * 3.6);
color.brightness(filters[4] / 100);
color.contrast(filters[5] / 100);
const colorHSL = color.hsl();
return (
Math.abs(color.r - this.target.r) +
Math.abs(color.g - this.target.g) +
Math.abs(color.b - this.target.b) +
Math.abs(colorHSL.h - this.targetHSL.h) +
Math.abs(colorHSL.s - this.targetHSL.s) +
Math.abs(colorHSL.l - this.targetHSL.l)
);
}
const colorHSL = color.hsl();
return (
Math.abs(color.r - this.target.r) +
Math.abs(color.g - this.target.g) +
Math.abs(color.b - this.target.b) +
Math.abs(colorHSL.h - this.targetHSL.h) +
Math.abs(colorHSL.s - this.targetHSL.s) +
Math.abs(colorHSL.l - this.targetHSL.l)
);
}
css(filters: number[]) {
function fmt(idx: number, multiplier = 1) {
return Math.round(filters[idx] * multiplier);
}
return `invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(
2,
)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(
5,
)}%)`;
}
css(filters: number[]) {
function fmt(idx: number, multiplier = 1) {
return Math.round(filters[idx] * multiplier);
}
return `invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(
2,
)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(
5,
)}%)`;
}
}
type RGB = [number, number, number];
function hexToRgb(hex: string): RGB {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, (_m, r, g, b) => {
return r + r + g + g + b + b;
});
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
// biome-ignore lint/style/noParameterAssign: biome migration
hex = hex.replace(shorthandRegex, (_m, r, g, b) => {
return r + r + g + g + b + b;
});
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (result) {
return [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16),
];
}
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (result) {
return [
Number.parseInt(result[1], 16),
Number.parseInt(result[2], 16),
Number.parseInt(result[3], 16),
];
}
throw new Error("Error parsing hex: " + hex);
throw new Error(`Error parsing hex: ${hex}`);
}
function hexToFilter(hex: string) {
let rgb = [255, 255, 255];
try {
rgb = hexToRgb(hex);
} catch (e) {}
const color = new Color(rgb[0], rgb[1], rgb[2]);
const solver = new Solver(color);
const result = solver.solve();
console.log(" --- RESULT ---");
console.log(result.filter);
let rgb = [255, 255, 255];
try {
rgb = hexToRgb(hex);
} catch (e) {}
const color = new Color(rgb[0], rgb[1], rgb[2]);
const solver = new Solver(color);
const result = solver.solve();
logger.info(" --- RESULT ---");
logger.info(result.filter);
}
hexToFilter(process.argv[2]?.trim());

View File

@@ -1,126 +1,126 @@
/* eslint-disable no-console */
import "dotenv/config";
import { db } from "~/db/sql";
import { BANNED_MAPS } from "~/features/sendouq-settings/banned-maps";
import type { ModeShort, StageId } from "~/modules/in-game-lists";
import { modesShort, stageIds } from "~/modules/in-game-lists";
import names from "../locales/en/game-misc.json";
import {
databaseTimestampToDate,
dateToDatabaseTimestamp,
databaseTimestampToDate,
dateToDatabaseTimestamp,
} from "~/utils/dates";
import { BANNED_MAPS } from "~/features/sendouq-settings/banned-maps";
import { logger } from "~/utils/logger";
import names from "../locales/en/game-misc.json";
const SEASON_2_START = new Date("2023-12-04T17:00:00.000Z");
async function main() {
const appearance = await db
.selectFrom("GroupMatchMap")
.innerJoin("GroupMatch", "GroupMatchMap.matchId", "GroupMatch.id")
.select(({ fn }) => [
"GroupMatchMap.mode",
"GroupMatchMap.stageId",
fn.countAll<number>().as("count"),
])
.groupBy(["GroupMatchMap.stageId", "GroupMatchMap.mode"])
.where("GroupMatch.createdAt", ">", dateToDatabaseTimestamp(SEASON_2_START))
.execute();
const appearance = await db
.selectFrom("GroupMatchMap")
.innerJoin("GroupMatch", "GroupMatchMap.matchId", "GroupMatch.id")
.select(({ fn }) => [
"GroupMatchMap.mode",
"GroupMatchMap.stageId",
fn.countAll<number>().as("count"),
])
.groupBy(["GroupMatchMap.stageId", "GroupMatchMap.mode"])
.where("GroupMatch.createdAt", ">", dateToDatabaseTimestamp(SEASON_2_START))
.execute();
const usage: Record<
ModeShort | "ALL",
{ stageId: StageId; count: number }[]
> = {
TW: [],
SZ: [],
TC: [],
RM: [],
CB: [],
ALL: [],
};
const usage: Record<
ModeShort | "ALL",
{ stageId: StageId; count: number }[]
> = {
TW: [],
SZ: [],
TC: [],
RM: [],
CB: [],
ALL: [],
};
const ageRow = await db
.selectFrom("Build")
.select((eb) => eb.fn.max("Build.updatedAt").as("age"))
.executeTakeFirstOrThrow();
const ageRow = await db
.selectFrom("Build")
.select((eb) => eb.fn.max("Build.updatedAt").as("age"))
.executeTakeFirstOrThrow();
const dbAgeDate = databaseTimestampToDate(ageRow.age);
const dbAgeDate = databaseTimestampToDate(ageRow.age);
for (const mode of modesShort) {
for (const stageId of stageIds) {
const count =
appearance.find((row) => row.stageId === stageId && row.mode === mode)
?.count ?? 0;
for (const mode of modesShort) {
for (const stageId of stageIds) {
const count =
appearance.find((row) => row.stageId === stageId && row.mode === mode)
?.count ?? 0;
usage[mode].push({
stageId,
count,
});
usage[mode].push({
stageId,
count,
});
const existingAllCount = usage.ALL.find((row) => row.stageId === stageId);
const existingAllCount = usage.ALL.find((row) => row.stageId === stageId);
if (!existingAllCount) {
usage.ALL.push({
stageId,
count,
});
} else {
existingAllCount.count += count;
}
}
if (!existingAllCount) {
usage.ALL.push({
stageId,
count,
});
} else {
existingAllCount.count += count;
}
}
usage[mode].sort((a, b) => b.count - a.count);
}
usage[mode].sort((a, b) => b.count - a.count);
}
usage.ALL.sort((a, b) => b.count - a.count);
usage.ALL.sort((a, b) => b.count - a.count);
console.log(`DB Age: ${dbAgeDate.toISOString()}\n`);
logger.info(`DB Age: ${dbAgeDate.toISOString()}\n`);
// all modes
// all modes
console.log("All");
let banCount = 0;
for (const [i, { stageId, count }] of usage["ALL"].entries()) {
const name = names[`STAGE_${stageId}`];
logger.info("All");
let banCount = 0;
for (const [i, { stageId, count }] of usage.ALL.entries()) {
const name = names[`STAGE_${stageId}`];
const partlyBanned = Object.values(BANNED_MAPS).some((arr) =>
arr.includes(stageId as any),
);
const partlyBanned = Object.values(BANNED_MAPS).some((arr) =>
arr.includes(stageId as any),
);
if (partlyBanned) banCount++;
if (partlyBanned) banCount++;
console.log(
`${i < 9 ? " " : ""}${i + 1}) ${
partlyBanned ? "🔴" : " "
} ${name}: ${count}`,
);
}
logger.info(
`${i < 9 ? " " : ""}${i + 1}) ${
partlyBanned ? "🔴" : " "
} ${name}: ${count}`,
);
}
console.log("Banned maps (at least one mode): " + banCount);
console.log();
logger.info(`Banned maps (at least one mode): ${banCount}`);
logger.info();
// modes
for (const mode of modesShort) {
// if (usage[mode].every((e) => e.count === 0)) continue;
// modes
for (const mode of modesShort) {
// if (usage[mode].every((e) => e.count === 0)) continue;
console.log(mode);
let banCount = 0;
for (const [i, { stageId, count }] of usage[mode].entries()) {
const name = names[`STAGE_${stageId}`];
logger.info(mode);
let banCount = 0;
for (const [i, { stageId, count }] of usage[mode].entries()) {
const name = names[`STAGE_${stageId}`];
const isBanned = BANNED_MAPS[mode].includes(stageId);
if (isBanned) banCount++;
const isBanned = BANNED_MAPS[mode].includes(stageId);
if (isBanned) banCount++;
console.log(
`${i < 9 ? " " : ""}${i + 1}) ${
isBanned ? "❌" : " "
} ${name}: ${count}`,
);
}
logger.info(
`${i < 9 ? " " : ""}${i + 1}) ${
isBanned ? "❌" : " "
} ${name}: ${count}`,
);
}
console.log("Banned maps: " + banCount);
console.log(
Object.values(usage[mode]).reduce((acc, cur) => acc + cur.count, 0),
);
}
logger.info(`Banned maps: ${banCount}`);
logger.info(
Object.values(usage[mode]).reduce((acc, cur) => acc + cur.count, 0),
);
}
}
void main();

View File

@@ -1,108 +1,108 @@
/* eslint-disable no-console */
import "dotenv/config";
import { db } from "~/db/sql";
import { BANNED_MAPS } from "~/features/sendouq-settings/banned-maps";
import type { ModeShort, StageId } from "~/modules/in-game-lists";
import { modesShort, stageIds } from "~/modules/in-game-lists";
import names from "../locales/en/game-misc.json";
import { databaseTimestampToDate } from "~/utils/dates";
import { logger } from "~/utils/logger";
import { cutToNDecimalPlaces } from "~/utils/number";
import { BANNED_MAPS } from "~/features/sendouq-settings/banned-maps";
import names from "../locales/en/game-misc.json";
async function main() {
const appearance = await db
.selectFrom("MapPoolMap")
.select(({ fn }) => [
"MapPoolMap.stageId",
"MapPoolMap.mode",
fn.countAll<number>().as("count"),
])
.where("MapPoolMap.calendarEventId", "is not", null)
.groupBy(["MapPoolMap.stageId", "MapPoolMap.mode"])
.execute();
const appearance = await db
.selectFrom("MapPoolMap")
.select(({ fn }) => [
"MapPoolMap.stageId",
"MapPoolMap.mode",
fn.countAll<number>().as("count"),
])
.where("MapPoolMap.calendarEventId", "is not", null)
.groupBy(["MapPoolMap.stageId", "MapPoolMap.mode"])
.execute();
const usage: Record<
ModeShort,
{ stageId: StageId; count: number; relativeCount: number }[]
> = {
TW: [],
SZ: [],
TC: [],
RM: [],
CB: [],
};
const usage: Record<
ModeShort,
{ stageId: StageId; count: number; relativeCount: number }[]
> = {
TW: [],
SZ: [],
TC: [],
RM: [],
CB: [],
};
const ageRow = await db
.selectFrom("Build")
.select((eb) => eb.fn.max("Build.updatedAt").as("age"))
.executeTakeFirstOrThrow();
const ageRow = await db
.selectFrom("Build")
.select((eb) => eb.fn.max("Build.updatedAt").as("age"))
.executeTakeFirstOrThrow();
const dbAgeDate = databaseTimestampToDate(ageRow.age);
const dbAgeDate = databaseTimestampToDate(ageRow.age);
for (const mode of modesShort) {
for (const stageId of stageIds) {
const count =
appearance.find((row) => row.stageId === stageId && row.mode === mode)
?.count ?? 0;
for (const mode of modesShort) {
for (const stageId of stageIds) {
const count =
appearance.find((row) => row.stageId === stageId && row.mode === mode)
?.count ?? 0;
const firstAppear = await db
.selectFrom("MapPoolMap")
.innerJoin(
"CalendarEvent",
"MapPoolMap.calendarEventId",
"CalendarEvent.id",
)
.innerJoin(
"CalendarEventDate",
"CalendarEvent.id",
"CalendarEventDate.eventId",
)
.select((eb) =>
eb.fn.min("CalendarEventDate.startTime").as("firstAppear"),
)
.executeTakeFirst();
const firstAppear = await db
.selectFrom("MapPoolMap")
.innerJoin(
"CalendarEvent",
"MapPoolMap.calendarEventId",
"CalendarEvent.id",
)
.innerJoin(
"CalendarEventDate",
"CalendarEvent.id",
"CalendarEventDate.eventId",
)
.select((eb) =>
eb.fn.min("CalendarEventDate.startTime").as("firstAppear"),
)
.executeTakeFirst();
const firstAppearDate = firstAppear
? databaseTimestampToDate(firstAppear.firstAppear)
: null;
const firstAppearDate = firstAppear
? databaseTimestampToDate(firstAppear.firstAppear)
: null;
const datesSinceFirstAppear = firstAppearDate
? Math.floor((dbAgeDate.getTime() - firstAppearDate.getTime()) / 864e5)
: null;
const datesSinceFirstAppear = firstAppearDate
? Math.floor((dbAgeDate.getTime() - firstAppearDate.getTime()) / 864e5)
: null;
usage[mode].push({
stageId,
count,
relativeCount: datesSinceFirstAppear
? cutToNDecimalPlaces((count / datesSinceFirstAppear) * 30, 3)
: 0,
});
}
usage[mode].push({
stageId,
count,
relativeCount: datesSinceFirstAppear
? cutToNDecimalPlaces((count / datesSinceFirstAppear) * 30, 3)
: 0,
});
}
usage[mode].sort((a, b) => b.relativeCount - a.relativeCount);
}
usage[mode].sort((a, b) => b.relativeCount - a.relativeCount);
}
console.log(`DB Age: ${dbAgeDate.toISOString()}\n`);
for (const mode of modesShort) {
console.log(mode);
let banCount = 0;
for (const [i, { stageId, count, relativeCount }] of usage[
mode
].entries()) {
const name = names[`STAGE_${stageId}`];
logger.info(`DB Age: ${dbAgeDate.toISOString()}\n`);
for (const mode of modesShort) {
logger.info(mode);
let banCount = 0;
for (const [i, { stageId, count, relativeCount }] of usage[
mode
].entries()) {
const name = names[`STAGE_${stageId}`];
const isBanned = BANNED_MAPS[mode].includes(stageId);
if (isBanned) banCount++;
const isBanned = BANNED_MAPS[mode].includes(stageId);
if (isBanned) banCount++;
console.log(
`${i < 9 ? " " : ""}${i + 1}) ${
isBanned ? "❌" : " "
} ${name}: ${relativeCount} (${count})`,
);
}
logger.info(
`${i < 9 ? " " : ""}${i + 1}) ${
isBanned ? "❌" : " "
} ${name}: ${relativeCount} (${count})`,
);
}
console.log("Banned maps: " + banCount);
console.log();
}
logger.info(`Banned maps: ${banCount}`);
logger.info();
}
}
void main();

View File

@@ -1,8 +1,8 @@
import "dotenv/config";
import invariant from "~/utils/invariant";
import { db } from "~/db/sql";
import { currentSeason as _currentSeason } from "~/features/mmr/season";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const discordId = process.argv[2]?.trim();
@@ -10,56 +10,56 @@ const discordId = process.argv[2]?.trim();
invariant(discordId, "discord id is required (argument 1)");
async function main() {
const currentSeason = _currentSeason(new Date());
if (!currentSeason) {
logger.info("No current season found");
return;
}
const currentSeason = _currentSeason(new Date());
if (!currentSeason) {
logger.info("No current season found");
return;
}
const user = await db
.selectFrom("User")
.select(["User.id"])
.where("User.discordId", "=", discordId)
.executeTakeFirstOrThrow();
const user = await db
.selectFrom("User")
.select(["User.id"])
.where("User.discordId", "=", discordId)
.executeTakeFirstOrThrow();
const groupMatchMaps = await db
.selectFrom("GroupMember")
.innerJoin("Group", "Group.id", "GroupMember.groupId")
.innerJoin("GroupMatch", (join) =>
join.on((eb) =>
eb.or([
eb("GroupMatch.alphaGroupId", "=", eb.ref("Group.id")),
eb("GroupMatch.bravoGroupId", "=", eb.ref("Group.id")),
]),
),
)
.innerJoin("GroupMatchMap", "GroupMatchMap.matchId", "GroupMatch.id")
.select("GroupMatchMap.id")
.where(
"GroupMatch.createdAt",
">",
dateToDatabaseTimestamp(currentSeason.starts),
)
.where(
"GroupMatch.createdAt",
"<",
dateToDatabaseTimestamp(currentSeason.ends),
)
.where("GroupMember.userId", "=", user.id)
.where("GroupMatchMap.winnerGroupId", "is not", null)
.execute();
const groupMatchMaps = await db
.selectFrom("GroupMember")
.innerJoin("Group", "Group.id", "GroupMember.groupId")
.innerJoin("GroupMatch", (join) =>
join.on((eb) =>
eb.or([
eb("GroupMatch.alphaGroupId", "=", eb.ref("Group.id")),
eb("GroupMatch.bravoGroupId", "=", eb.ref("Group.id")),
]),
),
)
.innerJoin("GroupMatchMap", "GroupMatchMap.matchId", "GroupMatch.id")
.select("GroupMatchMap.id")
.where(
"GroupMatch.createdAt",
">",
dateToDatabaseTimestamp(currentSeason.starts),
)
.where(
"GroupMatch.createdAt",
"<",
dateToDatabaseTimestamp(currentSeason.ends),
)
.where("GroupMember.userId", "=", user.id)
.where("GroupMatchMap.winnerGroupId", "is not", null)
.execute();
const groupMatchMapIds = groupMatchMaps.map((gmm) => gmm.id);
const groupMatchMapIds = groupMatchMaps.map((gmm) => gmm.id);
await db
.deleteFrom("ReportedWeapon")
.where("userId", "=", user.id)
.where("ReportedWeapon.groupMatchMapId", "in", groupMatchMapIds)
.execute();
await db
.deleteFrom("ReportedWeapon")
.where("userId", "=", user.id)
.where("ReportedWeapon.groupMatchMapId", "in", groupMatchMapIds)
.execute();
logger.info(
`Deleted ${groupMatchMapIds.length} reported weapons for user ${discordId}`,
);
logger.info(
`Deleted ${groupMatchMapIds.length} reported weapons for user ${discordId}`,
);
}
void main();

View File

@@ -1,139 +1,139 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import type { XRankPlacement } from "~/db/types";
import { type MainWeaponId, mainWeaponIds } from "~/modules/in-game-lists";
import { xRankSchema } from "./schemas";
import { syncXPBadges } from "~/features/badges/queries/syncXPBadges.server";
import { type MainWeaponId, mainWeaponIds } from "~/modules/in-game-lists";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
import { xRankSchema } from "./schemas";
const rawJsonNumber = process.argv[2]?.trim();
invariant(rawJsonNumber, "jsonNumber is required (argument 1)");
const jsonNumber = Number(rawJsonNumber);
invariant(
Number.isInteger(jsonNumber),
"jsonNumber must be an integer (argument 1)",
Number.isInteger(jsonNumber),
"jsonNumber must be an integer (argument 1)",
);
type Placements = Array<
Omit<XRankPlacement, "playerId" | "id"> & { playerSplId: string }
Omit<XRankPlacement, "playerId" | "id"> & { playerSplId: string }
>;
const modes = ["splatzones", "towercontrol", "rainmaker", "clamblitz"] as const;
const modeToShort = {
splatzones: "SZ",
towercontrol: "TC",
rainmaker: "RM",
clamblitz: "CB",
splatzones: "SZ",
towercontrol: "TC",
rainmaker: "RM",
clamblitz: "CB",
} as const;
const regions = ["a", "p"] as const;
void main();
async function main() {
const placements: Placements = [];
const placements: Placements = [];
wipeMonthYearPlacements(resolveMonthYear(jsonNumber));
for (const mode of modes) {
for (const region of regions) {
for (const includeWeapon of [false]) {
placements.push(
...(await processJson({
includeWeapon,
mode,
region,
number: jsonNumber,
})),
);
}
}
}
wipeMonthYearPlacements(resolveMonthYear(jsonNumber));
for (const mode of modes) {
for (const region of regions) {
for (const includeWeapon of [false]) {
placements.push(
...(await processJson({
includeWeapon,
mode,
region,
number: jsonNumber,
})),
);
}
}
}
addPlacements(placements);
syncXPBadges();
console.log(`done reading in ${placements.length} placements`);
addPlacements(placements);
syncXPBadges();
logger.info(`done reading in ${placements.length} placements`);
}
async function processJson(args: {
mode: (typeof modes)[number];
region: (typeof regions)[number];
includeWeapon: boolean;
number: number;
mode: (typeof modes)[number];
region: (typeof regions)[number];
includeWeapon: boolean;
number: number;
}) {
const result: Placements = [];
const result: Placements = [];
const url = `https://splatoon3.ink/data/xrank/xrank.detail.${args.region}-${
args.number
}.${args.mode}${args.includeWeapon ? ".weapons" : ""}.json`;
const url = `https://splatoon3.ink/data/xrank/xrank.detail.${args.region}-${
args.number
}.${args.mode}${args.includeWeapon ? ".weapons" : ""}.json`;
console.log(`reading in ${url}...`);
logger.info(`reading in ${url}...`);
const json = await fetch(url).then((res) => res.json());
const validated = xRankSchema.parse(json);
const json = await fetch(url).then((res) => res.json());
const validated = xRankSchema.parse(json);
const array =
validated.data.node.xRankingAr ??
validated.data.node.xRankingCl ??
validated.data.node.xRankingLf ??
validated.data.node.xRankingGl;
invariant(array, "array is null");
const array =
validated.data.node.xRankingAr ??
validated.data.node.xRankingCl ??
validated.data.node.xRankingLf ??
validated.data.node.xRankingGl;
invariant(array, "array is null");
for (const { node: placement } of array.edges) {
const weaponId = Number(atob(placement.weapon.id).replace("Weapon-", ""));
if (!mainWeaponIds.includes(weaponId as MainWeaponId)) {
throw new Error(`Invalid weapon ID: ${weaponId}`);
}
for (const { node: placement } of array.edges) {
const weaponId = Number(atob(placement.weapon.id).replace("Weapon-", ""));
if (!mainWeaponIds.includes(weaponId as MainWeaponId)) {
throw new Error(`Invalid weapon ID: ${weaponId}`);
}
const { month, year } = resolveMonthYear(args.number);
const { month, year } = resolveMonthYear(args.number);
result.push({
name: placement.name,
badges: placement.nameplate.badges
.map((badge) => (badge ? atob(badge.id).replace("Badge-", "") : "null"))
.join(","),
bannerSplId: Number(
atob(placement.nameplate.background.id).replace(
"NameplateBackground-",
"",
),
),
nameDiscriminator: placement.nameId,
power: placement.xPower,
rank: placement.rank,
region: args.region === "p" ? "JPN" : "WEST",
title: placement.byname,
weaponSplId: weaponId as MainWeaponId,
month,
year,
mode: modeToShort[args.mode],
playerSplId: parsePlayerId(placement.id),
});
}
result.push({
name: placement.name,
badges: placement.nameplate.badges
.map((badge) => (badge ? atob(badge.id).replace("Badge-", "") : "null"))
.join(","),
bannerSplId: Number(
atob(placement.nameplate.background.id).replace(
"NameplateBackground-",
"",
),
),
nameDiscriminator: placement.nameId,
power: placement.xPower,
rank: placement.rank,
region: args.region === "p" ? "JPN" : "WEST",
title: placement.byname,
weaponSplId: weaponId as MainWeaponId,
month,
year,
mode: modeToShort[args.mode],
playerSplId: parsePlayerId(placement.id),
});
}
return result;
return result;
}
function parsePlayerId(encoded: string) {
const parts = atob(encoded).split("-");
const last = parts[parts.length - 1];
invariant(last, "last is null");
const parts = atob(encoded).split("-");
const last = parts[parts.length - 1];
invariant(last, "last is null");
return last;
return last;
}
function resolveMonthYear(number: number) {
const start = new Date("2023-03-15");
// 2 is the first X Rank month
// 3 is the length of x rank season
const monthsToAdd = (number - 2) * 3;
const start = new Date("2023-03-15");
// 2 is the first X Rank month
// 3 is the length of x rank season
const monthsToAdd = (number - 2) * 3;
start.setMonth(start.getMonth() + monthsToAdd);
start.setMonth(start.getMonth() + monthsToAdd);
return {
month: start.getMonth() + 1,
year: start.getFullYear(),
};
return {
month: start.getMonth() + 1,
year: start.getFullYear(),
};
}
const addPlayerStm = sql.prepare(/* sql */ `
@@ -176,26 +176,26 @@ const addPlacementStm = sql.prepare(/* sql */ `
`);
function addPlacements(placements: Placements) {
sql.transaction(() => {
for (const placement of placements) {
addPlayerStm.run({ splId: placement.playerSplId });
addPlacementStm.run(placement);
}
})();
sql.transaction(() => {
for (const placement of placements) {
addPlayerStm.run({ splId: placement.playerSplId });
addPlacementStm.run(placement);
}
})();
}
function wipeMonthYearPlacements({
month,
year,
month,
year,
}: {
month: number;
year: number;
month: number;
year: number;
}) {
const wipeMonthYearPlacementsStm = sql.prepare(/* sql */ `
const wipeMonthYearPlacementsStm = sql.prepare(/* sql */ `
delete from "XRankPlacement"
where "month" = @month
and "year" = @year
`);
wipeMonthYearPlacementsStm.run({ month, year });
wipeMonthYearPlacementsStm.run({ month, year });
}

View File

@@ -1,76 +1,76 @@
import { z } from "zod";
const placements = z.object({
edges: z.array(
z.object({
node: z.object({
id: z.string(),
name: z.string(),
rank: z.number(),
rankDiff: z.union([z.string(), z.null()]),
xPower: z.number(),
weapon: z.object({
name: z.string(),
image: z.object({ url: z.string() }),
id: z.string(),
image3d: z.object({ url: z.string() }),
image2d: z.object({ url: z.string() }),
image3dThumbnail: z.object({ url: z.string() }),
image2dThumbnail: z.object({ url: z.string() }),
subWeapon: z.object({
name: z.string(),
image: z.object({ url: z.string() }),
id: z.string(),
}),
specialWeapon: z.object({
name: z.string(),
image: z.object({ url: z.string() }),
id: z.string(),
}),
}),
weaponTop: z.boolean(),
__isPlayer: z.string(),
byname: z.string(),
nameId: z.string(),
nameplate: z.object({
badges: z.array(
z.union([
z.object({
image: z.object({ url: z.string() }),
id: z.string(),
}),
z.null(),
]),
),
background: z.object({
textColor: z.object({
a: z.number(),
b: z.number(),
g: z.number(),
r: z.number(),
}),
image: z.object({ url: z.string() }),
id: z.string(),
}),
}),
__typename: z.string(),
}),
cursor: z.string(),
}),
),
pageInfo: z.object({ endCursor: z.string(), hasNextPage: z.boolean() }),
edges: z.array(
z.object({
node: z.object({
id: z.string(),
name: z.string(),
rank: z.number(),
rankDiff: z.union([z.string(), z.null()]),
xPower: z.number(),
weapon: z.object({
name: z.string(),
image: z.object({ url: z.string() }),
id: z.string(),
image3d: z.object({ url: z.string() }),
image2d: z.object({ url: z.string() }),
image3dThumbnail: z.object({ url: z.string() }),
image2dThumbnail: z.object({ url: z.string() }),
subWeapon: z.object({
name: z.string(),
image: z.object({ url: z.string() }),
id: z.string(),
}),
specialWeapon: z.object({
name: z.string(),
image: z.object({ url: z.string() }),
id: z.string(),
}),
}),
weaponTop: z.boolean(),
__isPlayer: z.string(),
byname: z.string(),
nameId: z.string(),
nameplate: z.object({
badges: z.array(
z.union([
z.object({
image: z.object({ url: z.string() }),
id: z.string(),
}),
z.null(),
]),
),
background: z.object({
textColor: z.object({
a: z.number(),
b: z.number(),
g: z.number(),
r: z.number(),
}),
image: z.object({ url: z.string() }),
id: z.string(),
}),
}),
__typename: z.string(),
}),
cursor: z.string(),
}),
),
pageInfo: z.object({ endCursor: z.string(), hasNextPage: z.boolean() }),
});
// e.g. https://splatoon3.ink/data/xrank/xrank.detail.a-2.clamblitz.json
export const xRankSchema = z.object({
data: z.object({
node: z.object({
__typename: z.string(),
xRankingAr: placements.optional(),
xRankingCl: placements.optional(),
xRankingLf: placements.optional(),
xRankingGl: placements.optional(),
id: z.string(),
}),
}),
data: z.object({
node: z.object({
__typename: z.string(),
xRankingAr: placements.optional(),
xRankingCl: placements.optional(),
xRankingLf: placements.optional(),
xRankingGl: placements.optional(),
id: z.string(),
}),
}),
});

View File

@@ -4,56 +4,56 @@ import { databaseTimestampToDate } from "~/utils/dates";
import { logger } from "~/utils/logger";
async function main() {
const allFcs = await db
.selectFrom("UserFriendCode")
.innerJoin("User", "User.id", "UserFriendCode.userId")
.select(["UserFriendCode.friendCode", "User.id as userId"])
.orderBy("UserFriendCode.createdAt", "asc")
.whereRef("User.id", "=", "UserFriendCode.submitterUserId")
.execute();
const allFcs = await db
.selectFrom("UserFriendCode")
.innerJoin("User", "User.id", "UserFriendCode.userId")
.select(["UserFriendCode.friendCode", "User.id as userId"])
.orderBy("UserFriendCode.createdAt", "asc")
.whereRef("User.id", "=", "UserFriendCode.submitterUserId")
.execute();
const fcMap = new Map<string, number[]>();
for (const fc of allFcs) {
const fcs = fcMap.get(fc.friendCode) ?? [];
fcs.push(fc.userId);
fcMap.set(fc.friendCode, fcs);
}
const fcMap = new Map<string, number[]>();
for (const fc of allFcs) {
const fcs = fcMap.get(fc.friendCode) ?? [];
fcs.push(fc.userId);
fcMap.set(fc.friendCode, fcs);
}
const friendCodeAdders = await db
.selectFrom("UserFriendCode")
.innerJoin("User", "User.id", "UserFriendCode.userId")
.select([
"UserFriendCode.friendCode",
"UserFriendCode.createdAt",
"User.id",
"User.discordId",
"User.discordUniqueName",
])
.orderBy("UserFriendCode.createdAt", "desc")
.whereRef("User.id", "=", "UserFriendCode.submitterUserId")
.limit(90)
.execute();
const friendCodeAdders = await db
.selectFrom("UserFriendCode")
.innerJoin("User", "User.id", "UserFriendCode.userId")
.select([
"UserFriendCode.friendCode",
"UserFriendCode.createdAt",
"User.id",
"User.discordId",
"User.discordUniqueName",
])
.orderBy("UserFriendCode.createdAt", "desc")
.whereRef("User.id", "=", "UserFriendCode.submitterUserId")
.limit(90)
.execute();
let result = "";
let result = "";
let date = "";
for (const [i, friendCodeAdder] of friendCodeAdders.entries()) {
const utc = databaseTimestampToDate(
friendCodeAdder.createdAt,
).toUTCString();
const newDate = utc.split(",")[0];
if (date !== newDate) {
date = newDate;
result += "\n";
}
let date = "";
for (const [i, friendCodeAdder] of friendCodeAdders.entries()) {
const utc = databaseTimestampToDate(
friendCodeAdder.createdAt,
).toUTCString();
const newDate = utc.split(",")[0];
if (date !== newDate) {
date = newDate;
result += "\n";
}
const isDuplicate =
(fcMap.get(friendCodeAdder.friendCode) ?? [])?.length > 1;
const isDuplicate =
(fcMap.get(friendCodeAdder.friendCode) ?? [])?.length > 1;
result += `${i < 9 ? "0" : ""}${i + 1}) ${utc} - ${friendCodeAdder.friendCode}${isDuplicate ? " >>DUPLICATE<<" : ""} - ${friendCodeAdder.discordUniqueName} - ${friendCodeAdder.discordId}\n`;
}
result += `${i < 9 ? "0" : ""}${i + 1}) ${utc} - ${friendCodeAdder.friendCode}${isDuplicate ? " >>DUPLICATE<<" : ""} - ${friendCodeAdder.discordUniqueName} - ${friendCodeAdder.discordId}\n`;
}
logger.info(result);
logger.info(result);
}
void main();

View File

@@ -1,38 +1,37 @@
import fs from "node:fs";
import path from "path";
import { fileURLToPath } from "url";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
import path from "node:path";
function main() {
const dbProdPath = path.join(__dirname, "..", "db-prod.sqlite3");
const dbProdShmPath = path.join(__dirname, "..", "db-prod.sqlite3-shm");
const dbProdWalPath = path.join(__dirname, "..", "db-prod.sqlite3-wal");
const dbCopyPath = path.join(__dirname, "..", "db-copy.sqlite3");
const dbProdPath = path.join(__dirname, "..", "db-prod.sqlite3");
const dbProdShmPath = path.join(__dirname, "..", "db-prod.sqlite3-shm");
const dbProdWalPath = path.join(__dirname, "..", "db-prod.sqlite3-wal");
const dbCopyPath = path.join(__dirname, "..", "db-copy.sqlite3");
if (!fs.existsSync(dbCopyPath)) {
console.error(`File ${dbCopyPath} does not exist`);
process.exit(1);
}
if (!fs.existsSync(dbCopyPath)) {
console.error(`File ${dbCopyPath} does not exist`);
process.exit(1);
}
// delete db-prod.sqlite3-shm file if exists
if (fs.existsSync(dbProdShmPath)) {
fs.unlinkSync(dbProdShmPath);
}
// delete db-prod.sqlite3-shm file if exists
if (fs.existsSync(dbProdShmPath)) {
fs.unlinkSync(dbProdShmPath);
}
// delete db-prod.sqlite3-wal file if exists
if (fs.existsSync(dbProdWalPath)) {
fs.unlinkSync(dbProdWalPath);
}
// delete db-prod.sqlite3-wal file if exists
if (fs.existsSync(dbProdWalPath)) {
fs.unlinkSync(dbProdWalPath);
}
// delete db-prod.sqlite3 if exists
if (fs.existsSync(dbProdPath)) {
fs.unlinkSync(dbProdPath);
}
// delete db-prod.sqlite3 if exists
if (fs.existsSync(dbProdPath)) {
fs.unlinkSync(dbProdPath);
}
// copy db-copy.sqlite3 to db-prod.sqlite3
fs.copyFileSync(dbCopyPath, dbProdPath);
// copy db-copy.sqlite3 to db-prod.sqlite3
fs.copyFileSync(dbCopyPath, dbProdPath);
}
main();

View File

@@ -1,26 +1,26 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const discordId = process.argv[2]?.trim();
invariant(discordId, "discord id is required (argument 1)");
const id = (
sql
.prepare(`select "id" from "User" where "discordId" = @discordId`)
.get({ discordId }) as any
sql
.prepare(`select "id" from "User" where "discordId" = @discordId`)
.get({ discordId }) as any
)?.id;
invariant(id, "user not found");
sql
.prepare(`update "User" set "isVideoAdder" = 0 where "id" = @id`)
.run({ id });
.prepare(`update "User" set "isVideoAdder" = 0 where "id" = @id`)
.run({ id });
sql
.prepare(`delete from "UnvalidatedVideo" where "submitterUserId" = @id`)
.run({ id });
.prepare(`delete from "UnvalidatedVideo" where "submitterUserId" = @id`)
.run({ id });
console.log(`Removed vodder`);
logger.info("Removed vodder");

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const id = process.argv[2]?.trim();
const newName = process.argv[3]?.trim();
@@ -9,12 +9,12 @@ const newName = process.argv[3]?.trim();
invariant(id, "id of badge is required (argument 1)");
invariant(newName, "display name of badge is required (argument 2)");
invariant(
newName !== newName.toLocaleLowerCase(),
"displayName of badge must have at least one uppercase letter",
newName !== newName.toLocaleLowerCase(),
"displayName of badge must have at least one uppercase letter",
);
sql
.prepare("update badge set displayName = @newName where id = @id")
.run({ id, newName });
.prepare("update badge set displayName = @newName where id = @id")
.run({ id, newName });
console.log(`Added updated name. New name: ${newName}`);
logger.info(`Added updated name. New name: ${newName}`);

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
// only to be used if tournament didn't have skills etc. calculated
@@ -10,9 +10,9 @@ const id = process.argv[2]?.trim();
invariant(id, "id of tournament is required (argument 1)");
sql
.prepare(
`delete from "TournamentResult" where "TournamentResult"."tournamentId" = @id`,
)
.run({ id });
.prepare(
`delete from "TournamentResult" where "TournamentResult"."tournamentId" = @id`,
)
.run({ id });
console.log(`Reopened tournament with id ${id}`);
logger.info(`Reopened tournament with id ${id}`);

View File

@@ -1,66 +1,66 @@
/* eslint-disable no-console */
import fs from "node:fs";
import path from "node:path";
import invariant from "~/utils/invariant";
import { fileURLToPath } from "url";
import { fileURLToPath } from "node:url";
import { logger } from "~/utils/logger";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const GEAR_IMAGES_DIR_PATH = path.join(
__dirname,
"..",
"public",
"static-assets",
"img",
"gear",
__dirname,
"..",
"public",
"static-assets",
"img",
"gear",
);
const GEAR_JSON_PATH = path.join(__dirname, "output", "gear.json");
async function main() {
const gear = JSON.parse(fs.readFileSync(GEAR_JSON_PATH, "utf8"));
for (const gearSlot of ["head", "clothes", "shoes"] as const) {
const gearSlotDirPath = path.join(GEAR_IMAGES_DIR_PATH, gearSlot);
const files = await fs.promises.readdir(gearSlotDirPath);
const gear = JSON.parse(fs.readFileSync(GEAR_JSON_PATH, "utf8"));
for (const gearSlot of ["head", "clothes", "shoes"] as const) {
const gearSlotDirPath = path.join(GEAR_IMAGES_DIR_PATH, gearSlot);
const files = await fs.promises.readdir(gearSlotDirPath);
const type =
gearSlot === "head" ? "Hed" : gearSlot === "shoes" ? "Shs" : "Clt";
const type =
gearSlot === "head" ? "Hed" : gearSlot === "shoes" ? "Shs" : "Clt";
for (const file of files) {
// did we already replace the name
if (
!file.startsWith("Shs") &&
!file.startsWith("Clt") &&
!file.startsWith("Hed")
) {
continue;
}
for (const file of files) {
// did we already replace the name
if (
!file.startsWith("Shs") &&
!file.startsWith("Clt") &&
!file.startsWith("Hed")
) {
continue;
}
if (file.endsWith(".webp")) {
fs.unlinkSync(path.join(gearSlotDirPath, file));
continue;
}
if (file.endsWith(".webp")) {
fs.unlinkSync(path.join(gearSlotDirPath, file));
continue;
}
const internalName = file.replace(".png", "").split("_")[1];
invariant(internalName);
const internalName = file.replace(".png", "").split("_")[1];
invariant(internalName);
const gearId = gear.find(
(g: any) => g.internalName === internalName && g.type === type,
)?.id;
const gearId = gear.find(
(g: any) => g.internalName === internalName && g.type === type,
)?.id;
if (typeof gearId !== "number") {
fs.unlinkSync(path.join(gearSlotDirPath, file));
continue;
}
if (typeof gearId !== "number") {
fs.unlinkSync(path.join(gearSlotDirPath, file));
continue;
}
fs.renameSync(
path.join(gearSlotDirPath, file),
path.join(gearSlotDirPath, `${gearId}.png`),
);
}
}
fs.renameSync(
path.join(gearSlotDirPath, file),
path.join(gearSlotDirPath, `${gearId}.png`),
);
}
}
console.log("done with all");
logger.info("done with all");
}
void main();

View File

@@ -1,4 +1,3 @@
/* eslint-disable */
// @ts-nocheck
import fs from "node:fs";
@@ -6,73 +5,74 @@ import path from "node:path";
import invariant from "~/utils/invariant";
import weapons from "./dicts/WeaponInfoMain.json";
import { fileURLToPath } from "url";
import { fileURLToPath } from "node:url";
import { logger } from "~/utils/logger";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const DIR_PATH_1 = path.join(
__dirname,
"..",
"public",
"static-assets",
"img",
"main-weapons",
__dirname,
"..",
"public",
"static-assets",
"img",
"main-weapons",
);
const DIR_PATH_2 = path.join(
__dirname,
"..",
"public",
"static-assets",
"img",
"main-weapons-outlined",
__dirname,
"..",
"public",
"static-assets",
"img",
"main-weapons-outlined",
);
const DIR_PATH_3 = path.join(
__dirname,
"..",
"public",
"static-assets",
"img",
"main-weapons-outlined-2",
__dirname,
"..",
"public",
"static-assets",
"img",
"main-weapons-outlined-2",
);
async function main() {
for (const [i, dir] of [DIR_PATH_1, DIR_PATH_2, DIR_PATH_3].entries()) {
const files = await fs.promises.readdir(dir);
for (const [i, dir] of [DIR_PATH_1, DIR_PATH_2, DIR_PATH_3].entries()) {
const files = await fs.promises.readdir(dir);
for (const file of files) {
// skip if already replaced
if (file.length <= 8) continue;
for (const file of files) {
// skip if already replaced
if (file.length <= 8) continue;
const differentLevelBadge = (fileName: string) => {
if (i === 1 && fileName.includes("Lv01")) return true;
if (i === 2 && fileName.includes("Lv00")) return true;
const differentLevelBadge = (fileName: string) => {
if (i === 1 && fileName.includes("Lv01")) return true;
if (i === 2 && fileName.includes("Lv00")) return true;
return false;
};
return false;
};
if (file.includes(".webp") || differentLevelBadge(file)) {
await fs.promises.unlink(path.join(dir, file));
continue;
}
if (file.includes(".webp") || differentLevelBadge(file)) {
await fs.promises.unlink(path.join(dir, file));
continue;
}
const weapon: any = weapons.find(
(weapon: any) =>
file.includes(`${weapon.__RowId}.`) ||
file.includes(`${weapon.__RowId}_`),
);
const weapon: any = weapons.find(
(weapon: any) =>
file.includes(`${weapon.__RowId}.`) ||
file.includes(`${weapon.__RowId}_`),
);
if (!weapon) {
await fs.promises.unlink(path.join(dir, file));
continue;
}
if (!weapon) {
await fs.promises.unlink(path.join(dir, file));
continue;
}
fs.renameSync(path.join(dir, file), path.join(dir, `${weapon.Id}.png`));
}
}
fs.renameSync(path.join(dir, file), path.join(dir, `${weapon.Id}.png`));
}
}
console.log("done with all");
logger.info("done with all");
}
void main();

View File

@@ -1,12 +1,12 @@
/* eslint-disable no-console */
import "dotenv/config";
import { ordinal } from "openskill";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import type { Skill } from "~/db/types";
import type { TierName } from "~/features/mmr/mmr-constants";
import { freshUserSkills } from "~/features/mmr/tiered.server";
import { addInitialSkill } from "~/features/sendouq/queries/addInitialSkill.server";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const rawNth = process.argv[2]?.trim();
@@ -25,12 +25,12 @@ const skillsExistStm = sql.prepare(/* sql */ `
`);
invariant(
skillsExistStm.get({ season: nth - 1 }),
`No skills for season ${nth - 1}`,
skillsExistStm.get({ season: nth - 1 }),
`No skills for season ${nth - 1}`,
);
invariant(
!skillsExistStm.get({ season: nth }),
`Skills for season ${nth} already exist`,
!skillsExistStm.get({ season: nth }),
`Skills for season ${nth} already exist`,
);
const activeMatchExistsStm = sql.prepare(/* sql */ `
@@ -42,12 +42,12 @@ const activeMatchExistsStm = sql.prepare(/* sql */ `
"Skill"."id" is null
`);
const idsOfActiveMatches = activeMatchExistsStm
.all()
.map((row) => (row as any).id) as number[];
.all()
.map((row) => (row as any).id) as number[];
invariant(
!activeMatchExistsStm.get(),
`There are active matches: (ids: ${idsOfActiveMatches.join(", ")})`,
!activeMatchExistsStm.get(),
`There are active matches: (ids: ${idsOfActiveMatches.join(", ")})`,
);
// from prod database:
@@ -58,33 +58,33 @@ invariant(
const DEFAULT_NEW_SIGMA = 6.5;
const TIER_TO_NEW_TIER: Record<TierName, TierName> = {
IRON: "BRONZE",
BRONZE: "BRONZE",
SILVER: "SILVER",
GOLD: "GOLD",
PLATINUM: "PLATINUM",
DIAMOND: "DIAMOND",
LEVIATHAN: "DIAMOND",
IRON: "BRONZE",
BRONZE: "BRONZE",
SILVER: "SILVER",
GOLD: "GOLD",
PLATINUM: "PLATINUM",
DIAMOND: "DIAMOND",
LEVIATHAN: "DIAMOND",
};
const allSkills = Object.entries(freshUserSkills(nth - 1).userSkills)
.map(([userId, skill]) => ({ userId: Number(userId), ...skill }))
.filter((s) => !s.approximate)
.sort((a, b) => b.ordinal - a.ordinal);
.map(([userId, skill]) => ({ userId: Number(userId), ...skill }))
.filter((s) => !s.approximate)
.sort((a, b) => b.ordinal - a.ordinal);
const skillsToConsider = allSkills.filter((s) =>
Object.values(TIER_TO_NEW_TIER).includes(s.tier.name),
Object.values(TIER_TO_NEW_TIER).includes(s.tier.name),
);
const groupedSkills = skillsToConsider.reduce(
(acc, skill) => {
const { tier } = skill;
if (!acc[tier.name]) {
acc[tier.name] = [];
}
acc[tier.name].push(skill);
return acc;
},
{} as Record<TierName, typeof skillsToConsider>,
(acc, skill) => {
const { tier } = skill;
if (!acc[tier.name]) {
acc[tier.name] = [];
}
acc[tier.name].push(skill);
return acc;
},
{} as Record<TierName, typeof skillsToConsider>,
);
const skillStm = sql.prepare(/* sql */ `
@@ -96,29 +96,29 @@ const skillStm = sql.prepare(/* sql */ `
and "ordinal" = @ordinal
`);
const midPoints = Object.entries(groupedSkills).reduce(
(acc, [tier, skills]) => {
const midPoint = skills[Math.floor(skills.length / 2)];
const midPointSkill = skillStm.get(midPoint) as Skill;
invariant(midPointSkill, "midPointSkill not found");
(acc, [tier, skills]) => {
const midPoint = skills[Math.floor(skills.length / 2)];
const midPointSkill = skillStm.get(midPoint) as Skill;
invariant(midPointSkill, "midPointSkill not found");
acc[tier as TierName] = midPointSkill;
return acc;
},
{} as Record<TierName, Skill>,
acc[tier as TierName] = midPointSkill;
return acc;
},
{} as Record<TierName, Skill>,
);
const newSkills = allSkills.map((s) => {
const newTier = TIER_TO_NEW_TIER[s.tier.name];
const mu = midPoints[newTier].mu;
const sigma = DEFAULT_NEW_SIGMA;
const newTier = TIER_TO_NEW_TIER[s.tier.name];
const mu = midPoints[newTier].mu;
const sigma = DEFAULT_NEW_SIGMA;
return {
userId: s.userId,
sigma,
mu,
ordinal: ordinal({ sigma, mu }),
season: nth,
};
return {
userId: s.userId,
sigma,
mu,
ordinal: ordinal({ sigma, mu }),
season: nth,
};
});
const allGroupsInactiveStm = sql.prepare(/* sql */ `
@@ -128,12 +128,12 @@ const allGroupsInactiveStm = sql.prepare(/* sql */ `
"status" = 'INACTIVE'
`);
sql.transaction(() => {
for (const skill of newSkills) {
addInitialSkill(skill);
}
allGroupsInactiveStm.run();
for (const skill of newSkills) {
addInitialSkill(skill);
}
allGroupsInactiveStm.run();
})();
console.log(
`Done adding new skills for season ${nth} (${newSkills.length} added)`,
logger.info(
`Done adding new skills for season ${nth} (${newSkills.length} added)`,
);

View File

@@ -1,8 +1,8 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import { currentOrPreviousSeason } from "~/features/mmr/season";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const discordId = process.argv[2]?.trim();
@@ -13,11 +13,11 @@ const currentSeasonNth = currentOrPreviousSeason(new Date())?.nth;
invariant(currentSeasonNth, "current season nth is required");
sql
.prepare(
'update "User" set plusSkippedForSeasonNth = @plusSkippedForSeasonNth where discordId = @discordId',
)
.run({ discordId, plusSkippedForSeasonNth: currentSeasonNth });
.prepare(
'update "User" set plusSkippedForSeasonNth = @plusSkippedForSeasonNth where discordId = @discordId',
)
.run({ discordId, plusSkippedForSeasonNth: currentSeasonNth });
console.log(
`Plus Server admission will be skipped for Discord ID: ${discordId} (season ${currentSeasonNth})`,
logger.info(
`Plus Server admission will be skipped for Discord ID: ${discordId} (season ${currentSeasonNth})`,
);

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
const discordId = process.argv[2]?.trim();
const discordId2 = process.argv[3]?.trim();
@@ -13,23 +12,23 @@ invariant(discordId !== discordId2, "discord ids must be different");
const tempDiscordId = "temp-discord-id";
const stm = sql.prepare(
/** sql */ `update "User" set "discordId" = @newDiscordId where "discordId" = @discordId;`,
/** sql */ `update "User" set "discordId" = @newDiscordId where "discordId" = @discordId;`,
);
// swap user discordIds
sql.transaction(() => {
stm.run({
discordId: discordId,
newDiscordId: tempDiscordId,
});
stm.run({
discordId: discordId,
newDiscordId: tempDiscordId,
});
stm.run({
discordId: discordId2,
newDiscordId: discordId,
});
stm.run({
discordId: discordId2,
newDiscordId: discordId,
});
stm.run({
discordId: tempDiscordId,
newDiscordId: discordId2,
});
stm.run({
discordId: tempDiscordId,
newDiscordId: discordId2,
});
})();

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import "dotenv/config";
import { syncXPBadges } from "~/features/badges/queries/syncXPBadges.server";
import { logger } from "~/utils/logger";
syncXPBadges();
console.log("Synced XP badges");
logger.info("Synced XP badges");

View File

@@ -1,48 +1,48 @@
/* eslint-disable no-console */
import "dotenv/config";
import { db } from "~/db/sql";
import { logger } from "~/utils/logger";
async function main() {
const weaponPools = await db
.selectFrom("UserWeapon")
.select([
"UserWeapon.userId",
"UserWeapon.weaponSplId",
"UserWeapon.userId",
])
.where("UserWeapon.order", "!=", 5)
.orderBy("UserWeapon.order asc")
.execute();
const weaponPools = await db
.selectFrom("UserWeapon")
.select([
"UserWeapon.userId",
"UserWeapon.weaponSplId",
"UserWeapon.userId",
])
.where("UserWeapon.order", "!=", 5)
.orderBy("UserWeapon.order asc")
.execute();
// group by userId
const weaponPoolsByUserId = weaponPools.reduce(
(acc, weaponPool) => {
if (!acc[weaponPool.userId]) {
acc[weaponPool.userId] = [];
}
// group by userId
const weaponPoolsByUserId = weaponPools.reduce(
(acc, weaponPool) => {
if (!acc[weaponPool.userId]) {
acc[weaponPool.userId] = [];
}
acc[weaponPool.userId].push(weaponPool);
acc[weaponPool.userId].push(weaponPool);
return acc;
},
{} as Record<string, typeof weaponPools>,
);
return acc;
},
{} as Record<string, typeof weaponPools>,
);
for (const [userId, weaponPools] of Object.entries(weaponPoolsByUserId)) {
const weaponPoolIds = weaponPools.map(
(weaponPool) => weaponPool.weaponSplId,
);
for (const [userId, weaponPools] of Object.entries(weaponPoolsByUserId)) {
const weaponPoolIds = weaponPools.map(
(weaponPool) => weaponPool.weaponSplId,
);
await db
.updateTable("User")
.set({
qWeaponPool: JSON.stringify(weaponPoolIds),
})
.where("User.id", "=", Number(userId))
.execute();
}
await db
.updateTable("User")
.set({
qWeaponPool: JSON.stringify(weaponPoolIds),
})
.where("User.id", "=", Number(userId))
.execute();
}
console.log("done with the transfer");
logger.info("done with the transfer");
}
void main();

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
import { syncXPBadges } from "../app/features/badges/queries/syncXPBadges.server";
const discordId = process.argv[2]?.trim();
@@ -9,10 +9,10 @@ const discordId = process.argv[2]?.trim();
invariant(discordId, "discord id is required (argument 1)");
sql
.prepare(
'update "SplatoonPlayer" set userId = null where userId = (select id from "User" where discordId = @discordId)',
)
.run({ discordId });
.prepare(
'update "SplatoonPlayer" set userId = null where userId = (select id from "User" where discordId = @discordId)',
)
.run({ discordId });
syncXPBadges();
console.log(`Unlinked player for discord id: ${discordId}`);
logger.info(`Unlinked player for discord id: ${discordId}`);

View File

@@ -1,16 +1,16 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
const discordId = process.argv[2]?.trim();
invariant(discordId, "discord id is required (argument 1)");
sql
.prepare(
'update "User" set plusSkippedForSeasonNth = null where discordId = @discordId',
)
.run({ discordId });
.prepare(
'update "User" set plusSkippedForSeasonNth = null where discordId = @discordId',
)
.run({ discordId });
console.log(`Plus Server admission unskipped for Discord ID: ${discordId}`);
logger.info(`Plus Server admission unskipped for Discord ID: ${discordId}`);

View File

@@ -1,27 +1,27 @@
/* eslint-disable no-console */
import "dotenv/config";
import invariant from "~/utils/invariant";
import { ADMIN_ID } from "~/constants";
import { FRIEND_CODE_REGEXP } from "~/features/sendouq/q-constants";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
async function main() {
const discordId = process.argv[2]?.trim();
const discordId = process.argv[2]?.trim();
invariant(discordId, "discord id is required (argument 1)");
invariant(discordId, "discord id is required (argument 1)");
const newFriendCode = process.argv[3]?.trim();
const newFriendCode = process.argv[3]?.trim();
invariant(discordId, "friend code is required (argument 2)");
invariant(discordId, "friend code is required (argument 2)");
invariant(FRIEND_CODE_REGEXP.test(newFriendCode), "Invalid friend code");
invariant(FRIEND_CODE_REGEXP.test(newFriendCode), "Invalid friend code");
await UserRepository.insertFriendCode({
friendCode: newFriendCode,
submitterUserId: ADMIN_ID,
userId: await UserRepository.findByIdentifier(discordId).then((u) => u!.id),
});
console.log(`Friend code updated: ${discordId} - ${newFriendCode}`);
await UserRepository.insertFriendCode({
friendCode: newFriendCode,
submitterUserId: ADMIN_ID,
userId: await UserRepository.findByIdentifier(discordId).then((u) => u!.id),
});
logger.info(`Friend code updated: ${discordId} - ${newFriendCode}`);
}
void main();

View File

@@ -1,52 +1,51 @@
/* eslint-disable */
// @ts-nocheck
import path from "node:path";
import fs from "node:fs";
import path from "node:path";
import type euEn from "./dicts/langs/EUen.json";
import { fileURLToPath } from "url";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const LANG_DICTS_PATH = path.join(__dirname, "dicts", "langs");
export const LANG_JSONS_TO_CREATE = [
"EUen",
"CNzh",
"EUde",
"EUes",
"USes",
"EUfr",
"EUit",
"EUnl",
"EUru",
"JPja",
"KRko",
"USfr",
"EUen",
"CNzh",
"EUde",
"EUes",
"USes",
"EUfr",
"EUit",
"EUnl",
"EUru",
"JPja",
"KRko",
"USfr",
];
export async function loadLangDicts() {
const result: Array<[langCode: string, translations: typeof euEn]> = [];
const result: Array<[langCode: string, translations: typeof euEn]> = [];
const files = await fs.promises.readdir(LANG_DICTS_PATH);
for (const file of files) {
if (file === ".gitkeep") continue;
const files = await fs.promises.readdir(LANG_DICTS_PATH);
for (const file of files) {
if (file === ".gitkeep") continue;
const translations = JSON.parse(
fs.readFileSync(path.join(LANG_DICTS_PATH, file), "utf8"),
);
const translations = JSON.parse(
fs.readFileSync(path.join(LANG_DICTS_PATH, file), "utf8"),
);
result.push([file.replace(".json", ""), translations]);
}
result.push([file.replace(".json", ""), translations]);
}
return result;
return result;
}
export function translationJsonFolderName(langCode: string) {
if (langCode === "EUes") return "es-ES";
if (langCode === "USes") return "es-US";
if (langCode === "EUfr") return "fr-EU";
if (langCode === "USfr") return "fr-CA";
return langCode.slice(2);
if (langCode === "EUes") return "es-ES";
if (langCode === "USes") return "es-US";
if (langCode === "EUfr") return "fr-EU";
if (langCode === "USfr") return "fr-CA";
return langCode.slice(2);
}