Typescript fixes

This commit is contained in:
Jordan Eldredge 2025-06-05 18:00:42 -04:00
parent 12ec93d2d2
commit b794ca333c
6 changed files with 10 additions and 191 deletions

View file

@ -35,7 +35,6 @@ import { setHashesForSkin } from "./skinHash";
import * as S3 from "./s3";
import { generateDescription } from "./services/openAi";
import KeyValue from "./data/KeyValue";
import detectRepacks from "./tasks/detectRepacks";
async function withHandler(
cb: (handler: DiscordEventHandler) => Promise<void>
@ -389,10 +388,6 @@ program
"Compute the order in which skins should be displayed in the museum"
)
.option("--foo", "Learn about missing skins")
.option(
"--winampskinsinfo",
"Detect skins that were broken by winampskins.info injecting an ad file"
)
.option("--test-cloudflare", "Try to upload to cloudflare")
.action(async (arg) => {
const {
@ -404,16 +399,12 @@ program
configureR2Cors,
computeMuseumOrder,
foo,
winampskinsinfo,
testCloudflare,
} = arg;
if (testCloudflare) {
const buffer = new Buffer("testing", "utf8");
await S3.putTemp("hello", buffer);
}
if (winampskinsinfo) {
await detectRepacks();
}
if (computeMuseumOrder) {
await Skins.computeMuseumOrder();
console.log("Museum order updated.");
@ -460,10 +451,12 @@ program
}
if (foo) {
const ctx = new UserContext();
const missingModernSkins = await KeyValue.get("missingModernSkins");
const missingModernSkins = await KeyValue.get<string[]>(
"missingModernSkins"
);
const missingModernSkinsSet = new Set(missingModernSkins);
const skins = {};
for (const md5 of missingModernSkins) {
for (const md5 of missingModernSkins!) {
const skin = await SkinModel.fromMd5(ctx, md5);
if (skin == null) {
continue;
@ -489,7 +482,7 @@ program
.select();
console.log(`Found ${skinRows.length} skins to update`);
const missingModernSkins = new Set(
await KeyValue.get("missingModernSkins")
await KeyValue.get<string[]>("missingModernSkins")
);
const skins = skinRows.map((row) => new SkinModel(ctx, row));
for (const skin of skins) {

View file

@ -249,7 +249,7 @@ export default class SkinModel {
const response = await fetch(this.getSkinUrl());
if (!response.ok) {
const missingModernSkins =
(await KeyValue.get("missingModernSkins")) ?? [];
(await KeyValue.get<string[]>("missingModernSkins")) ?? [];
const missingModernSkinsSet = new Set(missingModernSkins);
missingModernSkinsSet.add(this.getMd5());
await KeyValue.set(
@ -410,6 +410,7 @@ export default class SkinModel {
getZip = mem(async (): Promise<JSZip> => {
const buffer = await this.getBuffer();
// @ts-ignore TODO: Is this typing correct?
return JSZip.loadAsync(buffer);
});

View file

@ -55,6 +55,7 @@
"sync": "ts-node --transpile-only ./tasks/syncWithArchive.ts",
"migrate": "knex migrate:latest",
"grats": "grats",
"typecheck": "tsc --noEmit",
"dev:next": "next dev",
"build:next": "next build",
"start:next": "next start",

View file

@ -1,8 +0,0 @@
import Task from "./ITask";
import * as Skins from "../data/skins";
export default class DeleteSkin extends Task {
async run(): Promise<void> {
await Skins.deleteSkin(md5);
}
}

View file

@ -1,168 +0,0 @@
import KeyValue from "../data/KeyValue";
import SkinModel from "../data/SkinModel";
import UserContext from "../data/UserContext";
import { knex } from "../db";
export default async function detectRepacks() {
const ctx = new UserContext();
const stored = await KeyValue.get<{ [key: string]: string[] }>(
"winampskins.info"
);
if (stored == null) {
throw new Error("Expected kv");
}
// console.log(stored);
let found = 0;
let corrupt = 0;
let identical = 0;
for (const [key, orignalCandidates] of Object.entries(stored)) {
const repack = await SkinModel.fromMd5Assert(ctx, key);
for (const originalHash of orignalCandidates) {
const original = await SkinModel.fromMd5Assert(ctx, originalHash);
if (await isRepackOf(repack, original)) {
console.log(`${key} is a repack of ${originalHash}`);
found++;
} else if (await areIdentical(repack, original)) {
console.log(`${key} is an identical skin to ${originalHash}`);
identical++;
} else if (await isCorruptRepack(repack, original)) {
console.log(`${key} is a CORRUPT repack of ${originalHash}`);
corrupt++;
}
}
}
console.log(
`Found ${found} originals out of ${Object.keys(stored).length} repacks`
);
console.log(
`Found ${identical} twins out of ${Object.keys(stored).length} repacks`
);
console.log(
`Found ${corrupt} corrupt repacks out of ${
Object.keys(stored).length
} repacks`
);
}
// A skin is a repack if it contains exactly the same files except for the addition of advertizing files
async function isRepackOf(
repack: SkinModel,
original: SkinModel
): Promise<boolean> {
const nonAdHashesSet = await getSkinArchiveFileHashesSansAds(repack);
const hashesSet = await getSkinArchiveFileHashes(original);
return setsAreEqual(nonAdHashesSet, hashesSet);
}
async function isCorruptRepack(
repack: SkinModel,
original: SkinModel
): Promise<boolean> {
const repackHashesSet = await getSkinArchiveFileHashes(repack);
const hashesSet = await getSkinArchiveFileHashes(original);
return setContains(repackHashesSet, hashesSet);
}
async function areIdentical(
repack: SkinModel,
original: SkinModel
): Promise<boolean> {
const repackHashesSet = await getSkinArchiveFileHashes(repack);
const hashesSet = await getSkinArchiveFileHashes(original);
return setsAreEqual(repackHashesSet, hashesSet);
}
async function getSkinArchiveFileHashes(skin: SkinModel): Promise<Set<string>> {
const archiveFiles = await skin.getArchiveFiles();
const nonAdHashes = archiveFiles.map((f) => f.getFileMd5());
return new Set(nonAdHashes);
}
async function getSkinArchiveFileHashesSansAds(
skin: SkinModel
): Promise<Set<string>> {
const archiveFiles = await skin.getArchiveFiles();
const nonAdFiles = archiveFiles.filter(
(f) => !f.getFileName().match(/winampskins\.info/)
);
const nonAdHashes = nonAdFiles.map((f) => f.getFileMd5());
return new Set(nonAdHashes);
}
// Get all the skins that have ad files in them
async function getRepackSkins(ctx: UserContext) {
const rows = await knex.raw(
`SELECT DISTINCT(skin_md5) FROM archive_files WHERE file_name = "winampskins.info.txt";`
);
return Promise.all(
rows.map((row) => {
return SkinModel.fromMd5Assert(ctx, row.skin_md5);
})
);
}
function setsAreEqual(a: Set<string>, b: Set<string>): boolean {
if (a === b) return true;
if (a.size !== b.size) {
return false;
}
for (const value of a) if (!b.has(value)) return false;
return true;
}
// Does a contain b?
function setContains(a: Set<string>, b: Set<string>): boolean {
if (a === b) return true;
if (a.size < b.size) {
return false;
}
for (const value of b) if (!a.has(value)) return false;
return true;
}
async function foo() {
const repacks = await getRepackSkins(ctx);
for (const skin of repacks) {
const md5 = skin.getMd5();
if (stored[md5] != null) {
console.log(`Already computed ${md5}`);
continue;
}
const archiveFiles = await skin.getArchiveFiles();
const archiveFilesMd5 = archiveFiles.map((af) => af.getFileMd5());
const matches = await knex.raw(`
SELECT
skin_md5
FROM
(
SELECT
skin_md5,
COUNT(*) as total_files,
SUM(CASE WHEN file_md5 IN (${archiveFilesMd5
.map((m) => `"${m}"`)
.join(",")}) THEN 1 ELSE 0 END) as matching_files
FROM
archive_files
GROUP BY
skin_md5
) AS t
WHERE
total_files = matching_files;
`);
const filtered = matches
.map((r) => r.skin_md5)
.filter((m) => m != skin_md5);
stored[skin_md5] = filtered;
await KeyValue.update("winampskins.info", stored);
console.log(`Stored that ${skin_md5} matches ${JSON.stringify(filtered)}`);
}
}

View file

@ -103,8 +103,8 @@ export async function followerCount(handler: DiscordEventHandler) {
const user = response.data;
const followerCount = user.followers_count;
const current: { count: number } = await KeyValue.get(FOLLOW_COUNT_KEY);
const currentNumber = current.count;
const current = await KeyValue.get<{ count: number }>(FOLLOW_COUNT_KEY);
const currentNumber = current!.count;
const nextMilestone = currentNumber + FOLLOW_COUNT_BRACKET_SIZE;