housingEnricherNL

A script with the goal of enriching funda.nl and pararius.nl sites with information about the listing from official sources

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name          housingEnricherNL
// @namespace     com.parker.david
// @version       V0.0.9
// @description   A script with the goal of enriching funda.nl and pararius.nl sites with information about the listing from official sources
// @author        David Parker
// @match         https://www.funda.nl/zoeken/huur*
// @match         https://www.funda.nl/zoeken/koop*
// @match         https://www.pararius.nl/koopwoningen*
// @match         https://www.pararius.nl/huurwoningen*
// @icon          https://www.google.com/s2/favicons?sz=64&domain=funda.nl
// @grant         GM_xmlhttpRequest
// @connect       www.ep-online.nl
// @connect       www.wozwaardeloket.nl
// ==/UserScript==

//switching stuff for old vs new funda
//https://stackoverflow.com/questions/48587922/using-the-same-userscript-to-run-different-code-at-different-urls

'use strict';

const labelColor = new Map([
  ['A+++', '#00A54E'],
  ['A++', '#4CB948'],
  ['A+', '#BFD72F'],
  ['A', '#FFF100'],
  ['B', '#FDB914'],
  ['C', '#F56E20'],
  ['D', "#EF1C22"],
  ['E', "#EF1C22"],
  ['F', "#EF1C22"],
  ['G', "#EF1C22"],
  [undefined, "#D8A3DD"],
]);


//debugger;

const eponline = 'https://www.ep-online.nl/Energylabel/Search'

async function Request(url, opt = {}) {
  Object.assign(opt, { url, timeout: 5000, responseType: 'json' })
  return new Promise((resolve, reject) => {
    opt.onerror = opt.ontimeout = reject
    opt.onload = resolve
    GM_xmlhttpRequest(opt)
  })
}

function extractPostcode(base) {
  var parts = base.split(' ');
  return parts[0] + parts[1];
}

function extractAddress(base) {
  if (!/\d/.test(base)) return undefined
  //get last number
  var number = base.match('\(\\d\+\)\(\?\!\.\*\\d\)')[0];
  //get last character
  var letter = base.match('\[a\-zA\-Z\]\(\?\!\.\*\[a\-zA\-Z\]\)')[0];
  // if ends with letter, return number+letter, else just number
  return (base.slice(-1) == letter) ? number + ' ' + letter : number
}

async function getToken() {
  let response = await Request(eponline, { method: 'GET' })
  let parser = new DOMParser();
  let responseDoc = parser.parseFromString(response.responseText, "text/html");
  return responseDoc.querySelector('[name="__RequestVerificationToken"]').value;
}

async function getLabel(token, address, postcode) {
  let response = await Request(eponline, {
    method: 'POST',
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    data: new URLSearchParams({
      __RequestVerificationToken: token,
      SearchValue: `${postcode} ${address}`
    })
  })
  return extractLabel(response, address, postcode);
}

function extractLabel(response, address, postcode) {
  //todo: handle multi-page eponline results, one such example is "1072NK 2"
  var parser = new DOMParser();
  var responseDoc = parser.parseFromString(response.responseText, "text/html");

  var labelBlock = Array.from(responseDoc.querySelectorAll('.se-result-item-nta.se-sm-noborder'))
    .filter((doc) => {
      //empty = false, else true
      return doc.querySelector('span.sort-value-pht.text-nowrap').textContent.trim() === postcode + ' ' + address
    })[0]
  return labelBlock
}

function generateLabelSummary(node) {
  if (node === undefined)
    return { text: "issue getting label", label: undefined }

  //get the letter label
  let label = node.querySelector('[class*=bg-label-class-] > span').innerText.trim()

  // check if label is valid
  let Opnamedatum = Array.from(node.querySelectorAll('.se-item-description-nta')).filter(x => x.innerText.trim() === "Opnamedatum")[0].nextElementSibling.innerText.trim()
  if (Opnamedatum === "-") {
    return { text: "unofficial " + label, label: label }
  }

  //check if pre-2021 type label
  let energyIndex = Array.from(node.querySelectorAll('.se-item-description-nta')).filter(x => x.innerText.trim() === "EI")
  if (!!energyIndex.length) {
    return { text: "EnergyIndex: " + energyIndex[0].nextElementSibling.innerText.trim() + " (letter: " + label + ")", label: label }
  }

  // return current energy label class
  return { text: "EnergyLabel: " + label, label: label }

}

async function getWoz() {
  return "WIP"
}

async function generateWozSummary(node) {
  return node
}

function applyEnrichment(nodeToEnrich, labelSummary, wozText) {
  let pLabel = document.createElement('p')
  pLabel.textContent = labelSummary.text
  pLabel.style.backgroundColor = labelColor.get(labelSummary.label)
  nodeToEnrich.after(pLabel)
  // handle pWoz
}

async function enrich(nodes) {
  let token = await getToken()

  await Promise.all(nodes.map(async (node) => {
    let labelNode = await getLabel(token, node.address, node.postcode)
    let labelSummary = generateLabelSummary(labelNode)
    let wozNodes = await getWoz(node.address, node.postcode)
    let wozSummary = generateWozSummary(wozNodes)
    applyEnrichment(node.appendNode, labelSummary, wozSummary)
  }));
}

function getNodesToEnrichPararius() {
  let searchResultBase = document.querySelectorAll('.search-list__item--listing')
  return Array.from(searchResultBase)
    .map(node => {
      let address = extractAddress(node.querySelector('.listing-search-item__link--title').textContent.trim());
      let postcode = extractPostcode(node.querySelector(".listing-search-item__sub-title\\'").textContent.trim());
      let appendNode = node.querySelector('.listing-search-item__features')
      return { appendNode: appendNode, address: address, postcode: postcode };
    })
}

function getNodesToEnrichFunda() {
  let searchResultBase = document.querySelectorAll('[data-test-id="search-result-item"]')
  return Array.from(searchResultBase)
    .map(node => {
      let address = extractAddress(node.querySelector('[data-test-id="street-name-house-number"]').textContent.trim());
      let postcode = extractPostcode(node.querySelector('[data-test-id="postal-code-city"]').textContent.trim());
      let appendNode = node.querySelector('.flex-wrap.overflow-hidden')
      return { appendNode: appendNode, address: address, postcode: postcode };
    })
}

(async () => {

  // https://stackoverflow.com/questions/48587922/using-the-same-userscript-to-run-different-code-at-different-urls
  if (/funda\.nl/.test(location.hostname)) {
    // Run code for new funda.nl
    await enrich(getNodesToEnrichFunda())
  }
  else if (/pararius\.nl/.test(location.hostname)) {
    // Run code for pararius.nl
    await enrich(getNodesToEnrichPararius())
  }
})().catch(err => {
  console.error(err);
});



// process for wozwardeloket:

//await fetch("https://api.pdok.nl/bzk/locatieserver/search/v3_1/suggest?q=2665BH%2C%20105&rows=10", {
//    "credentials": "omit",
//    "headers": {
//        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/114.0",
//        "Accept": "application/json, text/plain, */*",
//        "Accept-Language": "en-US,en;q=0.5",
//        "Sec-Fetch-Dest": "empty",
//        "Sec-Fetch-Mode": "cors",
//        "Sec-Fetch-Site": "cross-site"
//    },
//    "method": "GET",
//    "mode": "cors"
//});

//await fetch("https://api.pdok.nl/bzk/locatieserver/search/v3_1/lookup?fl=*&id=adr-8eba3e1f7fd5d73e3f3402da85f62b7c", {
//    "credentials": "omit",
//    "headers": {
//        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/114.0",
//        "Accept": "application/json, text/plain, */*",
//        "Accept-Language": "en-US,en;q=0.5",
//        "Sec-Fetch-Dest": "empty",
//        "Sec-Fetch-Mode": "cors",
//        "Sec-Fetch-Site": "cross-site"
//    },
//    "method": "GET",
//    "mode": "cors"
//});

//await fetch("https://www.wozwaardeloket.nl/wozwaardeloket-api/v1/wozwaarde/nummeraanduiding/1621200000027796", {
//    "credentials": "include",
//    "headers": {
//        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/114.0",
//        "Accept": "application/json, text/plain, */*",
//        "Accept-Language": "en-US,en;q=0.5",
//        "Sec-Fetch-Dest": "empty",
//        "Sec-Fetch-Mode": "cors",
//        "Sec-Fetch-Site": "same-origin"
//    },
//    "referrer": "https://www.wozwaardeloket.nl/",
//    "method": "GET",
//    "mode": "cors"
//});


//{
//	"properties": {
//		"identificatie": "1621010000027755",
//		"rdf_seealso": "http://bag.basisregistraties.overheid.nl/bag/id/verblijfsobject/1621010000027755",
//		"oppervlakte": 71,
//		"status": "Verblijfsobject in gebruik",
//		"gebruiksdoel": "woonfunctie",
//		"openbare_ruimte": "Dorpsstraat",
//		"huisnummer": 105,
//		"huisletter": "",
//		"toevoeging": "",
//		"postcode": "2665BH",
//		"woonplaats": "Bleiswijk",
//		"bouwjaar": 2017,
//		"pandidentificatie": "1621100000037510",
//		"pandstatus": "Pand in gebruik"
//	}
//}