mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-07 03:35:40 -05:00
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:
@@ -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`);
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)`,
|
||||
);
|
||||
|
||||
@@ -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})`,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user