112 lines
3.2 KiB
JavaScript
112 lines
3.2 KiB
JavaScript
|
|
import path from "path";
|
||
|
|
import { fileURLToPath } from "url";
|
||
|
|
import { readFileSync } from "fs";
|
||
|
|
import https from "https";
|
||
|
|
import http from "http";
|
||
|
|
import { dbPool, getIcons, setIconCache } from "./db.js";
|
||
|
|
import { s3Config, uploadToS3 } from "./s3.js";
|
||
|
|
|
||
|
|
const __filename = fileURLToPath(import.meta.url);
|
||
|
|
const __dirname = path.dirname(__filename);
|
||
|
|
|
||
|
|
// MHF UUID 매핑 로드
|
||
|
|
const entityToMHF = JSON.parse(
|
||
|
|
readFileSync(path.join(__dirname, "..", "data", "entityToMHF.json"), "utf-8")
|
||
|
|
);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* URL에서 이미지 다운로드
|
||
|
|
*/
|
||
|
|
function downloadImage(url) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const protocol = url.startsWith("https") ? https : http;
|
||
|
|
protocol
|
||
|
|
.get(url, (res) => {
|
||
|
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
||
|
|
return downloadImage(res.headers.location)
|
||
|
|
.then(resolve)
|
||
|
|
.catch(reject);
|
||
|
|
}
|
||
|
|
if (res.statusCode !== 200) {
|
||
|
|
reject(new Error(`HTTP ${res.statusCode}`));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const chunks = [];
|
||
|
|
res.on("data", (chunk) => chunks.push(chunk));
|
||
|
|
res.on("end", () => resolve(Buffer.concat(chunks)));
|
||
|
|
res.on("error", reject);
|
||
|
|
})
|
||
|
|
.on("error", reject);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 온디맨드 아이콘 가져오기 (없으면 다운로드/저장)
|
||
|
|
*/
|
||
|
|
async function getIconUrl(name, type, modId = "minecraft") {
|
||
|
|
const iconsCache = getIcons();
|
||
|
|
|
||
|
|
// item 타입일 때 실제로 blocks인지 items인지 DB에서 확인
|
||
|
|
let actualType = type;
|
||
|
|
if (type === "item") {
|
||
|
|
const [itemRows] = await dbPool.query(
|
||
|
|
"SELECT 1 FROM items WHERE name = ? AND mod_id = ? LIMIT 1",
|
||
|
|
[name, modId]
|
||
|
|
);
|
||
|
|
if (itemRows.length === 0) {
|
||
|
|
const [blockRows] = await dbPool.query(
|
||
|
|
"SELECT 1 FROM blocks WHERE name = ? AND mod_id = ? LIMIT 1",
|
||
|
|
[name, modId]
|
||
|
|
);
|
||
|
|
if (blockRows.length > 0) {
|
||
|
|
actualType = "block";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 캐시 키: type:name 형식
|
||
|
|
const cacheKey = `${actualType}:${name}`;
|
||
|
|
|
||
|
|
// 캐시에 있으면 반환
|
||
|
|
if (iconsCache[cacheKey]) return iconsCache[cacheKey];
|
||
|
|
|
||
|
|
// 소스 URL 결정
|
||
|
|
let sourceUrl;
|
||
|
|
let s3Key;
|
||
|
|
|
||
|
|
if (actualType === "entity") {
|
||
|
|
const mhfUuid = entityToMHF[name];
|
||
|
|
if (mhfUuid) {
|
||
|
|
sourceUrl = `https://mc-heads.net/avatar/${mhfUuid}/64`;
|
||
|
|
} else {
|
||
|
|
sourceUrl = `https://mc.nerothe.com/img/1.21/minecraft_${name}_spawn_egg.png`;
|
||
|
|
}
|
||
|
|
s3Key = `icon/entities/${modId}_entity_${name}.png`;
|
||
|
|
} else {
|
||
|
|
sourceUrl = `https://mc.nerothe.com/img/1.21/minecraft_${name}.png`;
|
||
|
|
s3Key = `icon/${actualType}s/${modId}_${actualType}_${name}.png`;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const imageData = await downloadImage(sourceUrl);
|
||
|
|
const publicUrl = await uploadToS3(s3Config.bucket, s3Key, imageData);
|
||
|
|
|
||
|
|
// DB 업데이트
|
||
|
|
const table = actualType === "entity" ? "entities" : `${actualType}s`;
|
||
|
|
await dbPool.query(
|
||
|
|
`UPDATE ${table} SET icon = ? WHERE name = ? AND mod_id = ?`,
|
||
|
|
[publicUrl, name, modId]
|
||
|
|
);
|
||
|
|
|
||
|
|
// 캐시 업데이트
|
||
|
|
setIconCache(cacheKey, publicUrl);
|
||
|
|
console.log(`[Icon] 저장 완료: ${cacheKey} → ${publicUrl}`);
|
||
|
|
return publicUrl;
|
||
|
|
} catch (error) {
|
||
|
|
console.error(`[Icon] 다운로드 실패: ${cacheKey} - ${error.message}`);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export { getIconUrl };
|