您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Script to get the current available / taken flats for HDB BTO Selection
// ==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"); } } })();