<div>const category = { </div><div> 목걸이: 200010, </div><div> 귀걸이: 200020, </div><div> 반지: 200030, </div><div> }; </div><br /><div> const grade = { </div><div> 전설: 4, </div><div> 유물: 5, </div><div> 고대: 6, </div><div> 전체: null, </div><div> }; </div><div> </div><div> const dealOption = { </div><div> 치명: 15, </div><div> 특화: 16, </div><div> 제압: 17, </div><div> 신속: 18, </div><div> 인내: 19, </div><div> 숙련: 20, </div><div> }; </div><div> </div><div> const imprintOption = { </div><div> 각성: 255, </div><div> 갈증: 286, </div><div> 강령술: 243, </div><div> "강화 무기": 129, </div><div> "강화 방패": 242, </div><div> "결투의 대가": 288, </div><div> "고독한 기사": 225, </div><div> 광기: 125, </div><div> "광전사의 비기": 188, </div><div> 구슬동자: 134, </div><div> "굳은 의지": 123, </div><div> "극의: 체술": 190, </div><div> "급소 타격": 142, </div><div> "기습의 대가": 249, </div><div> 긴급구조: 302, </div><div> "넘치는 교감": 199, </div><div> "달의 소리": 287, </div><div> "달인의 저력": 238, </div><div> 돌격대장: 254, </div><div> "두 번째 동료": 258, </div><div> "마나 효율 증가": 168, </div><div> "마나의 흐름": 251, </div><div> "멈출 수 없는 충동": 281, </div><div> 바리케이드: 253, </div><div> 버스트: 279, </div><div> "번개의 분노": 246, </div><div> "부러진 뼈": 245, </div><div> "분노의 망치": 196, </div><div> "분쇄의 주먹": 236, </div><div> 불굴: 235, </div><div> "사냥의 시간": 290, </div><div> "상급 소환사": 198, </div><div> 선수필승: 244, </div><div> 세맥타통: 256, </div><div> 속전속결: 300, </div><div> "슈퍼 차지": 121, </div><div> 승부사: 248, </div><div> "시선 집중": 298, </div><div> "실드 관통": 237, </div><div> 심판자: 282, </div><div> 아드레날린: 299, </div><div> "아르데타인의 기술": 284, </div><div> "안정된 상태": 111, </div><div> "약자 무시": 107, </div><div> "에테르 포식자": 110, </div><div> "여신의 가호": 239, </div><div> 역천지체: 257, </div><div> "예리한 둔기": 141, </div><div> "오의 강화": 127, </div><div> 오의난무: 292, </div><div> "완벽한 억제": 280, </div><div> 원한: 118, </div><div> "위기 모면": 140, </div><div> 일격필살: 291, </div><div> "잔재된 기운": 278, </div><div> "저주받은 인형": 247, </div><div> 전문의: 301, </div><div> "전투 태세": 224, </div><div> "절실한 구원": 195, </div><div> 절정: 276, </div><div> 절제: 277, </div><div> 점화: 293, </div><div> "정기 흡수": 109, </div><div> "정밀 단도": 303, </div><div> "죽음의 습격": 259, </div><div> "중갑 착용": 240, </div><div> "중력 수련": 197, </div><div> "진실된 용맹": 194, </div><div> "진화의 유산": 285, </div><div> "질량 증가": 295, </div><div> 초심: 189, </div><div> "최대 마나 증가": 167, </div><div> 추진력: 296, </div><div> "축복의 오라": 283, </div><div> "충격 단련": 191, </div><div> "타격의 대가": 297, </div><div> "탈출의 명수": 202, </div><div> "포격 강화": 193, </div><div> "폭발물 전문가": 241, </div><div> 피스메이커: 289, </div><div> 핸드거너: 192, </div><div> "화력 강화": 130, </div><div> 환류: 294, </div><div> "황제의 칙령": 201, </div><div> "황후의 은총": 200, </div><div> 회귀: 305, </div><div> 만개: 306, </div><div> }; </div><div> </div><div> function parse(document, index) { </div><div> const row = document.querySelector( </div><div> `#auctionListTbody > tr:nth-child(${index})` </div><div> ); </div><div> if (!row) { </div><div> return; </div><div> } </div><div> </div><div> const grade = parseInt(row.querySelector('td:nth-child(1) > div.grade').getAttribute('data-grade'), 10); </div><div> const id = row.querySelector('td:nth-child(7) > button').getAttribute('data-productid'); </div><div> const name = row </div><div> .querySelector(`td:nth-child(1) > div.grade > span.name`) </div><div> .innerText.trim(); </div><div> const tradeLeftStr = row </div><div> .querySelector(`td:nth-child(1) > div.grade > span.count`) </div><div> .innerText.trim(); </div><div> const tradeLeft = tradeLeftStr === "[구매 후 거래 불가]" ? 0 : parseInt(tradeLeftStr.split("거래 ")[1].split("회")[0], 10) </div><div> const effects = row </div><div> .querySelector(`td:nth-child(1) > div.effect`) </div><div> .innerText.trim() </div><div> .split("\n") </div><div> .map((str) => str.trim()) </div><div> .filter((str) => !!str) </div><div> .map((str) => [ </div><div> str.split("[")[1].split("]")[0], </div><div> parseInt(str.split("+")[1], 10), </div><div> ]); </div><div> const quality = parseInt( </div><div> row.querySelector(`td:nth-child(3) > div > span.txt`).innerText.trim(), </div><div> 10 </div><div> ); </div><div> const buyPrice = parseFloat( </div><div> row </div><div> .querySelector(`td:nth-child(6) > div > em`) </div><div> .innerText.trim() </div><div> .replace(/,/g, "") </div><div> ) </div><div> const auctionPrice = parseFloat( </div><div> row </div><div> .querySelector(`td:nth-child(5) > div > em`) </div><div> .innerText.trim() </div><div> .replace(/,/g, "") </div><div> ); </div><div> const price = buyPrice || auctionPrice; </div><div> </div><div> return { </div><div> isFixed: false, </div><div> name, </div><div> id, </div><div> grade, </div><div> tradeLeft, </div><div> effects, </div><div> quality, </div><div> price, </div><div> buyPrice, </div><div> auctionPrice, </div><div> }; </div><div> } </div><div> </div><div> async function search(form, pageNo) { </div><div> const body = new URLSearchParams(); </div><div> </div><div> body.append("request[firstCategory]", 200000); </div><div> body.append("request[secondCategory]", form.category); </div><div> body.append("request[itemTier]", 3); </div><div> body.append("request[itemGrade]", form.grade ?? ""); </div><div> body.append("request[itemLevelMin]", 0); </div><div> body.append("request[itemLevelMax]", 1700); </div><div> body.append("request[gradeQuality]", form.quality); </div><div> body.append("request[etcOptionList][0][firstOption]", 2); </div><div> body.append( </div><div> "request[etcOptionList][0][secondOption]", </div><div> form.dealOption1?.type ?? "" </div><div> ); </div><div> body.append( </div><div> "request[etcOptionList][0][minValue]", </div><div> form.dealOption1?.min ?? "" </div><div> ); </div><div> body.append("request[etcOptionList][0][maxValue]", ""); </div><div> body.append("request[etcOptionList][1][firstOption]", 2); </div><div> body.append( </div><div> "request[etcOptionList][1][secondOption]", </div><div> form.dealOption2?.type ?? "" </div><div> ); </div><div> body.append( </div><div> "request[etcOptionList][1][minValue]", </div><div> form.dealOption2?.min ?? "" </div><div> ); </div><div> body.append("request[etcOptionList][1][maxValue]", ""); </div><div> body.append("request[etcOptionList][2][firstOption]", 3); </div><div> body.append( </div><div> "request[etcOptionList][2][secondOption]", </div><div> form.imprintOption1?.type ?? "" </div><div> ); </div><div> body.append( </div><div> "request[etcOptionList][2][minValue]", </div><div> form.imprintOption1?.min ?? "" </div><div> ); </div><div> body.append("request[etcOptionList][2][maxValue]", ""); </div><div> body.append("request[etcOptionList][3][firstOption]", 3); </div><div> body.append( </div><div> "request[etcOptionList][3][secondOption]", </div><div> form.imprintOption2?.type ?? "" </div><div> ); </div><div> body.append( </div><div> "request[etcOptionList][3][minValue]", </div><div> form.imprintOption2?.min ?? "" </div><div> ); </div><div> body.append("request[etcOptionList][3][maxValue]", ""); </div><div> body.append("request[pageNo]", pageNo); </div><div> body.append("request[sortOption][Sort]", "BUY_PRICE"); </div><div> body.append("request[sortOption][IsDesc]", false); </div><div> </div><div> return fetch("<a href="https://lostark.game.onstove.com/Auction/GetAuctionListV2">https://lostark.game.onstove.com/Auction/GetAuctionListV2</a>", { </div><div> headers: { </div><div> "content-type": "application/x-www-form-urlencoded; charset=UTF-8", </div><div> }, </div><div> body: body, </div><div> method: "POST", </div><div> }) </div><div> .then((res) => { </div><div> if (res.status === 500) { </div><div> throw new Error('ERR_INTERNAL_SERVER'); </div><div> } </div><div> return res.text(); </div><div> }) </div><div> .then((html) => { </div><div> if (html.includes('서비스 점검 중입니다.')) { </div><div> throw new Error('ERR_MAINTENANCE'); </div><div> } </div><div> const parser = new DOMParser(); </div><div> return parser.parseFromString(html, "text/html"); </div><div> }) </div><div> .then((document) => { </div><div> if (document.querySelector("#idLogin")) { </div><div> throw new Error('ERR_NO_LOGIN'); </div><div> } </div><div> if (document.querySelector("#auctionListTbody > tr.empty")) { </div><div> if (document.querySelector("#auctionListTbody > tr.empty").innerText.trim() === "경매장 연속 검색으로 인해 검색 이용이 최대 5분간 제한되었습니다.") { </div><div> throw new Error('ERR_LIMIT_REACHED'); </div><div> } </div><div> return { </div><div> products: [], </div><div> totalPages: 1, </div><div> }; </div><div> } </div><div> const products = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] </div><div> .map((index) => parse(document, index)) </div><div> .filter((x) => !!x); </div><div> </div><div> const lastPageFn = document.querySelector("a.pagination__last").getAttribute(""); </div><div> const totalPages = lastPageFn ? parseInt(lastPageFn.split(".page(")[1].split(");")[0], 10) : 1; </div><br /><div> return { </div><div> products, </div><div> totalPages, </div><div> } </div><div> }); </div><div> } </div><br /><div> async function trySearch(form, pageNo) { </div><div> let searchResult; </div><div> let failCount = 0; </div><div> while (true) { </div><div> try { </div><div> searchResult = await search(form, pageNo); </div><div> return searchResult; </div><div> } catch (err) { </div><div> failCount += 1; </div><div> if (failCount > 5) { </div><div> throw new Error('경매장 검색에 5회 연속 실패했습니다. 스크립트를 종료합니다.') </div><div> } </div><div> if (err.message === 'ERR_LIMIT_REACHED') { </div><div> console.log('경매장 검색 횟수 제한을 초과했습니다. 5분 후에 자동으로 재시도합니다.'); </div><div> await new Promise(resolve => setTimeout(resolve, 60000 * 5 + 1000)); </div><div> continue; </div><div> } </div><div> if (err.message === 'ERR_INTERNAL_SERVER') { </div><div> console.log('경매장 검색 서버에 오류가 발생했습니다. 30초 후에 자동으로 재시도합니다.'); </div><div> await new Promise(resolve => setTimeout(resolve, 30000)); </div><div> continue; </div><div> } </div><div> if (err.message === 'ERR_MAINTENANCE') { </div><div> console.log('경매장 서비스 점검 중입니다. 스크립트를 종료합니다.'); </div><div> throw err; </div><div> } </div><div> if (err.message === 'ERR_NO_LOGIN') { </div><div> console.log('로그인이 필요합니다. 스크립트를 종료합니다.'); </div><div> throw err; </div><div> } </div><div> console.log('식별되지 않은 오류가 발생했습니다. 스크립트를 종료합니다.'); </div><div> throw err; </div><div> } </div><div> } </div><div> } </div><div> </div><div> async function getSearchResult(imprints, accTypes, accMap, overlapping, searchGrade) { </div><div> const result = []; </div><div> const total = imprints.length * accTypes.length; </div><div> let count = 0; </div><div> for (const imprint of imprints) { </div><div> for (const accType of accTypes) { </div><div> count += 1; </div><div> const estimated = new Date(); </div><div> estimated.setSeconds(estimated.getSeconds() + (total - count) * 6); </div><div> console.log(`검색 진행중 - ${count} / ${total} </div><div>예상 완료 시각: ${estimated.toLocaleTimeString()}`) </div><div> const [[type1, min1], [type2, min2]] = .entries(imprint); </div><div> const acc = accMap[accType]; </div><div> const form = { </div><div> category: category[acc.category], </div><div> grade: grade[searchGrade], </div><div> quality: acc.quality, </div><div> dealOption1: acc.dealOption1 && { </div><div> type: dealOption[acc.dealOption1[0]], </div><div> min: acc.dealOption1[1], </div><div> }, </div><div> dealOption2: acc.dealOption2 && { </div><div> type: dealOption[acc.dealOption2[0]], </div><div> min: acc.dealOption2[1], </div><div> }, </div><div> imprintOption1: { </div><div> type: imprintOption[type1], </div><div> min: min1, </div><div> }, </div><div> imprintOption2: { </div><div> type: imprintOption[type2], </div><div> min: min2, </div><div> }, </div><div> }; </div><div> const { products, totalPages } = await trySearch(form, 1); </div><div> const productsAll = [...products]; </div><div> if (products.filter(product => product.buyPrice).length <= 3 && totalPages > 1) { </div><div> console.log("1페이지에 충분한 매물이 발견되지 않아 추가 검색을 진행합니다.") </div><div> await new Promise(resolve => setTimeout(resolve, 6000)); </div><div> const { products: products5p } = await trySearch(form, Math.max(Math.floor(totalPages / 20), 2)); </div><div> productsAll.push(...products5p); </div><div> } </div><div> result.push([ </div><div> `${type1}_${min1}_${type2}_${min2}_${accType}`, </div><div> productsAll, </div><div> ]); </div><div> if (accType === "귀걸이1" && overlapping.귀걸이) { </div><div> result.push([ </div><div> `${type1}_${min1}_${type2}_${min2}_귀걸이2`, </div><div> productsAll, </div><div> ]); </div><div> } </div><div> if (accType === "반지1" && overlapping.반지) { </div><div> result.push([ </div><div> `${type1}_${min1}_${type2}_${min2}_반지2`, </div><div> productsAll, </div><div> ]); </div><div> } </div><div> await new Promise(resolve => setTimeout(resolve, 6000)); </div><div> } </div><div> } </div><div> return .fromEntries(result); </div><div> } </div><br /><div> let result; </div><div> function copyResult() { </div><div> const el = document.createElement('textarea'); </div><div> document.body.appendChild(el); </div><div> el.value = JSON.stringify(result, null, 2); </div><div> el.select(); </div><div> document.execCommand('copy'); </div><div> document.body.removeChild(el); </div><div> alert('검색 결과가 복사되었습니다.'); </div><div> } </div><br /><div> const btn = document.getElementById('copyBtn'); </div><div> if (btn) { </div><div> btn.remove(); </div><div> } </div><div> getSearchResult([{"예리한 둔기":3,"원한":5},{"저주받은 인형":3,"타격의 대가":5},{"예리한 둔기":3,"타격의 대가":5},{"저주받은 인형":3,"원한":5}], ["목걸이","반지2"], {"목걸이":{"category":"목걸이","quality":85,"dealOption1":["치명",0],"dealOption2":["신속",0],"name":"","imprintOption1":["",0],"imprintOption2":["",0],"imprintPenalty":["",0]},"귀걸이1":{"category":"귀걸이","quality":50,"dealOption1":["치명",0],"name":"울부짖는 환상의 귀걸이","imprintOption1":["원한",5],"imprintOption2":["예리한 둔기",3],"imprintPenalty":["공격속도 감소",1]},"귀걸이2":{"category":"귀걸이","quality":50,"dealOption1":["치명",0],"name":"울부짖는 혼돈의 귀걸이","imprintOption1":["원한",5],"imprintOption2":["타격의 대가",3],"imprintPenalty":["방어력 감소",2]},"반지1":{"category":"반지","quality":50,"dealOption1":["치명",0],"name":"찬란한 파멸자의 반지","imprintOption1":["피스메이커",5],"imprintOption2":["아드레날린",3],"imprintPenalty":["이동속도 감소",3]},"반지2":{"category":"반지","quality":50,"dealOption1":["특화",0],"name":"","imprintOption1":["",0],"imprintOption2":["",0],"imprintPenalty":["",0]}}, {"귀걸이":false,"반지":false}, "유물").then(res => { </div><div> result = res; </div><div> console.log(res); </div><div> const el = document.createElement('button'); </div><div> el.id = 'copyBtn'; </div><div> el.style = 'width: 100%; height: 64px; text-align: center'; </div><div> el.innerText = '검색 결과 복사'; </div><div> el. = copyResult; </div><div> document.body.prepend(el); </div><div> }); </div><div> </div>
댓글 영역
획득법
① NFT 발행
작성한 게시물을 NFT로 발행하면 일주일 동안 사용할 수 있습니다. (최초 1회)
② NFT 구매
다른 이용자의 NFT를 구매하면 한 달 동안 사용할 수 있습니다. (구매 시마다 갱신)
사용법
디시콘에서지갑연결시 바로 사용 가능합니다.