// ==UserScript==
// @name test_for_naver_land
// @namespace Violentmonkey Scripts
// @match https://new.land.naver.com/complexes*
// @version 1.01
// @author .
// @description Please use with violentmonkey
// @require https://code.jquery.com/jquery-1.12.4.min.js
// @license MIT
// ==/UserScript==
let isCreateCheckArea = false
let checkAreaValue = false
const AREA_CHECK = 'area_check';
const LOW_JEONSE_CHECK = 'low_jeonse_check'
const SEANGO_CHECK = 'seango_check'
const STORE_NAME = 'wolbu_price_filter'
const STORE_VALUE = { [AREA_CHECK]: true, [LOW_JEONSE_CHECK]: false, [SEANGO_CHECK]: true};
const validityCheck = {
[AREA_CHECK] : { isCreate : false, value: false, title: "35평이상 포함"}
, [LOW_JEONSE_CHECK] : { isCreate : false, value: false, title: "최저전세값"}
, [SEANGO_CHECK] : { isCreate : false, value: false, title: "세안고포함"}
}
// get local store value
function getStoreValue(id){
let storeVal = localStorage.getItem(STORE_NAME);
if(!storeVal){
localStorage.setItem(STORE_NAME, JSON.stringify(STORE_VALUE) );
storeVal = localStorage.getItem(STORE_NAME);
}
return JSON.parse(storeVal)[id]
}
// set local store value
function setStoreValue(id, val){
let storeVal = localStorage.getItem(STORE_NAME)
if(!storeVal)
localStorage.setItem(STORE_NAME, JSON.stringify(STORE_VALUE) );
let parseVal = JSON.parse(storeVal);
parseVal[id] = val;
localStorage.setItem(STORE_NAME, JSON.stringify(parseVal) );
}
function CheckBox(id, target){
this.div_id = 'div_'+id;
this.id = id;
this.labelText = validityCheck[id].title;
this.divEle = this.init();
target.after(this.divEle);
let storeVal = getStoreValue(this.id);
validityCheck[id].value = storeVal
document.querySelector('#'+id).checked = storeVal;
document.querySelector('#'+id).addEventListener('change', function(e){
validityCheck[id].value = this.checked;
setStoreValue(id, this.checked)
});
validityCheck[id].isCreate = true;
}
CheckBox.prototype = {
constructor : CheckBox
, init: function(){
const divEle = document.createElement('div');
divEle.setAttribute('id', this.div_id)
divEle.classList.add('filter_group', 'filter_group--size');
divEle.style.margin= '6px 10px 0 0';
divEle.innerHTML = '<input type="checkbox" name="type" id="'+this.id+'" class="checkbox_input" ><label for="'+this.id+'" class="checkbox_label">'+this.labelText+'</label>';
return divEle;
}
}
// 상황에 따라 분기가 필요할 수도
function createCheckBox(type){
new CheckBox(type, document.querySelector('.filter_btn_detail'));
// // 35평이상 포함여부
// if(type === AREA_CHECK ){
// new CheckBox(type, document.querySelector('.filter_btn_detail'));
// }
// else if(type === LOW_JEONSE_CHECK ){
// new CheckBox(type, document.querySelector('.filter_btn_detail'));
// }
}
function checkMandantoryCondition(size) {
// 35평이상 포함여부 check
//if(!validityCheck[AREA_CHECK].isCreate) createCheckBox(AREA_CHECK);
if( validityCheck[AREA_CHECK].value ) return true;
// 35평 미만
if (/\d+/g.exec(size) > (35 * 3.3)) {
//console.log('Filtered by size - ', size);
return false;
}
// todo : 300세대 미만, 용적률, 기타등등
return true;
}
function getFloor(strFloor) {
return strFloor.replace("층", "").split('/');
}
function checkItemCondition(tradeType, floor, spec) {
//매매, 전세
if (tradeType != "전세" && tradeType != "매매") {
//console.log('Filtered by trade type - ', tradeType);
return false;
}
// 세안고 제외
if ( !validityCheck[SEANGO_CHECK].value && (spec.includes("끼고") || spec.includes("안고") || spec.includes("승계")) ) {
//console.log('Filtered by spec - ', spec);
return false;
} else {
//console.log('Allowed spec - ', spec);
}
// 층 - 전세의 경우 층에 관계없이 최고가 적용
if (tradeType == "매매") {
// 층 명확하지 않은 것 제외
var _floorInfo = getFloor(floor);
if (_floorInfo[0] == "저") {
//console.log('Filtered by floor - ', _floorInfo);
return false;
}
// 1층, 2층, 탑층 제외
if ("1|2|3".indexOf(_floorInfo[0]) > -1 || _floorInfo[0] == _floorInfo[1]) {
//console.log('Filtered by floor - ', _floorInfo);
return false;
}
// 5층 이상 건물에서 3층 이하 제외
if (_floorInfo[1] >= 5 && _floorInfo[0] <= 3) {
//console.log('Filtered by floor - ', _floorInfo);
return false;
}
}
return true;
}
function parsePrice(tradePrice) {
tradePrice = tradePrice.replace(" ", "").replace(",", "");
if (tradePrice.includes("억"))
return parseInt(tradePrice.split("억")[0] * 10000) + (parseInt(tradePrice.split("억")[1]) || 0);
else
return parseInt(tradePrice)
}
function getPrice_WeolbuStandard() {
let result = {};
let dictPricePerSize = {};
let tradeTypeValueFnc = function( tradeType, befVal, newVal){
let price, floor, spec;
if( tradeType === '매매'){
price = befVal[0] > newVal[0] ? newVal[0] : befVal[0]
floor = befVal[0] > newVal[0] ? newVal[1] : befVal[1]
}else {
price = befVal[0] < newVal[0] ? newVal[0] : befVal[0]
floor = befVal[0] < newVal[0] ? newVal[1] : befVal[1]
}
return [price, floor, befVal[2]+newVal[2], ++befVal[3] ];
}
document.querySelectorAll("#articleListArea > div").forEach(function(ele) {
// console.log( ele.querySelectorAll("div.info_area .line .spec")[0].innerText)
let aptInfo = ele.querySelectorAll("div.info_area .line .spec")[0].innerText.split(", ");
let size = aptInfo[0]; // 103/84m^2
let floor = aptInfo[1]; // 3/10층
let tradeType = ele.querySelector("div.price_line .type").innerText; // 매매, 전세
let tradePrice = parsePrice(ele.querySelector("div.price_line .price").innerText); // 141000
let spec = ele.querySelectorAll(" div.info_area > p:nth-child(2) > span")[0]; // 확장올수리, 정상입주, 수내중학군
spec = spec ? spec.innerText : "";
if( "매매|전세".indexOf(tradeType) > -1){
if (!checkMandantoryCondition(size)) {
return;
}
if (!(size in result)){
result[size] = {'매매': 0, '전세': 0, '갭': 0, '전세가율': '-', '매매층': '-', '전세층': '-', '매매갯수': 0, '전세갯수': '0' };
dictPricePerSize[size] = {"매매": {}, "전세": {}};
}
if( !dictPricePerSize[size][tradeType][aptInfo.join(',')] )
{
dictPricePerSize[size][tradeType][aptInfo.join(',')] = [tradePrice, getFloor(floor)[0], spec, 1]
}
else
{
let beforeValue = dictPricePerSize[size][tradeType][aptInfo.join(',')];
let newValue = [tradePrice, getFloor(floor)[0], spec ];
dictPricePerSize[size][tradeType][aptInfo.join(',')] = tradeTypeValueFnc(tradeType, beforeValue, newValue)
}
}
});
let isGrouped = document.querySelector('#address_group2').checked
for( let key in result){
let sellObj = dictPricePerSize[key]['매매'];
let liveObj = dictPricePerSize[key]['전세'];
let sellCnt = !isGrouped ? Object.keys(sellObj).length : Object.entries(sellObj).reduce( (acc, [, item]) => (parseInt(acc) + parseInt(item[3])), 0 );
let liveCnt = !isGrouped ? Object.keys(liveObj).length : Object.entries(liveObj).reduce( (acc, [, item]) => (parseInt(acc) + parseInt(item[3])), 0 );
// console.log(JSON.parse(JSON.stringify(sellObj)));
// console.log(sellCnt, liveCnt)
for( let key in sellObj ){
let aptObj = sellObj[key]
if (!checkItemCondition('매매', aptObj[1], aptObj[2])){
delete sellObj[key]
}
}
let finalSellObj = Object.entries(sellObj).sort(([, a], [, b]) => a[0] - b[0]);
let finalLivelObj = Object.entries(liveObj).sort(([, a], [, b]) => b[0] - a[0]);
if(finalSellObj && finalSellObj.length){
result[key]['매매'] = finalSellObj[0][1][0];
result[key]['매매층'] = finalSellObj[0][1][1];
}
result[key]['매매갯수'] = sellCnt;
if(finalLivelObj && finalLivelObj.length){
let idx = 0;
result[key]['전세'] = finalLivelObj[idx][1][0];
result[key]['전세층'] = finalLivelObj[idx][1][1];
result[key]['전세갯수'] = liveCnt;
let idx2 = finalLivelObj.length-1;
result[key]['전세2'] = finalLivelObj[idx2][1][0];
result[key]['전세층2'] = finalLivelObj[idx2][1][1];
result[key]['갭'] = parseInt(result[key]['매매']) - parseInt(result[key]['전세']);
result[key]['전세가율'] = parseInt( parseInt(result[key]['전세']) / parseInt(result[key]['매매']) * 100) + "%";
}
// console.log('finalSellObj', finalSellObj);
// console.log(finalLivelObj);
}
// console.log(dictPricePerSize);
// console.log(result)
return result;
}
function addInfoToScreen(infos) {
var oldScreenInfo = document.querySelector("#summaryInfo > div.complex_summary_info > div.complex_price_info");
if (oldScreenInfo)
oldScreenInfo.remove();
var screenInfo = document.createElement('div');
screenInfo.setAttribute('class', 'complex_price_info');
screenInfo.style.marginTop = "10px";
for (let size in infos) {
var strTradePriceInfo = (infos[size]['매매'] ? infos[size]['매매'] + "/" + infos[size]['매매층'] : "0/-");
var strLeasePriceInfo = (infos[size]['전세'] ? infos[size]['전세'] + "/" + infos[size]['전세층'] : "0/-");
var strLeasePriceInfo2 = (infos[size]['전세2'] ? infos[size]['전세2'] + "/" + infos[size]['전세층2'] : "0/-");
var additionalInfos = [];
if (infos[size]['매매'] && infos[size]['전세']) {
additionalInfos.push(infos[size]['갭']);
additionalInfos.push(infos[size]['전세가율']);
}
if (infos[size]['매매']) {
var py = parseInt(/\d+/g.exec(size), 10) / 3.3;
additionalInfos.push(parseInt(infos[size]['매매'] / py));
}
var strAdditionalInfo = "";
// if (additionalInfos.length > 0){
// strAdditionalInfo += " (" + additionalInfos.join(", ") + ")("+infos[size]['매매갯수']+"/"+infos[size]['전세갯수']+")";
if(document.querySelector('#address_group2').checked)
strAdditionalInfo += additionalInfos.length > 0 ? " (" + additionalInfos.join(", ") + ")("+infos[size]['매매갯수']+"/"+infos[size]['전세갯수']+")" : " ("+infos[size]['매매갯수']+"/"+infos[size]['전세갯수']+")";
else
strAdditionalInfo += additionalInfos.length > 0 ? " (" + additionalInfos.join(", ") + ")" : "";
// }
var cloned = document.querySelector("#summaryInfo > div.complex_summary_info > div.complex_trade_wrap > div > dl:nth-child(1)").cloneNode(true);
cloned.setAttribute("added", true);
cloned.getElementsByClassName("title")[0].innerText = size;
var trade = cloned.getElementsByClassName("data")[0];
var lease = trade.cloneNode(true);
var lease2 = trade.cloneNode(true);
var additionalInfo = trade.cloneNode(true);
var delim = trade.cloneNode(true);
// remove, then reordering (please make it more fancy)
trade.innerText = strTradePriceInfo;
trade.style.color = '#f34c59';
lease.innerText = strLeasePriceInfo;
lease.style.color = '#4c94e8';
lease2.innerText = strLeasePriceInfo2;
lease2.style.color = '#4c94e8';
delim.innerText = " / ";
delim.style.color = '#ffffff';
additionalInfo.innerText = strAdditionalInfo;
// remove, then reordering (please make it fancy..)
cloned.removeChild(trade);
cloned.appendChild(delim);
cloned.appendChild(trade);
cloned.appendChild(delim.cloneNode(true));
cloned.appendChild(lease);
cloned.appendChild(delim.cloneNode(true));
cloned.appendChild(lease2);
cloned.appendChild(delim.cloneNode(true));
cloned.appendChild(additionalInfo);
screenInfo.appendChild(cloned);
}
document.querySelector("#summaryInfo > div.complex_summary_info").insertBefore(screenInfo, document.querySelector("#summaryInfo > div.complex_summary_info > div.complex_detail_link"))
}
function sortOnKeys(dict) {
var tempDict = {};
let sorted = jQuery('#complexOverviewList > div.list_contents > div.list_fixed > div.list_filter > div > div:nth-child(2) > div > div > ul > li label.checkbox_label')
.map((idx, item) => {
return item.innerText.replace('㎡', '');
})
let keys = Object.keys(dict)
sorted.map( (idx, item) => {
keys.map( (key) => {
if( key.indexOf(item) === 0 ) tempDict[key] = dict[key]
})
})
return tempDict;
}
var g_lastSelectedApt = "";
function addObserverIfDesiredNodeAvailable() {
var target = document.getElementsByClassName('map_wrap')[0];
var inDebounce;
if (!target)
return;
// 35평이상 포함여부 check
if(!validityCheck[LOW_JEONSE_CHECK].isCreate) createCheckBox(LOW_JEONSE_CHECK);
if(!validityCheck[SEANGO_CHECK].isCreate) createCheckBox(SEANGO_CHECK);
if(!validityCheck[AREA_CHECK].isCreate) createCheckBox(AREA_CHECK);
jQuery(document).on('click', (e) => {
if( jQuery(e.target).parents('a.item_link').length > 0 )
setTimeout((runFnc) => { jQuery('.detail_panel').css("left", "450px"); }, 500);
});
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
[].slice.call(mutation.addedNodes).forEach(function(addedNode) {
//console.log('???');
//console.log(addedNode.classList);
if (!addedNode.classList ||
(!addedNode.classList.contains('infinite_scroll') && !addedNode.classList.contains('item'))) {
return;
}
if (!document.querySelector("#complexTitle")) {
console.log("Unexpected issues #1");
return;
}
if (document.querySelector("#complexTitle").innerText != g_lastSelectedApt) {
document.querySelectorAll("#summaryInfo > div.complex_summary_info > div.complex_trade_wrap > div > dl").forEach(function(ele) {
if (ele.hasAttribute("added"))
ele.remove();
});
g_lastSelectedApt = document.querySelector("#complexTitle").innerText;
}
//console.log('result ', result);
document.querySelector("#complexOverviewList > div > div.item_area > div").scrollTop =
document.querySelector("#complexOverviewList > div > div.item_area > div").scrollHeight;
//document.querySelector("#complexOverviewList > div > div.item_area > div").scrollTop = 0;
var runFnc = function (){
jQuery('.list_panel').css("width", "450px");
jQuery('.detail_panel').css("left", "450px");
result = getPrice_WeolbuStandard();
result = sortOnKeys(result);
addInfoToScreen(result);
document.querySelector(".item_list--article").scrollTop = 0;
}
if(inDebounce) clearTimeout(inDebounce)
inDebounce = setTimeout(runFnc, 500);
});
});
});
var config = {
childList: true,
subtree: true,
};
observer.observe(target, config);
}
addObserverIfDesiredNodeAvailable();