Separating code into files.
Bundling the code with node. Adding some neccesary files.
This commit is contained in:
parent
62825829e2
commit
73ae583881
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
app.bundle.js
|
191
app.js
191
app.js
@ -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
20
bundler.js
Normal 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
7629
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
Normal file
24
package.json
Normal 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
45
src/Configuration.js
Normal 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
71
src/Item.js
Normal 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
87
src/ItemHTML.js
Normal 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>`;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user