-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathscript.min.js
32 lines (31 loc) · 38.2 KB
/
script.min.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const locations = [{ location: "티르코네일", npc: "상인 네루" }, { location: "던바튼", npc: "상인 누누" }, { location: "카브", npc: "상인 아루" }, { location: "반호르", npc: "상인 라누" }, { location: "이멘마하", npc: "상인 메루" }, { location: "탈틴", npc: "상인 베루" }, { location: "타라", npc: "상인 에루" }, { location: "벨바스트", npc: "상인 피루" }, { location: "스카하", npc: "상인 세누" }, { location: "켈라베이스", npc: "테일로" }, { location: "카루", npc: "귀넥" }, { location: "코르", npc: "리나" }, { location: "오아시스", npc: "얼리" }, { location: "필리아", npc: "켄" }, { location: "발레스", npc: "카디" }, { location: "페라", npc: "데위" }, { location: "칼리다", npc: "모락" },]; let serverObject = { 류트: 42, 하프: 24, 울프: 15, 만돌린: 15 }; const url = "https://open.api.nexon.com/mabinogi/v1/npcshop/list"; function hexToRgb(e) { let t = parseInt(e.slice(1, 3), 16), n = parseInt(e.slice(3, 5), 16), l = parseInt(e.slice(5, 7), 16); return `(${t}, ${n}, ${l})` } let fetchedData = []; function isWithinTolerance(e, t, n) { let l = Math.abs(t.r - e.r), o = Math.abs(t.g - e.g), a = Math.abs(t.b - e.b); return l <= n && o <= n && a <= n } function parseRgb(e) { let t = e.replace(/[^0-9]/g, " ").trim().split(/\s+/).map(Number); return 3 !== t.length || t.some(e => isNaN(e) || void 0 === e) ? null : { r: t[0], g: t[1], b: t[2] } } let filterToggle = 0; function filterData() { let e = parseInt(document.getElementById("toleranceInput").value, 10) || 10, t = document.querySelectorAll("#filterAbove .filterInput"), n = Array.from(t).map(e => { let t = e.querySelector(".filterAndOr"), n = t ? t.value : "and", l = e.querySelector(".filterType").value, o = e.querySelector(".colorInput").value, a = o ? parseRgb(o) : null; return a ? { logicalOperator: n, type: l, color: a } : null }).filter(e => null !== e); document.querySelectorAll(".cell").forEach(t => { let l = t.querySelector(".A"), o = t.querySelector(".B"), a = t.querySelector(".C"), r = l ? window.getComputedStyle(l).backgroundColor : null, i = o ? window.getComputedStyle(o).backgroundColor : null, c = a ? window.getComputedStyle(a).backgroundColor : null, s = !0; n.forEach((t, n) => { let { type: d, color: u, logicalOperator: p } = t; if (u) { let m; if ("anywhere" === d) m = [r, i, c].some((t, n) => { let r = t && isWithinTolerance(u, parseRgb(t), e); return r && (0 === n ? l.nextSibling.classList.add("filterPassColor") : 1 === n ? o.nextSibling.classList.add("filterPassColor") : 2 === n && a.nextSibling.classList.add("filterPassColor")), r }); else { let y = "outer" === d ? l : "roman" === d ? o : "inner" === d ? a : null, g = "outer" === d ? r : "roman" === d ? i : "inner" === d ? c : null; (m = g && isWithinTolerance(u, parseRgb(g), e)) && y && y.nextSibling.classList.add("filterPassColor") } 0 === n ? s = m : "and" === p ? s = s && m : "or" === p && (s = s || m) } }), s ? t.classList.remove("hidden") : t.classList.add("hidden") }); let l = document.querySelectorAll(".location"); l.forEach(e => { let t = e.querySelectorAll(".table .cell"), n = Array.from(t).every(e => e.classList.contains("hidden")); n && e.classList.add("hidden") }), filterToggle = 1 } function resetFilterData() { document.querySelectorAll("#content > .location").forEach(e => { e.classList.remove("hidden") }), document.querySelectorAll(".cell").forEach(e => { e.classList.remove("hidden") }); let e = document.querySelectorAll(".filterPassColor"); e.forEach(e => { e.classList.remove("filterPassColor") }), filterToggle = 0 } async function fetchData() { let e = [], t = document.getElementById("serverSelect").value, n = document.getElementById("channelInput").value; n > serverObject[t] && (n = String(serverObject[t]), document.getElementById("channelInput").value = String(serverObject[t])); let l = document.getElementById("locationSelect").value, o = document.getElementById("apiKeyInput").value, a = { accept: "application/json", "x-nxopen-api-key": o }, r = 0; if (fetchedData.forEach(o => { o.serverNum === n && o.serverName == t && o.location == l && (e.push(o), r = 1) }), !r) { let i = document.getElementById("loadingOverlay"); for (let { location: c, npc: s } of (i.style.display = "flex", locations)) if ("전체" == l || l == c) { let d = async (e, t, n, l, o = 10) => { for (let a = 0; a < o; a++)try { let r = await fetchLocationData(e, t, n, l); return r } catch (i) { if (displayError(i, a + 1), console.log(`Attempt ${a + 1} failed: ${i.message}`), a < o - 1) console.log("Retrying in 1 minute..."), await new Promise(e => setTimeout(e, 6e4)); else throw console.log("Max retries reached. Exiting..."), i } }; try { let u = await d(s, t, n, a, 10), p = n, m = fetchedData.some(e => e.serverName == t && e.serverNum == n && e.location == c); m ? e.push({ serverName: t, serverNum: p, location: c, items: u }) : (e.push({ serverName: t, serverNum: p, location: c, items: u }), fetchedData.push({ serverName: t, serverNum: p, location: c, items: u })) } catch (y) { return console.log(y), displayError(y, 0), 0 } } } return e } async function fetchLocationData(e, t, n, l) { let o = new URLSearchParams({ npc_name: e, server_name: t, channel: n }), a = await fetch(`https://open.api.nexon.com/mabinogi/v1/npcshop/list?${o}`, { headers: l }); if (!a.ok) { let r = await a.text(); throw Error(`HTTP error! status: ${a.status}, ${r}`) } let i = await a.json(), c = processShops(i.shop); return c } function processShops(e) { let t = [], n = e.filter(e => "주머니" === e.tab_name); if (0 == n.length) throw JSON.stringify({ error: { name: "Error", message: "주머니 탭을 찾을 수 없습니다." } }); for (let l of n) for (let o of l.item) { let a = o.item_display_name, r = o.image_url; if (r.includes("img")) { let i = findColorData(r); t.push({ itemDisplayName: a, colors: i, imageUrl: r }) } } return t } document.getElementById("filterButton").addEventListener("click", function () { 1 != filterToggle ? filterData() : resetFilterData() }), document.getElementById("autoFilter").addEventListener("change", function () { this.checked ? filterData() : resetFilterData() }), window.onload = function () { let e = localStorage.getItem("apiKey"); e && (document.getElementById("apiKeyInput").value = e); let t = localStorage.getItem("server"), n = localStorage.getItem("channel"); if (t && (document.getElementById("serverSelect").value = t), n) { let l = serverObject[t]; channelInput.innerHTML = ""; for (let o = 1; o <= l; o++) { let a = document.createElement("option"); a.value = o, a.textContent = o, channelInput.appendChild(a) } document.getElementById("channelInput").value = n } let r = document.getElementById("locationSelect"); locations.forEach(({ location: e }) => { let t = document.createElement("option"); t.value = e, t.textContent = e, r.appendChild(t) }); let i = localStorage.getItem("tolerance"); i && (document.getElementById("toleranceInput").value = i) }, document.getElementById("apiKeyInput").addEventListener("input", function () { let e = this.value; localStorage.setItem("apiKey", e) }), document.getElementById("serverSelect").addEventListener("change", function () { let e = this.value; localStorage.setItem("server", e) }), document.getElementById("channelInput").addEventListener("input", function () { let e = this.value; localStorage.setItem("channel", e) }); let decodeData; async function loadDecodeData() { try { let e = await fetch("combined_result.json"); decodeData = await e.json() } catch (t) { console.error("Error loading JSON data:", t) } } async function initialize() { await loadDecodeData() } function extractColors(e) { let t = e.split("?q=")[1], n = t.match(/.{1,18}/g).map(e => 18 === e.length ? e.slice(4, -2) : e.slice(4)).map(e => e.match(/.{1,4}/g)); return n.slice(0, 3) } function findColorData(e) { let t = extractColors(e), n = { A: [], B: [], C: [] }, l = ["A", "B", "C"]; return t.forEach((e, t) => { e.forEach((e, o) => { let a = l[t], r = decodeData[a]?.[o + 1]?.upper?.[e.slice(0, 2).toUpperCase()] * 16 + decodeData[a]?.[o + 1]?.lower?.[e.slice(2).toUpperCase()]; n[a].push(void 0 !== r ? r : null) }) }), function e(t) { let n = {}; for (let l in t) n[l] = t[l].map(e => (null !== e ? e : 17).toString(16).padStart(2, "0")).join("").toString(); return n }(n) } function displayError(e, t) { let n = document.getElementById("content"), l, o = ""; try { let a = e.message.substring(e.message.indexOf("{")); switch ((l = JSON.parse(a)).error.name) { case "OPENAPI00001": o = "내부 서버 오류 - 서버 내부 오류"; break; case "OPENAPI00002": o = "접근 거부 - 권한 없는 접근"; break; case "OPENAPI00003": o = "잘못된 식별자"; break; case "OPENAPI00004": o = "누락되었거나 잘못된 매개변수"; break; case "OPENAPI00005": o = "잘못된 API 키"; break; case "OPENAPI00006": o = "잘못된 게임 또는 API 경로"; break; case "OPENAPI00007": o = "요청 과다 - API 호출 한도 초과"; break; case "OPENAPI00009": o = `데이터 준비 중 - 1분 후 자동으로 다시 시도합니다. ${t}번째 시도중...`; break; case "OPENAPI00010": o = "서비스 점검 중"; break; case "OPENAPI00011": o = "API 점검 중"; break; default: o = "알 수 없는 오류 - 다시 시도해 주세요." } } catch (r) { console.log("JSON 파싱 오류:", r), o = "JSON 파싱 중 오류가 발생했습니다. 응답이 올바른 JSON 형식이 아닙니다." } n.innerHTML = `<div style="color: red;">${o}</div>` } async function renderData(e) { let t = document.getElementById("content"); for (let { location: n, items: l } of (t.innerHTML = "", e)) { let o = document.createElement("div"); o.className = "location", o.innerHTML = `<div class="location_name">${n}<div>`, t.appendChild(o); let a = document.createElement("div"); for (let { itemDisplayName: r, colors: i, imageUrl: c } of (a.className = "table", l)) { let s = document.createElement("div"); s.className = "cell"; let d = document.createElement("div"); d.className = "container"; let u = document.createElement("div"); u.className = "itemName"; let p = document.createElement("div"); p.className = "itemDetail"; let m = document.createElement("div"); for (let [y, g] of (m.className = "bgColor", u.innerHTML = `${r}`, Object.entries(i))) { let h = document.createElement("div"); h.className = "color-box-box"; let v = document.createElement("div"); v.className = `color-box ${y}`, v.style.backgroundColor = "#" + g, v.style.width = "20px", v.style.height = "20px", h.appendChild(v), h.innerHTML += `<p class="color-box-color">${hexToRgb("#" + g)}</p><br>`, m.appendChild(h) } let [f, E, I] = Object.values(i).slice(0, 3); mabibase_color = [f, E, I].map(e => `0x${e}`).join("%2C"); let $ = document.createElement("div"); $.style.flex = "1", $.className = "item-img", $.style.display = "flex", $.style.justifyContent = "center", $.style.alignItems = "center"; let x = document.createElement("img"), B = await guichana(r, i); x.src = B.src, x.onerror = function () { x.src = "no_image.png" }, x.alt = r, x.style.height = "56px", x.style.objectFit = "contain", x.style.paddingBottom = "3px"; let L = document.createElement("img"); L.src = c, L.style.maxWidth = "64px", $.appendChild(L), $.appendChild(x), p.appendChild(m), p.appendChild($), d.appendChild(u), d.appendChild(p), s.appendChild(d), a.appendChild(s) } o.appendChild(a) } document.querySelectorAll(".cell") } initialize(), document.getElementById("locationSelect").addEventListener("change", () => { lastNextResetTime = null, document.getElementById("fetchButton").dispatchEvent(new Event("click")) }), document.getElementById("autoFetchCheckbox").addEventListener("change", e => { e.target.checked && "granted" !== Notification.permission && Notification.requestPermission().then(e => { }), document.getElementById("fetchButton").dispatchEvent(new Event("click")) }), document.getElementById("channelInput").addEventListener("change", function () { lastNextResetTime = null, document.getElementById("fetchButton").dispatchEvent(new Event("click")) }), document.getElementById("fetchButton").addEventListener("click", async () => { let e = document.getElementById("serverSelect").value, t = document.getElementById("channelInput").value, n = document.getElementById("apiKeyInput").value; if (!e || !t || !n) { alert("빈 값이 있으면 검색이 되지 않습니다."), e ? t ? n || document.getElementById("apiKeyInput").focus() : document.getElementById("channelInput").focus() : document.getElementById("serverSelect").focus(); return } if (lastNextResetTime && lastNextResetTime.getTime() === nextResetTime.getTime()) return; let l = await fetchData(); l && (await renderData(l), document.getElementById("autoFilter").checked && filterData(), lastNextResetTime = nextResetTime), document.getElementById("loadingOverlay").style.display = "none" }), document.getElementById("apiKeyInput").addEventListener("input", function () { let e = this.value; localStorage.setItem("apiKey", e) }), document.getElementById("serverSelect").addEventListener("change", function () { let e = this.value; localStorage.setItem("server", e), lastNextResetTime = null, document.getElementById("fetchButton").dispatchEvent(new Event("click")) }), document.getElementById("channelInput").addEventListener("input", function () { let e = this.value; localStorage.setItem("channel", e) }), document.getElementById("toleranceInput").addEventListener("input", function () { let e = this.value; localStorage.setItem("tolerance", e) }); const totalMinutesInDay = 1440, intervalMinutes = 36, totalIntervals = 40; let previousResetTime = null, timerId = null, nextResetTime = null, lastNextResetTime = null, resetTime = null; function updateNextResetTime() { let e = new Date, t = 60 * e.getHours() + e.getMinutes(), n = (Math.floor(t / 36) + 1) % 40 * 36; (nextResetTime = new Date).setHours(Math.floor(n / 60), n % 60, 0, 0); let l = nextResetTime - e; l < 0 && nextResetTime.setDate(nextResetTime.getDate() + 1), (resetTime = new Date(nextResetTime)).setMinutes(resetTime.getMinutes() - 36); let o = Math.ceil((nextResetTime - e) / 1e3); previousResetTime && previousResetTime.getTime() !== nextResetTime.getTime() && (console.log("시간 변경"), fetchedData = [], document.getElementById("autoFetchCheckbox").checked && (timerId && clearTimeout(timerId), timerId = setTimeout(async () => { let e = await fetchData(); if (e) { await renderData(e), document.getElementById("autoFilter").checked && filterData(); let t = document.getElementById("autoFetchSound").value; if ("none" != t) { let n = new Audio(t); n.currentTime = 0, n.play() } windowNotification("주머니 업데이트") } document.getElementById("loadingOverlay").style.display = "none" }, 3e4))), previousResetTime = nextResetTime, document.getElementById("next-time").innerHTML = `${resetTime.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} - ${nextResetTime.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} <span id="secondsRemaining">⌛${o}초</span> 후 초기화` } function windowNotification(e) { "granted" !== Notification.permission ? Notification.requestPermission().then(t => { "granted" === t && showWindowNotification(e) }) : (showWindowNotification(e), console.log(e)) } function showWindowNotification(e) { new Notification("주머니 상점 정보", { body: e, icon: "./favicon.ico" }).onclick = () => { window.focus() } } document.addEventListener("DOMContentLoaded", () => { updateNextResetTime(), setInterval(updateNextResetTime, 1e3) }); let channelingFetchedData = [], itemNameList = { 작물: [{ "튼튼한 달걀 주머니": [] }, { "튼튼한 감자 주머니": [] }, { "튼튼한 옥수수 주머니": [] }, { "튼튼한 밀 주머니": [] }, { "튼튼한 보리 주머니": [] },], 방직: [{ "튼튼한 양털 주머니": [] }, { "튼튼한 거미줄 주머니": [] }, { "튼튼한 가는 실뭉치 주머니": [] }, { "튼튼한 굵은 실뭉치 주머니": [] },], 가죽: [{ "튼튼한 저가형 가죽 주머니": [] }, { "튼튼한 일반 가죽 주머니": [] }, { "튼튼한 고급 가죽 주머니": [] }, { "튼튼한 최고급 가죽 주머니": [] },], 옷감: [{ "튼튼한 저가형 옷감 주머니": [] }, { "튼튼한 일반 옷감 주머니": [] }, { "튼튼한 고급 옷감 주머니": [] }, { "튼튼한 최고급 옷감 주머니": [] },], 실크: [{ "튼튼한 저가형 실크 주머니": [] }, { "튼튼한 일반 실크 주머니": [] }, { "튼튼한 고급 실크 주머니": [] }, { "튼튼한 최고급 실크 주머니": [] },], 꽃바구니: [{ "튼튼한 꽃바구니": [] }] }; const serverSelect = document.getElementById("serverSelect"), channelInput = document.getElementById("channelInput"); function updateChannelOptions() { let e = serverSelect.value, t = serverObject[e]; channelInput.innerHTML = ""; for (let n = 1; n <= t; n++) { let l = document.createElement("option"); l.value = n, l.textContent = n, channelInput.appendChild(l) } } function convertRgbToHex(e) { return e.map(e => { let t = e.match(/\d+/g); if (t && 3 === t.length) { let [n, l, o] = t.map(Number); return `${[n, l, o].map(e => e.toString(16).padStart(2, "0").toUpperCase()).join("")}` } return null }) } function compareColors(e, t) { return Object.values(e).every((e, n) => e.toLowerCase() === t[n].toLowerCase()) } function resetItemNameList() { itemNameList = { 작물: [{ "튼튼한 달걀 주머니": [] }, { "튼튼한 감자 주머니": [] }, { "튼튼한 옥수수 주머니": [] }, { "튼튼한 밀 주머니": [] }, { "튼튼한 보리 주머니": [] },], 방직: [{ "튼튼한 양털 주머니": [] }, { "튼튼한 거미줄 주머니": [] }, { "튼튼한 가는 실뭉치 주머니": [] }, { "튼튼한 굵은 실뭉치 주머니": [] },], 가죽: [{ "튼튼한 저가형 가죽 주머니": [] }, { "튼튼한 일반 가죽 주머니": [] }, { "튼튼한 고급 가죽 주머니": [] }, { "튼튼한 최고급 가죽 주머니": [] },], 옷감: [{ "튼튼한 저가형 옷감 주머니": [] }, { "튼튼한 일반 옷감 주머니": [] }, { "튼튼한 고급 옷감 주머니": [] }, { "튼튼한 최고급 옷감 주머니": [] },], 실크: [{ "튼튼한 저가형 실크 주머니": [] }, { "튼튼한 일반 실크 주머니": [] }, { "튼튼한 고급 실크 주머니": [] }, { "튼튼한 최고급 실크 주머니": [] },], 꽃바구니: [{ "튼튼한 꽃바구니": [] }] } } async function fetchLocationByServers(e, t) { let n = locations.find(t => t.location === e), l = { accept: "application/json", "x-nxopen-api-key": document.getElementById("apiKeyInput").value }; for (let [o, a] of Object.entries(serverObject)) if (!document.getElementById("channelingServer").checked || (console.log(`체크박스가 체크되었습니다. 선택된 서버: ${t} 현재서버: ${o}`), t == o)) { for (let r = 1; r <= a; r++)if (11 !== r) try { r = r.toString(); let i = fetchedData.some(t => t.serverName == o && t.serverNum == r && t.location == e); if (!i) { let c = await fetchLocationData(n.npc, o, r, l), s = e; fetchedData.push({ serverName: o, serverNum: r, location: s, items: c }) } } catch (d) { console.log(`Error fetching data for server ${o} ${r}:`, d), displayError(d, 0); return } } } async function channelingRender(e, t, n, l) {
let o = [], a = document.getElementById("modal-pouch"); a.innerHTML = "", document.getElementById("modal-time").innerHTML = ""; let r = document.getElementById("serverSelect").value; modalBody.innerHTML = `
<div class="spinner"></div>
`, modal.style.display = "flex", modal.style.display = "flex"; let i = locations.find(t => t.location === e); if (!i) { console.error("해당 location을 찾을 수 없습니다."); return } i.npc; let c = '<div class="modal-header">'; c += `<div class="modal-right">
<h2>${e} ${n}</h2>`, c += '<div class="modal-color">', t.forEach((e, t) => {
c += `
<div class="modal-color-div">
<div class="color-box color_0${t + 1}"
style="background-color: #${e}; width: 20px; height: 20px; margin:0;"></div>
<p style="margin:0;">${hexToRgb(`#${e}`)}</p>
</div>
`}), c += "</div></div>", c += '<div id="modal-set"></div>', c += "</div>"; let s = "", d = { 류트: [], 하프: [], 울프: [], 만돌린: [] }; for (let [u, p] of (await fetchLocationByServers(e, r), Object.entries(serverObject))) if (!document.getElementById("channelingServer").checked || serverSelect.value == u) { for (let m in fetchedData.forEach(e => { e.serverName == u && e.items.forEach(n => { if (n.colors.A.toUpperCase() === t[0] && n.colors.B.toUpperCase() === t[1]) { o.some(e => e[1] === n.itemDisplayName) || o.push([n.colors, n.itemDisplayName]); let l = n.itemDisplayName; Object.values(itemNameList).forEach(t => { t.forEach(t => { t.hasOwnProperty(l) && !t[l].includes(e.serverNum) && t[l].push(e.serverNum) }) }) } }) }), c += `<div class="serverName"><h2>${u}</h2></div>`, itemNameList) if (itemNameList.hasOwnProperty(m)) { let y = [], g = "", h = 0; itemNameList[m].forEach(e => { let t = Object.keys(e)[0], n = e[t]; 0 != n.length && (h = 1, g += `<div class="chanitems" style="display:flex;"><div class="chanItemName"><p>${t}<p></div>`, g += `<div><p>${n.join(", ")}</p></div></div>`) }), h && (c += g); let v = itemNameList[m].every(e => { let t = Object.values(e)[0]; return t.length > 0 && y.push(t[0]), t.length > 0 }); v && "꽃바구니" != m && d[u].push(`[${m} - ${y.join(", ")}]`) } resetItemNameList() } a.innerHTML = ""; let f = ["튼튼한 달걀 주머니", "튼튼한 감자 주머니", "튼튼한 옥수수 주머니", "튼튼한 밀 주머니", "튼튼한 보리 주머니", "튼튼한 양털 주머니", "튼튼한 거미줄 주머니", "튼튼한 가는 실뭉치 주머니", "튼튼한 굵은 실뭉치 주머니", "튼튼한 저가형 가죽 주머니", "튼튼한 일반 가죽 주머니", "튼튼한 고급 가죽 주머니", "튼튼한 최고급 가죽 주머니", "튼튼한 저가형 옷감 주머니", "튼튼한 일반 옷감 주머니", "튼튼한 고급 옷감 주머니", "튼튼한 최고급 옷감 주머니", "튼튼한 저가형 실크 주머니", "튼튼한 일반 실크 주머니", "튼튼한 고급 실크 주머니", "튼튼한 최고급 실크 주머니", "튼튼한 꽃바구니"].map(e => o.find(t => t[1] === e)).filter(e => void 0 !== e); async function E() { for (let e of f) { let t = document.createElement("img"), n = await guichana(e[1], e[0]); t.src = n.src, t.alt = "Pouch Image", a.appendChild(t) } } for (let [I, $] of (E(), showModal(c), Object.entries(d))) 0 != $.length && (s += `<p class="set-check">${I} ${$.join(" ")}</p>`); return document.getElementById("modal-time").innerHTML = `<div class="modal-time">${new Date().toLocaleDateString("ko-KR")} | ${resetTime.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: !0 })} - ${nextResetTime.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: !0 })}</div>`, document.getElementById("modal-set").innerHTML = s, []
} updateChannelOptions(), serverSelect.addEventListener("change", updateChannelOptions), document.addEventListener("DOMContentLoaded", function () { let e = document.getElementById("modal-content"), t = document.getElementById("content"), n = document.getElementById("channeling-tooltip"); e.addEventListener("mouseover", function (e) { let t = e.target.closest("div"), l = document.querySelector("#modal-close"); t && !l.contains(e.target) ? (n.innerHTML = `클릭 시 이미지 클립보드에 복사`, n.style.display = "block") : n.style.display = "none" }), e.addEventListener("mousemove", function (e) { n.style.left = e.pageX + 10 + "px", n.style.top = e.pageY + 10 + "px" }), e.addEventListener("mouseout", function (e) { e.target.closest("div") && (n.style.display = "none") }), t.addEventListener("click", function (e) { let t = e.target.closest(".cell"); if (t) { let n = t.querySelector(".container"); if (n) { if (e.target.classList.contains("color-box-color")) navigator.clipboard.writeText(e.target.textContent).then(() => { showNotification(`"${e.target.textContent}"가 복사되었습니다.`) }).catch(e => { console.error("복사 실패:", e) }); else { let l = n.querySelector(".itemName").textContent, o = Array.from(n.querySelectorAll(".bgColor .color-box")).map(function (e) { return window.getComputedStyle(e).backgroundColor }), a = n.querySelector(".item-img"); channelingRender(t.closest(".location").querySelector(".location_name").textContent, convertRgbToHex(o), l, a) } } } }), t.addEventListener("mouseover", function (e) { let t = e.target.closest(".cell"); if (t) { let l = t.querySelector(".container"); l && (l.querySelector(".itemName").textContent, Array.from(l.querySelectorAll(".bgColor .color-box")).map(function (e) { return window.getComputedStyle(e).backgroundColor }), e.target.classList.contains("color-box-color") ? (n.innerHTML = `클릭해서 ${e.target.textContent} 복사`, n.style.display = "block") : (t.closest(".location").querySelector(".location_name").textContent, n.innerHTML = `클릭하면 채널링합니다(교역소기준)`, n.style.display = "block")) } let o = e.target.closest(".tooltip"); if (o) { let a = o.querySelector(".tooltiptext"); a && (n.innerHTML = a.textContent, n.style.display = "block") } }), t.addEventListener("mousemove", function (e) { n.style.left = e.pageX + 10 + "px", n.style.top = e.pageY + 10 + "px" }), t.addEventListener("mouseout", function (e) { e.target.closest(".cell") && (n.style.display = "none") }); let l = document.querySelector("#top-menu"); l.addEventListener("mouseover", function (e) { let t = e.target.closest(".tooltip"); if (t) { let l = t.querySelector(".tooltiptext"); l && (n.innerHTML = l.textContent, n.style.display = "block") } }), l.addEventListener("mousemove", function (e) { n.style.left = e.pageX + 10 + "px", n.style.top = e.pageY + 10 + "px" }), l.addEventListener("mouseout", function (e) { e.target.closest(".tooltip") && (n.style.display = "none") }), document.addEventListener("mouseout", function (e) { l.contains(e.relatedTarget) || (n.style.display = "none") }) }); let modal = document.getElementById("modal"), modalClose = document.getElementById("modal-close"), modalBody = document.getElementById("modal-body"); function showModal(e) { modalBody.innerHTML = "", modalBody.innerHTML = e } function showNotification(e) { let t = document.getElementById("notification"); t.textContent = e, t.style.display = "block", setTimeout(() => { t.style.display = "none" }, 1500) } modalClose.addEventListener("click", function () { modalBody.innerHTML = "", modal.style.display = "none" }), window.addEventListener("click", function (e) { e.target === modal && (modalBody.innerHTML = "", modal.style.display = "none") }), document.querySelector(".question-button").addEventListener("click", function () { document.getElementById("modal-body").innerHTML = '<img src="howToGetAPIKey.png" alt="Key Generation Method" style="width:100%;max-width:100%;">', document.getElementById("modal").style.display = "flex" }), document.getElementById("channelingServer").addEventListener("change", function () { this.checked && document.getElementById("serverSelect").value }), document.getElementById("serverSelect").addEventListener("change", function () { let e = this.value, t = document.getElementById("channelingTooltipText"); t.textContent = `체크시 ${e} 서버만 채널링합니다` }), document.getElementById("modal-content").addEventListener("click", async e => { let t = document.getElementById("modal-close"); if (e.target === t) return; showNotification("잠시만 기다려주세요."); let n = document.getElementById("modal-content"); try { let l = await html2canvas(n, { ignoreElements: e => e === t }); l.toBlob(e => { e && navigator.clipboard.write([new ClipboardItem({ "image/png": e })]).then(() => { showNotification("이미지가 클립보드에 복사되었습니다.") }).catch(e => { console.error("클립보드 복사 실패:", e) }) }) } catch (o) { console.error("캡처 오류:", o) } }); let locationSelectButton = document.getElementById("locationSelect"), channelInputButton = document.getElementById("channelInput"), prevBtn = document.getElementById("prevBtn"), nextBtn = document.getElementById("nextBtn"), chanPrevBtn = document.getElementById("chanPrevBtn"), chanNextBtn = document.getElementById("chanNextBtn"); prevBtn.addEventListener("click", () => { let e = locationSelectButton.selectedIndex, t = 0 === e ? locationSelectButton.options.length - 1 : e - 1; locationSelectButton.selectedIndex = t, locationSelectButton.dispatchEvent(new Event("change")) }), nextBtn.addEventListener("click", () => { let e = locationSelectButton.selectedIndex, t = e === locationSelectButton.options.length - 1 ? 0 : e + 1; locationSelectButton.selectedIndex = t, locationSelectButton.dispatchEvent(new Event("change")) }), chanPrevBtn.addEventListener("click", () => { let e = channelInputButton.selectedIndex, t = 0 === e ? channelInputButton.options.length - 1 : e - 1; channelInputButton.selectedIndex = t, channelInputButton.dispatchEvent(new Event("change")) }), chanNextBtn.addEventListener("click", () => { let e = channelInputButton.selectedIndex, t = e === channelInputButton.options.length - 1 ? 0 : e + 1; channelInputButton.selectedIndex = t, channelInputButton.dispatchEvent(new Event("change")) }), document.addEventListener("DOMContentLoaded", () => { let e = document.getElementById("toggleFilterButton"), t = document.getElementById("filterColorMenu"), n = !1; e.addEventListener("click", () => { (n = !n) ? (t.classList.add("collapsed"), e.textContent = "필터링 펼치기") : (t.classList.remove("collapsed"), e.textContent = "필터링 접기") }) }); const pouchNames = ["튼튼한 달걀 주머니", "튼튼한 감자 주머니", "튼튼한 옥수수 주머니", "튼튼한 밀 주머니", "튼튼한 보리 주머니", "튼튼한 양털 주머니", "튼튼한 거미줄 주머니", "튼튼한 가는 실뭉치 주머니", "튼튼한 굵은 실뭉치 주머니", "튼튼한 꽃바구니", "튼튼한 저가형 가죽 주머니", "튼튼한 일반 가죽 주머니", "튼튼한 고급 가죽 주머니", "튼튼한 최고급 가죽 주머니", "튼튼한 저가형 옷감 주머니", "튼튼한 일반 옷감 주머니", "튼튼한 고급 옷감 주머니", "튼튼한 최고급 옷감 주머니", "튼튼한 저가형 실크 주머니", "튼튼한 일반 실크 주머니", "튼튼한 고급 실크 주머니", "튼튼한 최고급 실크 주머니"], basicTypes = { 달걀: "egg", 감자: "potato", 옥수수: "corn", 밀: "wheat", 보리: "barley" }, textileTypes = { 양털: "wool", 거미줄: "cobweb", 가는: "thinThread", 굵은: "thickThread", 꽃바구니: "flower" }, qualityMap = { 저가형: "cheap", 일반: "common", 고급: "fine", 최고급: "finest" }; function hexStringToRgb(e) { let t = parseInt(e, 16); return { r: t >> 16 & 255, g: t >> 8 & 255, b: 255 & t } } function guichana(e, t) { let n = hexStringToRgb(t.A), l = hexStringToRgb(t.B), o = hexStringToRgb(t.C); return drawLayers(e, n, l, o) } const canvas = document.createElement("canvas"), ctx = canvas.getContext("2d"); function loadImages(e) { return Promise.all(e.map(e => new Promise(t => { let n = new Image; n.src = e, n.onload = () => t(n) }))) } function applyColorAndMultiply(e, t, n, l, o = !0, a = 0, r = 0) { let i = document.createElement("canvas"), c = i.getContext("2d"); i.width = t.width, i.height = t.height, c.drawImage(t, 0, 0); let s = c.getImageData(0, 0, t.width, t.height), d = s.data; for (let u = 0; u < d.length; u += 4) { let p = d[u + 3]; p > 0 && o && (d[u] = d[u] * n.r / 255, d[u + 1] = d[u + 1] * n.g / 255, d[u + 2] = d[u + 2] * n.b / 255) } c.putImageData(s, 0, 0), e.globalCompositeOperation = l, e.drawImage(i, a, r), e.globalCompositeOperation = "source-over" } const accentuatedColor = { r: 50, g: 50, b: 50 }; function getImagePaths(e) { let t = "", n = "", l = []; if (basicTypes[e.split(" ")[1]]) n = "crop", t = basicTypes[e.split(" ")[1]]; else if (textileTypes[e.split(" ")[1]]) n = "textile", t = textileTypes[e.split(" ")[1]], [e.split(" ")[1]].includes("양털") || l.push(`./pouch/${n}/open_${t}_pattern.png`); else { let o = e.split(" ")[1]; t = n = e.includes("가죽") ? "leather" : e.includes("옷감") ? "fabric" : "silk"; let a = qualityMap[o]; l.push(`./pouch/${n}/${a}_roman1.png`, `./pouch/${n}/${a}_roman2.png`, `./pouch/${n}/${a}_roman3.png`) } let r = [`./pouch/${n}/open_${t}_base.png`, `./pouch/${n}/open_${t}_in.png`, `./pouch/${n}/open_${t}_out.png`, `./pouch/${n}/open_${t}_light.png`, "./pouch/mark.png"]; return r.concat(l) } const allImagePaths = pouchNames.map(e => getImagePaths(e)); async function drawLayers(e, t, n, l) { let o = getImagePaths(e), a = await loadImages(o); if (canvas.width = a[0].width, canvas.height = a[0].height, applyColorAndMultiply(ctx, a[0], { r: 0, g: 0, b: 0 }, "source-over", !1), /달걀|감자|옥수수|밀|보리/.test(e)) applyColorAndMultiply(ctx, a[1], n, "source-over", !1), applyColorAndMultiply(ctx, a[2], t, "color-burn"), applyColorAndMultiply(ctx, a[2], t, "multiply"), applyColorAndMultiply(ctx, a[3], { r: 255, g: 255, b: 255 }, "lighter", !1); else if (/거미줄|가는|굵은/.test(e)) { applyColorAndMultiply(ctx, a[1], l, "color-burn"), applyColorAndMultiply(ctx, a[1], l, "multiply"), applyColorAndMultiply(ctx, a[2], t, "color-burn"), applyColorAndMultiply(ctx, a[2], t, "multiply"); let r = a[5]; applyColorAndMultiply(ctx, r, n, "multiply"), applyColorAndMultiply(ctx, a[3], { r: 255, g: 255, b: 255 }, "lighter", !1) } else if (/꽃/.test(e)) { applyColorAndMultiply(ctx, a[1], n, "color-burn"), applyColorAndMultiply(ctx, a[1], n, "multiply"), applyColorAndMultiply(ctx, a[2], t, "color-burn"), applyColorAndMultiply(ctx, a[2], t, "multiply"); let i = a[5]; applyColorAndMultiply(ctx, i, l, "multiply"), applyColorAndMultiply(ctx, a[3], { r: 255, g: 255, b: 255 }, "lighter", !1) } else applyColorAndMultiply(ctx, a[1], n, "color-burn"), applyColorAndMultiply(ctx, a[1], n, "multiply"), applyColorAndMultiply(ctx, a[2], t, "color-burn"), applyColorAndMultiply(ctx, a[2], t, "multiply"), applyColorAndMultiply(ctx, a[3], { r: 255, g: 255, b: 255 }, "lighter", !1); if (e.includes("가죽") || e.includes("옷감") || e.includes("실크")) { let c = a.slice(5, 8); applyColorAndMultiply(ctx, c[2], l, "source-over"), applyColorAndMultiply(ctx, c[1], { r: 0, g: 0, b: 0 }, "screen", !1), applyColorAndMultiply(ctx, c[0], { r: 0, g: 0, b: 0 }, "color-burn", !1), applyColorAndMultiply(ctx, c[0], { r: 0, g: 0, b: 0 }, "multiply", !1), applyColorAndMultiply(ctx, c[0], l, "color-dodge", !1) } let s = a[4]; applyColorAndMultiply(ctx, s, { r: 255, g: 255, b: 255 }, "source-over", !1, 0, canvas.height - s.height); let d = document.createElement("img"); return d.src = canvas.toDataURL("image/png"), d } function createColorPreview(e) { let t = parseRgb(e.value); if (!t) { console.log('유효하지 않은 RGB 값입니다. 예: "255 255 255"'); return } let n = document.createElement("div"); n.className = "colorInputColor", n.style.backgroundColor = `rgb(${t.r}, ${t.g}, ${t.b})`; let l = e.previousElementSibling; l && "DIV" === l.tagName && l.remove(), e.parentNode.insertBefore(n, e) } function addInputGroup(e = "", t = "anywhere", n = "", l = "and") {
let o = document.createElement("div"); return o.classList.add("tooltip", "filterInput"), o.innerHTML = `
<label for="filterType"></label>
<input type="text" class="colorMemo" value="${e}" placeholder="메모용"/>
<select class="filterType">
<option value="anywhere" ${"anywhere" === t ? "selected" : ""}>전체</option>
<option value="outer" ${"outer" === t ? "selected" : ""}>겉감</option>
<option value="roman" ${"roman" === t ? "selected" : ""}>안감</option>
<option value="inner" ${"inner" === t ? "selected" : ""}>로마자</option>
</select>
<input type="text" class="colorInput" placeholder="예: 0, 255, 0" value="${n}" />
<span class="tooltiptext">A겉감B안감C로마자 / RGB값 예시-(0,0,0) | 0/0/0 | 0.0.0 | 0 0 0 </span>
<select class="filterAndOr">
<option value="and" ${"and" === l ? "selected" : ""}>AND</option>
<option value="or" ${"or" === l ? "selected" : ""}>OR</option>
</select>
<button class="filterDeleteButton">삭제</button>
`, o.querySelector(".filterDeleteButton").addEventListener("click", () => { o.remove(), saveToLocalStorage() }), document.getElementById("filterAbove").appendChild(o), o.querySelector(".colorMemo").addEventListener("input", saveToLocalStorage), o.querySelector(".filterType").addEventListener("change", saveToLocalStorage), o.querySelector(".colorInput").addEventListener("input", saveToLocalStorage), o.querySelector(".filterAndOr").addEventListener("change", saveToLocalStorage), o.querySelector(".colorInput").addEventListener("input", e => { let t = e.target; createColorPreview(t) }), o
} function saveToLocalStorage() { let e = Array.from(document.querySelectorAll(".filterInput")).map(e => { let t = e.querySelector(".colorMemo").value, n = e.querySelector(".filterType").value, l = e.querySelector(".colorInput").value, o = e.querySelector(".filterAndOr"), a = o ? o.value : "and"; return { colorMemo: t, filterType: n, colorInput: l, filterAndOr: a } }); localStorage.setItem("filterInputs", JSON.stringify(e)) } document.addEventListener("DOMContentLoaded", () => { let e = JSON.parse(localStorage.getItem("filterInputs")) || []; if (e.length > 0) { let t = e.shift(), n = document.querySelector(".filterInput"); n && (n.querySelector(".colorMemo").value = t.colorMemo, n.querySelector(".filterType").value = t.filterType, n.querySelector(".colorInput").value = t.colorInput, n.querySelector(".colorMemo").addEventListener("input", saveToLocalStorage), n.querySelector(".filterType").addEventListener("change", saveToLocalStorage), n.querySelector(".colorInput").addEventListener("input", saveToLocalStorage), n.querySelector(".colorInput").addEventListener("input", e => { let t = e.target; createColorPreview(t) })) } e.forEach(e => { addInputGroup(e.colorMemo, e.filterType, e.colorInput, e.filterAndOr) }), Array.from(document.querySelectorAll(".colorInput")).map(e => { console.log(e.value), createColorPreview(e) }) }), document.getElementById("addInputButton").addEventListener("click", () => { addInputGroup(), saveToLocalStorage() }); let soundSelector = document.getElementById("autoFetchSound"); soundSelector.addEventListener("change", e => { let t = e.target.value; if (localStorage.setItem("selectedSound", t), "none" === t) { console.log("소리 없음 선택됨"); return } let n = new Audio(t); "jong.mp3" == t && (n.volume = .2), n.play().catch(e => { console.error("소리를 재생할 수 없습니다:", e) }) }); let selectedSound = localStorage.getItem("selectedSound"); selectedSound && (document.getElementById("autoFetchSound").value = selectedSound);