webamp/packages/skin-database/tasks/refresh.ts
2025-07-09 21:05:54 -07:00

121 lines
3 KiB
TypeScript

import UserContext from "../data/UserContext";
import SkinModel from "../data/SkinModel";
import { knex } from "../db";
import { setHashesForSkin } from "../skinHash";
import Shooter from "../shooter";
import { screenshot } from "./screenshotSkin";
import * as Skins from "../data/skins";
import { SkinType } from "../types";
import { getSkinType } from "../addSkin";
// TODO Move this into the function so that we clean up on each run?
async function getExtractionError(skin: SkinModel): Promise<string | null> {
try {
await skin.getZip();
} catch (e) {
return e.message;
}
return null;
}
// A task to download a skin and refresh all the data we scrape from it
export async function getSkinsToRefresh(
ctx: UserContext,
n: number,
neverDone = false
): Promise<SkinModel[]> {
let query = knex("skins")
.leftJoin("refreshes", "skins.md5", "refreshes.skin_md5")
.where("skin_type", 1)
.orderBy("refreshes.timestamp", "asc")
.limit(n);
if (neverDone) {
query = query.where("refreshes.skin_md5", null);
}
const skins = await query.select();
return skins.map((row) => new SkinModel(ctx, row));
}
export async function refreshSkins(
skins: SkinModel[],
options: { noScreenshot: boolean }
): Promise<void> {
if (options.noScreenshot) {
while (skins.length > 0) {
const skin = skins.pop();
// @ts-ignore
await refresh(skin, null);
}
return;
}
const shooterLogger = () => {
// Don't log
};
await Shooter.withShooter(async (shooter: Shooter) => {
for (const [i, skin] of skins.entries()) {
await refresh(skin, shooter);
// We end up caching a lot of stuff (the whole skin/zip) on the model, so we can't just leave these around for the whole process.
delete skins[i];
}
}, shooterLogger);
}
export async function _refresh(
skin: SkinModel,
shooter: Shooter | null
): Promise<void> {
const extractionError = await getExtractionError(skin);
if (extractionError != null) {
throw new Error(extractionError);
}
await setHashesForSkin(skin);
await Skins.setContentHash(skin.getMd5());
let skinType: SkinType;
try {
skinType = await getSkinType(await skin.getZip());
} catch (_e) {
throw new Error("Not a skin (no main.bmp/skin.xml)");
}
if (skinType !== "CLASSIC") {
// TODO:
// Reviews are invalid
// Delete from algolia
// Delete screenshot
// Tweets are invalid
throw new Error("Skin type mismatch");
}
// Retake screenshot
if (shooter != null) {
await screenshot(skin, shooter);
}
await knex("refreshes").insert({
skin_md5: skin.getMd5(),
});
}
export async function refresh(
skin: SkinModel,
shooter: Shooter | null
): Promise<void> {
if (skin.getSkinType() !== "CLASSIC") {
throw new Error("Can't refresh non-classic skins");
}
try {
await _refresh(skin, shooter);
} catch (e) {
console.error(e);
await knex("refreshes").insert({
skin_md5: skin.getMd5(),
error: e.message,
});
return;
}
}