diff --git a/src/js/contentscript.js b/src/js/contentscript.js index c57e64d..a8d7033 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -16,6 +16,7 @@ import App from './components/App.vue'; RR.logInfo('contentscript loaded'); chrome.runtime.sendMessage({ 'switchIconOn': true }); +const POLL_ADDRESS_TIMEOUT = 500; const initVueTranslations = () => { return new Promise((resolve, reject) => { @@ -66,7 +67,7 @@ const loadPanel = function(address) { address, details: { price: { - perSquareMeter: pageDataExtractor.getPrices(window.location.host) + perSquareMeter: pageDataExtractor.extractSquarePrice() } } } @@ -78,7 +79,7 @@ const loadPanel = function(address) { let addressOfProperty; function initApp() { RR.logInfo('Initializing app widget'); - addressOfProperty = pageDataExtractor.getAddress(window.location.host); + addressOfProperty = pageDataExtractor.getAddress(); RR.logDebug('Address parsed: ', addressOfProperty); if (RR.String.isNotBlank(addressOfProperty)) { @@ -94,13 +95,13 @@ function initApp() { let pollAddressTimerId; function pollAddress() { //RR.logDebug('Polling address...'); // you can filter it out in console with regexp filter ^(?=.*?\b.*\b)((?!Poll).)*$ (match all except lines with 'Poll' match) - const currentAddressOfProperty = pageDataExtractor.getAddress(window.location.host); + const currentAddressOfProperty = pageDataExtractor.getAddress(); //RR.logDebug('Polled address:', currentAddressOfProperty); if (currentAddressOfProperty !== addressOfProperty) { $(document).trigger(RR.ADDRESS_CHANGED_EVENT); clearTimeout(pollAddressTimerId); } - pollAddressTimerId = setTimeout(pollAddress, 500); + pollAddressTimerId = setTimeout(pollAddress, POLL_ADDRESS_TIMEOUT); } $(document).on(RR.ADDRESS_CHANGED_EVENT, (event) => { diff --git a/src/js/sites/index.js b/src/js/sites/index.js index 43ddb1a..c211303 100644 --- a/src/js/sites/index.js +++ b/src/js/sites/index.js @@ -1,3 +1,6 @@ +const RENT = 'rent'; +const SALE = 'sale'; + const textOrNull = textElement => { if (textElement === null) { return null; @@ -6,57 +9,91 @@ const textOrNull = textElement => { } }; -export const siteHosts = { +const sites = { SREALITY: { - hostString: 'sreality.cz' + id: 'sreality', }, BEZREALITKY: { - hostString: 'bezrealitky.cz' + id: 'bezrealitky', }, MAXIREALITY: { - hostString: 'maxirealitypraha.cz' + id: 'maxirealitypraha', }, REALITY_IDNES: { - hostString: 'reality.idnes.cz' + id: 'idnes', } }; -/** - * - * @param {{ hostString: String }} hostId - * @param {String} host - * @return {Number|undefined} - */ -export const isCurrentHost = (hostId, host) => host.includes(hostId.hostString); - const priceAreaGuard = (price, area) => (area && !isNaN(area) && (price && !isNaN(price))) && price / area; +const getHostPredicate = (locationHost) => (siteHost) => locationHost.includes(siteHost); + +const containsBoxWords = (selector, words) => { + const containsNodeWord = (node, word) => node.textContent.includes(word); + + const node = document.querySelector(selector); + if (!node || !words.length) { + return false; + } + + const mapWords = (word) => containsNodeWord(node, word); + // ['foo'] => [true] + // ['foo', 'bar', 'baz'] => [true, false, true] => false + return words.map(mapWords).filter(Boolean).length === words.length; +}; + +// this is business logic, so it may contain site specific settings/params +// underneath it should only call some generic functions +const extractAdType = (locationHost) => { + const verify = getHostPredicate(locationHost); + + if (verify(sites.SREALITY.id) || verify(sites.SREALITY.id) || verify(sites.MAXIREALITY.id)) { + if (/pronajem/i.test(location.pathname)) { + return RENT; + } + return SALE; + } + + if (verify('bezrealitky')) { + const selector = '.box-params.col-1'; + return containsBoxWords(selector, ['typ', 'nabídky', 'Pronájem']) ? RENT : SALE; + } +}; + // TODO add extractor's methods for sites dynamically export const extractors = { - getAddress(host) { - if (isCurrentHost(siteHosts.SREALITY, host)) { + getAddress() { + const verify = getHostPredicate(window.location.host); + if (verify(sites.SREALITY.id)) { return textOrNull(document.querySelector('.location-text')); } - if (isCurrentHost(siteHosts.BEZREALITKY, host)) { + if (verify(sites.BEZREALITKY.id)) { return textOrNull(document.querySelector('header h2')); } - if (isCurrentHost(siteHosts.MAXIREALITY, host)) { + if (verify(sites.MAXIREALITY.id)) { const addressRow = Array.from(document.querySelectorAll('tr')) .filter(node => node.textContent.includes('Adresa'))[0]; return addressRow && addressRow.querySelector('td').innerHTML.replace(/
/g, ' ').trim(); } - if (isCurrentHost(siteHosts.REALITY_IDNES, host)) { + if (verify(sites.REALITY_IDNES.id)) { return textOrNull(document.querySelector('.realAddress')); } RR.logError('cannot parse address on page: ', window.location); return null; }, - getPrices(host) { - if (isCurrentHost(siteHosts.SREALITY, host)) { + extractSquarePrice() { + const adType = extractAdType(window.location.host); + const verify = getHostPredicate(window.location.host); + + if (adType === RENT) { + return; + } + + if (verify(sites.SREALITY.id)) { const propertyParams = Array.from(document.querySelectorAll('.params li')); const priceRow = propertyParams.filter(p => p.innerHTML.includes('Celková cena'))[0]; const areaRow = propertyParams.filter(p => p.innerHTML.includes('Užitná'))[0]; @@ -66,7 +103,7 @@ export const extractors = { return priceAreaGuard(price, area); } - if (isCurrentHost(siteHosts.BEZREALITKY, host)) { + if (verify(sites.BEZREALITKY.id)) { const propertyParams = Array.from(document.querySelectorAll('.box-params .row')); const areaRow = propertyParams.filter(item => item.innerHTML.includes('plocha'))[0]; // returns DOM node const priceRow = propertyParams.filter(item => item.innerHTML.includes('cena'))[0]; // returns DOM node @@ -80,7 +117,7 @@ export const extractors = { return priceAreaGuard(price, area); } - if (isCurrentHost(siteHosts.MAXIREALITY, host)) { + if (verify(sites.MAXIREALITY.id)) { const areaRow = Array.from(document.querySelectorAll('#makler_zaklad > table tr')) .filter(node => node.innerHTML.includes('Užitná plocha'))[0]; const priceNode = document.querySelector('.two.price'); @@ -90,12 +127,12 @@ export const extractors = { return priceAreaGuard(price, area); } - if (isCurrentHost(siteHosts.REALITY_IDNES, host)) { + if (verify(sites.REALITY_IDNES.id)) { const areaText = $('.parameters .leftCol dt:contains("Užitná plocha")').next().text(); const area = Number.parseInt(areaText); // eg. when text is "34 m2" Number.parseInt can strip text parts and parse it as just 34 - const priceText = document.querySelectorAll('.priceBox strong')[0].innerHTML; - const price = Number.parseInt(priceText.replace(/ /gi,'')); + const priceText = document.querySelectorAll('.priceBox strong')[0].innerHTML; + const price = Number.parseInt(priceText.replace(/ /gi, '')); return priceAreaGuard(price, area); } diff --git a/src/js/sites/index.spec.js b/src/js/sites/index.spec.js index 7705ef0..03c7e61 100644 --- a/src/js/sites/index.spec.js +++ b/src/js/sites/index.spec.js @@ -25,11 +25,11 @@ describe('extractors', () => { }); it('should return price per m2', () => { - expect(extractors.getPrices(window.location.host)).to.equal(22222.222222222223); + expect(extractors.extractSquarePrice()).to.equal(22222.222222222223); }); it('should return address', () => { - expect(extractors.getAddress(window.location.host)).to.equal('Komenského, Vlašim, Středočeský kraj'); + expect(extractors.getAddress()).to.equal('Komenského, Vlašim, Středočeský kraj'); }); }); @@ -43,11 +43,11 @@ describe('extractors', () => { }); it('should return price per m2', () => { - expect(extractors.getPrices(window.location.host)).to.equal(57042.25352112676); + expect(extractors.extractSquarePrice()).to.equal(57042.25352112676); }); it('should return address', () => { - expect(extractors.getAddress(window.location.host)).to.equal('Ortenovo náměstí, Praha 7 - Holešovice'); + expect(extractors.getAddress()).to.equal('Ortenovo náměstí, Praha 7 - Holešovice'); }); }); @@ -61,11 +61,11 @@ describe('extractors', () => { }); it('should return price per m2', () => { - expect(extractors.getPrices(window.location.host)).to.equal(64805.194805194806); + expect(extractors.extractSquarePrice()).to.equal(64805.194805194806); }); it('should return address', () => { - expect(extractors.getAddress(window.location.host)).to.equal('Praha - Smíchov Vrázova'); + expect(extractors.getAddress()).to.equal('Praha - Smíchov Vrázova'); }); }); @@ -83,11 +83,11 @@ describe('extractors', () => { }); it('should return price per m2', () => { - expect(extractors.getPrices(window.location.host)).to.equal(31888.88888888889); + expect(extractors.extractSquarePrice()).to.equal(31888.88888888889); }); it('should return address', () => { - expect(extractors.getAddress(window.location.host)).to.equal('Praha 5, Hlubočepy, Machatého'); + expect(extractors.getAddress()).to.equal('Praha 5, Hlubočepy, Machatého'); }); }); }); diff --git a/src/js/utils.js b/src/js/utils.js index bc60988..fd9b16f 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -7,7 +7,14 @@ export const streetNamePredicate = (address) => { return address; }; -export const formatPrice = price => Math.round(price) + ' Kč'; +export const formatPrice = price => { + const formatter = new Intl.NumberFormat('cs', { + style: 'currency', + currency: 'CZK', + minimumFractionDigits: 0, + }); + return formatter.format(Math.round(price)); +}; /** * Call ga (google analytics) in context of current page - we cannot directly call page functions here diff --git a/src/templates/panel.html b/src/templates/panel.html index 38929f7..4e203aa 100644 --- a/src/templates/panel.html +++ b/src/templates/panel.html @@ -4,7 +4,6 @@