Separating code into files.

Bundling the code with node.
Adding some neccesary files.
This commit is contained in:
Jorge Bolois Guerrero 2022-07-05 20:09:49 +02:00
parent 62825829e2
commit 73ae583881
8 changed files with 7898 additions and 171 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/node_modules
app.bundle.js

191
app.js
View File

@ -1,39 +1,14 @@
// ==UserScript==
// @name Idealista Enhancer
// @description Just some information for idealista.com
// @version 0.1.0
// @author Midefos
// @namespace https://github.com/Midefos
// @match https://www.idealista.com/*
// @require https://code.jquery.com/jquery-3.6.0.slim.min.js
// @license MIT
// ==/UserScript==
/* jshint esversion: 6 */
this.$ = this.jQuery = jQuery.noConflict(true);
import Configuration from "./src/Configuration.js";
import Item from "./src/Item.js";
import $ from 'jquery';
import ItemHTML from "./src/ItemHTML.js";
// Idealista selectors.
const ITEM_SELECTOR = 'article.item';
const NAME_SELECTOR = '.item-link';
const PRICE_SELECTOR = '.item-price';
const ROOM_SELECTOR = '.item-detail-char .item-detail:nth-child(1)';
const METERS_SELECTOR = '.item-detail-char .item-detail:nth-child(2)';
const ADDITIONAL_INFORMATION_SELECTOR = '.item-detail-char .item-detail:nth-child(3)';
// Own selectors.
const CONFIG_CLASS = 'midefos-idealista-config'
const DEFAULT_CONFIG = {
enabled: true,
percentages: true,
garage: false,
exterior: true,
lever: true,
'max-price': 120_000,
'max-price-per-meter': 1_500
}
const STYLES = `
.midefos-idealista-container {
display: flex;
@ -62,13 +37,13 @@ const STYLES = `
.${CONFIG_CLASS} {
display: none;
position: fixed;
z-index: 3;
z-index: 3;
background-color: rgba(225, 245, 110, 0.90);
width: 95%;
height: 95%;
height: 95%;
top: 2.5%;
left: 2.5%;
left: 2.5%;
padding: 2rem;
}
@ -84,28 +59,15 @@ const STYLES = `
}
`;
let currentConfig;
function init() {
currentConfig = getConfig()
Configuration.init();
addStyles();
createMenu();
createInformation();
}
function getConfig() {
const storageConfig = localStorage.getItem('midefos-idealista');
if (!storageConfig) return DEFAULT_CONFIG;
return JSON.parse(storageConfig);
}
function setConfig(config) {
localStorage.setItem('midefos-idealista', JSON.stringify(config));
currentConfig = config;
}
function createMenu() {
if (window.location.href.includes('mapa-google')) return;
@ -127,7 +89,7 @@ function createMenu() {
percentages: configNode.querySelector('#percentages').checked,
garage: configNode.querySelector('#garage').checked,
exterior: configNode.querySelector('#exterior').checked,
lever: configNode.querySelector('#lever').checked,
lift: configNode.querySelector('#lift').checked,
'max-price': configNode.querySelector('#max-price').value,
'max-price-per-meter': configNode.querySelector('#max-price-per-meter').value
}
@ -146,10 +108,11 @@ function createMenu() {
function createInformation() {
const items = document.querySelectorAll(ITEM_SELECTOR);
for (const item of items) {
const currentInformation = item.querySelector('.midefos-idealista-container');
for (const _item of items) {
const item = new Item(_item);
const currentInformation = _item.querySelector('.midefos-idealista-container');
if (currentInformation) currentInformation.remove();
item.innerHTML += createItemInformationHTML(item);
_item.innerHTML += ItemHTML.createInformation(item);
}
}
@ -160,45 +123,45 @@ function createConfigHTML() {
<h3>Configuración global:</h3>
<label>
<input type='checkbox' id='enabled' checked='${currentConfig.enabled}'>
<input type='checkbox' id='enabled' checked='${Configuration.get('enabled')}'>
<span>Habilitado</span>
</label>
<h3>Busqueda:</h3>
<label>
<input type='checkbox' id='percentages' checked='${currentConfig.percentages}'>
<input type='checkbox' id='percentages' checked='${Configuration.get('percentages')}'>
<span>Porcentajes</span>
</label>
<br>
<label>
<input type='checkbox' id='garage' checked='${currentConfig.garage}'>
<input type='checkbox' id='garage' checked='${Configuration.get('garage')}'>
<span>Garaje</span>
</label>
<br>
<label>
<input type='checkbox' id='exterior' checked='${currentConfig.exterior}'>
<input type='checkbox' id='exterior' checked='${Configuration.get('exterior')}'>
<span>Exterior</span>
</label>
<br>
<label>
<input type='checkbox' id='lever' checked='${currentConfig.lever}'>
<input type='checkbox' id='lift' checked='${Configuration.get('lift')}'>
<span>Ascensor</span>
</label>
<br>
<label>
<span>Precio máximo: </span>
<input type='number' id='max-price' value='${currentConfig['max-price']}' placeholder='0'>
<input type='number' id='max-price' value='${Configuration.get('max-price')}' placeholder='0'>
</label>
<br>
<label>
<span>Precio máximo por metro: </span>
<input type='number' id='max-price-per-meter' value='${currentConfig['max-price-per-meter']}' placeholder='0'>
<input type='number' id='max-price-per-meter' value='${Configuration.get('max-price-per-meter')}' placeholder='0'>
</label>
<br>
@ -215,120 +178,6 @@ function createMenuHTML() {
</div>`;
}
function createItemInformationHTML(item) {
return `
<div class='midefos-idealista-container'>
${createPercentagePriceHTML(item)}
${createPriceHTML(item)}
${createPriceMeterHTML(item)}
${createLeverHTML(item)}
${createInteriorHTML(item)}
</div>`
}
function findPrice(item) {
const priceText = item.querySelector(PRICE_SELECTOR).textContent;
return Number(priceText.replace('€', '').replace('.', '').replace(',', ''));
}
function findMeters(item) {
let metersText = item.querySelector(METERS_SELECTOR).textContent;
if (!metersText.includes('m²')) metersText = item.querySelector(ROOM_SELECTOR).textContent;
return Number(metersText.replace('m²', ''));
}
function findPriceMeter(item) {
const meters = findMeters(item);
const price = findPrice(item);
return Math.round(price / meters);
}
function createPercentagePriceHTML(item) {
if (!currentConfig.percentages) return ``;
const price = findPrice(item);
const twentyPercent = Math.round(price * 20 / 100);
const thirtyPercent = Math.round(price * 30 / 100);
return `
<span>
<strong>20%:</strong> ${twentyPercent} <br>
<strong>30%:</strong> ${thirtyPercent}
</span>`;
}
function createPriceHTML(item) {
const price = findPrice(item);
const desiredPrice = currentConfig['max-price'];
const desiredTwentyFivePercentMore = Math.round(desiredPrice * 1.25);
if (price <= desiredPrice) {
return `<span class='success'><strong>✓</strong> Precio</span>`;
} else if (price <= desiredTwentyFivePercentMore) {
return `<span class='warning'><strong>✓</strong> Precio</span>`;
}
return `<span class='error'><strong>X</strong> Precio</span>`;
}
function createPriceMeterHTML(item) {
const priceMeter = findPriceMeter(item);
const desiredPricePerMeter = currentConfig['max-price-per-meter'];
const desiredTwentyFivePercentMore = Math.round(desiredPricePerMeter * 1.25);
if (priceMeter <= desiredPricePerMeter) {
return `<span class='success'><strong>m²:</strong> ${priceMeter}€</span>`;
} else if (priceMeter <= desiredTwentyFivePercentMore) {
return `<span class='warning'><strong>m²:</strong> ${priceMeter}€</span>`;
}
return `<span class='error'><strong>m²:</strong> ${priceMeter}€</span>`;
}
function createLeverHTML(item) {
if (!currentConfig.lever) return ``;
const additionalInfo = item.querySelector(ADDITIONAL_INFORMATION_SELECTOR);
if (!additionalInfo) {
if (!isFlat(item)) return ``;
return `<span class='warning'><strong>?</strong> Ascensor</span>`
}
const leverText = additionalInfo.textContent;
const hasLever = leverText.includes('con ascensor');
if (hasLever) {
return `<span class='success'><strong>✓</strong> Ascensor</span>`
}
return `<span class='error'><strong>X</strong> Ascensor</span>`;
}
function createInteriorHTML(item) {
if (!currentConfig.exterior) return ``;
const additionalInfo = item.querySelector(ADDITIONAL_INFORMATION_SELECTOR);
if (!additionalInfo) {
if (!isFlat(item)) return ``;
return `<span class='warning'><strong>?</strong> Exterior</span>`
}
const interiorText = additionalInfo.textContent;
const hasLever = interiorText.includes('exterior');
if (hasLever) {
return `<span class='success'><strong>✓</strong> Exterior</span>`
}
return `<span class='error'><strong>X</strong> Exterior</span>`;
}
function isFlat(item) {
return item.querySelector(NAME_SELECTOR)
.textContent.includes('Piso');
}
init();
function addStyles() {

20
bundler.js Normal file
View File

@ -0,0 +1,20 @@
import fs from 'fs';
import browserify from 'browserify';
import banner from 'browserify-banner';
const STATIC_BANNER = "// ==UserScript==\n" +
"// @name Idealista Enhancer\n" +
"// @description Just some information for idealista.com\n" +
"// @version 0.1.0\n" +
"// @author Midefos\n" +
"// @namespace https://github.com/Midefos\n" +
"// @match https://www.idealista.com/*\n" +
"// @license MIT\n" +
"// ==/UserScript==\n" +
"/* jshint esversion: 6 */\n\n";
browserify("./app.js")
.transform("babelify", { presets: ["@babel/preset-env"] })
.plugin(banner, { banner: STATIC_BANNER })
.bundle()
.pipe(fs.createWriteStream("app.bundle.js"));

7629
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"type": "module",
"scripts": {
"bundle": "node bundler.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Midefos/idealista-enhancer.git"
},
"author": "Midefos",
"license": "MIT",
"bugs": {
"url": "https://github.com/Midefos/idealista-enhancer/issues"
},
"homepage": "https://github.com/Midefos/idealista-enhancer#readme",
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"babelify": "^10.0.0",
"browserify": "^17.0.0",
"browserify-banner": "^2.0.4",
"jquery": "^3.6.0"
}
}

45
src/Configuration.js Normal file
View File

@ -0,0 +1,45 @@
export default class Configuration {
static NAME = 'midefos-idealista';
static _current;
static init() {
this._current = this._getConfig();
}
static get(key) {
const config = this._getConfig();
return config[key];
}
static save(config) {
window.localStorage.setItem(this.NAME, JSON.stringify(config));
this._current = config;
}
static _getConfig() {
const storageConfig = this._getFromLocalStorage();
if (!storageConfig) return this._default();
return storageConfig;
}
static _getFromLocalStorage() {
const storageConfig = window.localStorage.getItem(this.NAME);
if (!storageConfig) return null;
return JSON.parse(storageConfig);
}
static _default() {
return {
enabled: true,
percentages: true,
garage: false,
exterior: true,
lift: true,
'max-price': 120_000,
'max-price-per-meter': 1_500
}
}
}

71
src/Item.js Normal file
View File

@ -0,0 +1,71 @@
export default class Item {
static NAME_SELECTOR = '.item-link';
static PRICE_SELECTOR = '.item-price';
static ROOM_SELECTOR = '.item-detail-char .item-detail:nth-child(1)';
static METERS_SELECTOR = '.item-detail-char .item-detail:nth-child(2)';
static ADDITIONAL_INFORMATION_SELECTOR = '.item-detail-char .item-detail:nth-child(3)';
constructor(htmlNode) {
this._node = htmlNode;
this.name = this._extractName();
this.price = this._extractPrice();
this.meters = this._extractMeters();
this.priceMeter = this._extractPriceMeter();
this.additionalInfo = this._extractAdditionalInfo();
this.hasLift = this._extractLift();
this.isExterior = this._extractExterior();
}
_extractName() {
return this._node.querySelector(Item.NAME_SELECTOR).textContent;;
}
_extractPrice() {
const priceText = this._node.querySelector(Item.PRICE_SELECTOR).textContent;
return Number(priceText.replace('€', '').replace('.', '').replace(',', ''));
}
_extractMeters() {
let metersText = this._node.querySelector(Item.METERS_SELECTOR).textContent;
if (!metersText.includes('m²')) {
metersText = this._node.querySelector(Item.ROOM_SELECTOR).textContent;
}
return Number(metersText.replace('m²', ''));
}
_extractPriceMeter() {
return Math.round(this.price / this.meters);
}
_extractAdditionalInfo() {
const additionalInfo = this._node.querySelector(Item.ADDITIONAL_INFORMATION_SELECTOR);
if (!additionalInfo) return null;
return additionalInfo.textContent;
}
_extractLift() {
if (!this.additionalInfo) return false;
return this.additionalInfo.includes('con ascensor');
}
_extractExterior() {
if (!this.additionalInfo) return false;
return this.additionalInfo.includes('exterior');
}
isFlat() {
return this.name.includes('Piso');
}
isHouse() {
return this.name.includes('Casa');
}
isGround() {
return this.name.includes('Bajo');
}
}

87
src/ItemHTML.js Normal file
View File

@ -0,0 +1,87 @@
import Configuration from "./Configuration.js";
export default class ItemHTML {
static createInformation(item) {
return `
<div class='midefos-idealista-container'>
${this._createPercentagePriceHTML(item)}
${this._createPriceHTML(item)}
${this._createPriceMeterHTML(item)}
${this._createLiftHTML(item)}
${this._createInteriorHTML(item)}
</div>`
}
static _createPercentagePriceHTML(item) {
if (!Configuration.get('percentages')) return ``;
const twentyPercent = Math.round(item.price * 20 / 100);
const thirtyPercent = Math.round(item.price * 30 / 100);
return `
<span>
<strong>20%:</strong> ${twentyPercent} <br>
<strong>30%:</strong> ${thirtyPercent}
</span>`;
}
static _createPriceHTML(item) {
const desiredPrice = Configuration.get('max-price');
const desiredTwentyFivePercentMore = Math.round(desiredPrice * 1.25);
if (item.price <= desiredPrice) {
return `<span class='success'><strong>✓</strong> Precio</span>`;
} else if (item.price <= desiredTwentyFivePercentMore) {
return `<span class='warning'><strong>✓</strong> Precio</span>`;
}
return `<span class='error'><strong>X</strong> Precio</span>`;
}
static _createPriceMeterHTML(item) {
const desiredPricePerMeter = Configuration.get('max-price-per-meter');
const desiredTwentyFivePercentMore = Math.round(desiredPricePerMeter * 1.25);
if (item.priceMeter <= desiredPricePerMeter) {
return `<span class='success'><strong>m²:</strong> ${item.priceMeter}€</span>`;
} else if (item.priceMeter <= desiredTwentyFivePercentMore) {
return `<span class='warning'><strong>m²:</strong> ${item.priceMeter}€</span>`;
}
return `<span class='error'><strong>m²:</strong> ${item.priceMeter}€</span>`;
}
static _createLiftHTML(item) {
if (!Configuration.get('lift')) return ``;
if (!item.additionalInfo) {
if (item.isFlat()) return ``;
return this.__createIndividual('?', 'Ascensor', 'warning');
}
if (item.hasLift) {
return this.__createIndividual('✓', 'Ascensor', 'warning');
}
return this.__createIndividual('X', 'Ascensor', 'error');
}
static _createInteriorHTML(item) {
if (!Configuration.get('exterior')) return ``;
if (!item.additionalInfo) {
if (!item.isFlat()) return ``;
return `<span class='warning'><strong>?</strong> Exterior</span>`
}
if (item.isExterior) {
return `<span class='success'><strong>✓</strong> Exterior</span>`
}
return `<span class='error'><strong>X</strong> Exterior</span>`;
}
static __createIndividual(strongText, infoText, className = '') {
return `<span class='${className}'><strong>${strongText}</strong> ${infoText}</span>`;
}
}