housingEnricherNL

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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"
//	}
//}