fix: precise item classification in catalog audit and repair
Detect badges by items_base.type='b' (authoritative, album gifs as fallback), recognize pet/animals (a0 pet<N>, pet<N> interaction) and system items (effects, bots, sticky notes), and resolve asset names for dot/star classname variants so the audit no longer floods with false missing nitro/icon errors and repair skips non-furni items.
This commit is contained in:
1 parent
b1a1e5125c
commit
d587749b50
5 files changed
+445
-36
No files matched your search
@@ -15,6 +15,7 @@ import {
|
||||
getGamedataRoot,
|
||||
} from "@/lib/services/furni-asset-dirs";
|
||||
import { downloadFile } from "@/lib/services/import/core/download";
|
||||
import { classifyCatalogItem, NO_ASSET_KINDS } from "@/lib/services/item-kind";
|
||||
import { ensureDirectories } from "./furni-import";
|
||||
|
||||
const REPAIR_CONCURRENCY = 6;
|
||||
@@ -141,28 +142,44 @@ export async function repairMissingIcons(
|
||||
...targets.mirrorDirs.map((d) => d.nitroDir),
|
||||
]);
|
||||
|
||||
// Load known badge codes from the album directory so we can skip badge
|
||||
// items — they use .gif files, not .png icons.
|
||||
let badgeSet: Set<string> | null = null;
|
||||
// Badges and other special items (pets, effects, bots, sticky notes) never
|
||||
// ship furni `_icon.png` files — resolve the skip set from the DB so badge
|
||||
// detection is precise (`items_base.type = 'b'`) instead of relying only on
|
||||
// which .gif files happen to exist in the album directory.
|
||||
let skipSet: Set<string> | null = null;
|
||||
try {
|
||||
const gamedataRoot = await getGamedataRoot();
|
||||
if (gamedataRoot) {
|
||||
const badgeFiles = await fs
|
||||
.readdir(path.join(gamedataRoot, "album1584"))
|
||||
.catch(() => [] as string[]);
|
||||
badgeSet = new Set(
|
||||
const albumBadgeSet = new Set(
|
||||
badgeFiles.filter((f) => f.endsWith(".gif")).map((f) => f.slice(0, -4)),
|
||||
);
|
||||
skipSet = albumBadgeSet;
|
||||
}
|
||||
} catch {
|
||||
badgeSet = null;
|
||||
skipSet = null;
|
||||
}
|
||||
|
||||
let items: Array<{ id: number; item_name: string }>;
|
||||
let items: Array<{
|
||||
id: number;
|
||||
item_name: string;
|
||||
type: string;
|
||||
interaction_type: string;
|
||||
}>;
|
||||
try {
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT id, item_name FROM items_base ORDER BY id
|
||||
`)) as unknown as [Array<{ id: number; item_name: string }>, unknown];
|
||||
SELECT id, item_name, type, interaction_type FROM items_base ORDER BY id
|
||||
`)) as unknown as [
|
||||
Array<{
|
||||
id: number;
|
||||
item_name: string;
|
||||
type: string;
|
||||
interaction_type: string;
|
||||
}>,
|
||||
unknown,
|
||||
];
|
||||
items = rows;
|
||||
} catch (err) {
|
||||
onEvent?.({
|
||||
@@ -172,13 +189,24 @@ export async function repairMissingIcons(
|
||||
return { succeeded: 0, failed: 0, skipped: 0 };
|
||||
}
|
||||
|
||||
// Build the authoritative skip set from the DB, supplemented by album .gifs.
|
||||
const dbSkipSet = new Set<string>();
|
||||
for (const item of items) {
|
||||
if (NO_ASSET_KINDS.has(classifyCatalogItem(item))) {
|
||||
dbSkipSet.add(item.item_name);
|
||||
}
|
||||
}
|
||||
for (const code of skipSet ?? []) {
|
||||
dbSkipSet.add(code);
|
||||
}
|
||||
|
||||
const total = items.length;
|
||||
onEvent?.({ type: "started", total });
|
||||
|
||||
const processItem = async (classname: string) => {
|
||||
// Badge items that only exist as .gif files (in album1584) don't have
|
||||
// Special items (badges, pets, effects, bots, sticky notes) don't have
|
||||
// .png icon files to repair. Skip them.
|
||||
if (badgeSet?.has(classname)) {
|
||||
if (dbSkipSet.has(classname)) {
|
||||
return { status: "skipped" } as const;
|
||||
}
|
||||
const fileName = `${classname}_icon.png`;
|
||||
@@ -269,6 +297,11 @@ export async function repairMissingIcons(
|
||||
`${classname.replace(/\*/g, "_")}_icon.png`,
|
||||
]
|
||||
: []),
|
||||
// Classnames with dots (`post.it`) are stored on disk with
|
||||
// underscores (`post_it_icon.png`).
|
||||
...(classname.includes(".")
|
||||
? [`${classname.replace(/\./g, "_")}_icon.png`]
|
||||
: []),
|
||||
];
|
||||
|
||||
for (const iconName of iconCandidates) {
|
||||
|
||||
Reference in new issue
Block a user