HDB Flat Availability

Script to get the current available / taken flats for HDB BTO Selection

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         HDB Flat Availability
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  Script to get the current available / taken flats for HDB BTO Selection
// @author       You
// @match        https://homes.hdb.gov.sg/home/bto/details/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=undefined.
// @grant        none
// @license      MIT
// ==/UserScript==
(function () {
  "use strict";

  // Wait for 2 Seconds for Ajax queries to complete first before running script
  setTimeout(() => {
    main();
  }, 4000);

  function main() {
    let curr_date = new Date();
    let data = { blocks: {}, summary: {}, meta:{
      created_time:`${curr_date.toLocaleString()}}`,
      created_timestamp: `${curr_date.getTime()}`,
    } };
    let blocks = [];
    var taken_units = [];

    // Create a text area on screen
    let container_div = document.evaluate(
      "/html/body/app-root/div[2]/app-bto-details/section/div/div[5]/div/div[1]/div",
      document,
      null,
      XPathResult.FIRST_ORDERED_NODE_TYPE,
      null
    ).singleNodeValue;
    const text_area = document.createElement("textarea"); // For JSON message
    const text_area2 = document.createElement("textarea"); // For normal non telegram forammted message
    const text_area3 = document.createElement("textarea"); // tele msg fomatted
    const text_area4 = document.createElement("textarea"); // tele msg fomatted
    const text_area5 = document.createElement("textarea"); // Taken units
    // edit placeholder for text area
    text_area.placeholder = "JSON Output";
    text_area2.placeholder = "Formatted Messgage";
    text_area3.placeholder = "Telegram Formatted Message";
    text_area4.placeholder = "Paste Previous Data";
    text_area5.placeholder = "Taken Units";
    const start_btn = document.createElement("button");
    const status = document.createElement("p");

    // Get the first option in select
    let room_type = document.getElementById("choose-room-type").options[1].text;

    // Get the name of the project
    let project = document
      .getElementsByClassName("col-12 col-sm-8 col-md-8 mb-5 mb-md-0")[0]
      .getElementsByTagName("h4")[0].innerHTML;

    // Add function to button
    start_btn.innerHTML = "Start";
    start_btn.onclick = function () {
      runner();
    };
    container_div.appendChild(text_area4);
    container_div.appendChild(text_area);
    container_div.appendChild(start_btn);
    container_div.appendChild(text_area2);
    container_div.appendChild(text_area3);
    container_div.appendChild(text_area5);
    container_div.appendChild(status);
    function formatTeleMonospace(msg) {
      let newMsg = "";
      var lines = msg.split("\n");
      for (let i = 0; i < lines.length; i++) {
        if (lines[i].length != 0) {
          newMsg += `\`\`\`${lines[i]}\`\`\`\n`;
        } else {
          newMsg += "\n";
        }
      }
      return newMsg;
    }
    function formatMsg(json) {
      let msg = "";
      msg = `${project}\n${room_type}\n`;
      msg += `Generated on ${curr_date.toLocaleString()}\n\n`;

      for (const [block, blk_details] of Object.entries(json.blocks)) {
        let floors = blk_details["unit_info"];
        let quota = blk_details["quota"];
        let summary = blk_details["summary"];
        msg += `\n${block}\n`;

        let seen_units = {};
        // Parsing floors and units data
        for (const [floor, units] of Object.entries(floors)) {
          msg += floor;
          for (const [unit, unit_details] of Object.entries(units)) {
            if (unit_details.avail) {
              msg += "  🟢  ";
            } else {
              msg += "  🔴  ";
            }
            if (unit in seen_units) {
              seen_units[unit] += 1;
            } else {
              seen_units[unit] = 1;
            }
          }
          msg += "\n";
        }
        // Adding the Units at the bottom of the message
        msg += "   ";
        for (let unit of Object.keys(seen_units)) {
          msg += ` ${unit}  `;
        }
        // Adding the quoata details
        msg += "\n";
        for (let [race, qty] of Object.entries(quota)) {
          msg += `   ${race}:${qty}`;
        }
        msg += "\n";
        // Adding Block Summary
        msg += `   Total:${summary.total} Taken:${summary.taken} Avail:${summary.avail}\n`;
      }
      // Add Project Summary
      msg += `\nTotal:${json.summary.total} Taken:${json.summary.taken} Avail:${json.summary.avail}`;
      // Add Taken Units
      if (taken_units.length != 0){
        msg += `\n\nTaken Units:\n${taken_units.join("\n")}`;
      }
      console.debug(msg);
      return msg;
    }


    function runner() {
      console.debug("Running Script");

      let prev_data = null;
      if(text_area4.value.length != 0){
        prev_data = JSON.parse(text_area4.value);
      }

      // Get the list of different blocks
      var select_block = document.querySelector('[aria-label="Block"]');
      for (let i = 0; i < select_block.options.length; i++) {
        var option = select_block.options[i];
        if (option.innerHTML != "Choose Block No.") {
          let block_info = option.innerHTML;
          blocks.push(block_info);
          data["blocks"][block_info] = {};
        }
      }
      // Get the grid element
      let project_total = 0;
      let project_taken = 0;

      for (let k = 0; k < blocks.length; k++) {
        let block_total = 0;
        let block_taken = 0;

        console.debug("Inside " + blocks[k]);
        const grid = document.getElementById("available-grid");
        let floors = grid.getElementsByClassName("row level");
        select_block.value = k;
        // Trigger new select button change
        var event = new Event("change");
        select_block.dispatchEvent(event);

        let curr_block = blocks[k];

        // get quota
        let quota_elems = document
          .getElementById("available-sidebar")
          .getElementsByClassName("col-12 col-md-auto");
        let quota_obj = {};
        for (let quota of quota_elems) {
          let quota_split = quota.innerHTML.trim().split(": ");
          quota_obj[quota_split[0]] = quota_split[1];
        }
        data["blocks"][curr_block]["quota"] = quota_obj;
        data["blocks"][curr_block]["unit_info"] = {};

        // Iterate through each row in the grid
        for (let i = 0; i < floors.length; i++) {
          let curr_floor = floors[i];
          let labels_curr_floor = curr_floor.getElementsByTagName("label");
          let curr_floor_text = labels_curr_floor[0].innerHTML;
          if (
            data["blocks"][curr_block]["unit_info"][curr_floor_text] ==
            undefined
          ) {
            data["blocks"][curr_block]["unit_info"][curr_floor_text] = {};
          }
          for (let j = 1; j < labels_curr_floor.length; j++) {
            let curr_unit_label = labels_curr_floor[j]; // should have length of 0 = unit , 2 = sqm, 4 = price
            let curr_unit_nodes = curr_unit_label.childNodes; // should have length of 0 = unit , 2 = sqm, 4 = price
            let curr_p1 = curr_unit_nodes[0].data;
            let curr_p2 = curr_unit_nodes[2].data;
            let curr_p3 = curr_unit_nodes[4].data;
            let curr_p4 =
              curr_unit_label.parentElement.hasAttribute("disabled"); // to check if a unit is selected and unavailable
            block_total += 1;
            if (curr_p4) {
              block_taken += 1;
              if(prev_data && prev_data.blocks[curr_block].unit_info[curr_floor_text][curr_p1].avail){
                // predict ethnic type
                let ethnic_type = "NA";
                let prev_quota = prev_data.blocks[curr_block].quota;

                for (const race in prev_quota){
                  if(quota_obj[race] !== prev_quota[race]){
                    ethnic_type = race
                    prev_quota[race] = (parseInt(prev_quota[race]) + 1).toString();
                  }
                }

                taken_units.push(`${curr_block} ${curr_floor_text} ${curr_p1} ${ethnic_type}`);
              }
            }

            data["blocks"][curr_block]["unit_info"][curr_floor_text][curr_p1] =
              {
                sqm: curr_p2,
                price: curr_p3,
                avail: !curr_p4,
              };
          }
        }
        data["blocks"][curr_block]["summary"] = {
          total: block_total,
          taken: block_taken,
          avail: block_total - block_taken,
        };

        project_total += block_total;
        project_taken += block_taken;
      }
      data.summary.total = project_total;
      data.summary.taken = project_taken;
      data.summary.avail = project_total - project_taken;

      text_area.value = JSON.stringify(data);
      let msg = formatMsg(data);
      text_area2.value = msg;
      text_area3.value = formatTeleMonospace(msg);
      text_area5.value = taken_units.join("\n");
      console.debug("Script Finished");
    }

  }
})();