Enrich IQDB candidates; link downloads the e621 original
- IQDB responses carry no preview/file data, so candidates only showed an ID; the SPA now enriches them with one batched posts lookup (preview, rating, score, favourites, dimensions, tag preview) - Candidate tiles are selectable instead of instantly resolving: picking one shows its info and an explicit 'Link selected post' button - Linking a post now fetches the e621 original into the library and drops the staged upload; when the staged file's MD5 already equals the post's file, the staged copy is moved instead (identical bytes) - Keep the file URL in stored e621 metadata; sanitize the new candidate fields server-side
This commit is contained in:
@@ -322,6 +322,26 @@ export interface E621IqdbResult {
|
||||
preview_url: string | null;
|
||||
rating: string | null;
|
||||
md5: string | null;
|
||||
score_total: number | null;
|
||||
fav_count: number | null;
|
||||
width: number | null;
|
||||
height: number | null;
|
||||
tags_preview: string[];
|
||||
}
|
||||
|
||||
function flattenTagPreview(tags: unknown, limit = 8): string[] {
|
||||
if (!tags || typeof tags !== "object") return [];
|
||||
const out: string[] = [];
|
||||
for (const value of Object.values(tags as Record<string, unknown>)) {
|
||||
if (!Array.isArray(value)) continue;
|
||||
for (const tag of value) {
|
||||
if (typeof tag === "string" && !out.includes(tag)) {
|
||||
out.push(tag);
|
||||
if (out.length >= limit) return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function normalizeIqdbEntry(entry: unknown): E621IqdbResult | null {
|
||||
@@ -344,12 +364,18 @@ function normalizeIqdbEntry(entry: unknown): E621IqdbResult | null {
|
||||
: typeof post.id === "number"
|
||||
? post.id
|
||||
: null;
|
||||
const scoreTotal = post.score;
|
||||
return {
|
||||
post_id: postId,
|
||||
score: typeof record.score === "number" ? record.score : null,
|
||||
preview_url: typeof preview.url === "string" ? preview.url : null,
|
||||
rating: typeof post.rating === "string" ? post.rating : null,
|
||||
md5: typeof file.md5 === "string" ? file.md5 : null,
|
||||
score_total: typeof scoreTotal === "number" ? scoreTotal : null,
|
||||
fav_count: typeof post.fav_count === "number" ? post.fav_count : null,
|
||||
width: typeof file.width === "number" ? file.width : null,
|
||||
height: typeof file.height === "number" ? file.height : null,
|
||||
tags_preview: flattenTagPreview(post.tags),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -371,13 +397,44 @@ export async function iqdbSearch(
|
||||
: Array.isArray((data as { matches?: unknown[] })?.matches)
|
||||
? (data as { matches: unknown[] }).matches
|
||||
: [];
|
||||
return list
|
||||
const candidates = list
|
||||
.map(normalizeIqdbEntry)
|
||||
.filter(
|
||||
(entry): entry is E621IqdbResult =>
|
||||
entry !== null && (entry.post_id !== null || entry.preview_url !== null),
|
||||
)
|
||||
.slice(0, 10);
|
||||
|
||||
// The IQDB payload carries no preview/file data; enrich with one batched
|
||||
// post lookup so candidates can show what they actually are.
|
||||
const ids = candidates
|
||||
.map((candidate) => candidate.post_id)
|
||||
.filter((id): id is number => typeof id === "number");
|
||||
if (ids.length > 0) {
|
||||
try {
|
||||
const posts = await fetchPostsByIds(credentials, ids, signal);
|
||||
const byId = new Map(posts.map((post) => [post.id, post]));
|
||||
return candidates.map((candidate) => {
|
||||
const post =
|
||||
candidate.post_id !== null ? byId.get(candidate.post_id) : undefined;
|
||||
if (!post) return candidate;
|
||||
return {
|
||||
...candidate,
|
||||
preview_url: post.preview.url ?? candidate.preview_url,
|
||||
rating: post.rating ?? candidate.rating,
|
||||
md5: post.file.md5 ?? candidate.md5,
|
||||
score_total: post.score.total,
|
||||
fav_count: post.fav_count,
|
||||
width: post.file.width,
|
||||
height: post.file.height,
|
||||
tags_preview: flattenTagPreview(post.tags),
|
||||
};
|
||||
});
|
||||
} catch {
|
||||
// Keep the base candidates when the enrichment request fails.
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
export function postIsBlacklisted(
|
||||
|
||||
@@ -113,6 +113,11 @@ export interface E621IqdbCandidate {
|
||||
preview_url: string | null;
|
||||
rating: string | null;
|
||||
md5: string | null;
|
||||
score_total?: number | null;
|
||||
fav_count?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
tags_preview?: string[];
|
||||
}
|
||||
|
||||
export interface TempUpload {
|
||||
|
||||
Reference in New Issue
Block a user