NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Rightmove Map Enhanced
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Extra info in property map view
// @author You
// @license MIT
// @match https://www.rightmove.co.uk/property-*/map.html*
// @icon https://www.google.com/s2/favicons?domain=rightmove.co.uk
// @require http://code.jquery.com/jquery-latest.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.core.js
// @connect localhost
// @connect bestmove.loophole.site
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @grant GM_addStyle
// @resource MATERIAL_ICONS https://fonts.googleapis.com/icon?family=Material+Icons
// ==/UserScript==
/* globals $ _ */
const propertyCoordinatePair = (property) => `${property.location.latitude},${property.location.longitude}`;
const state = {
currentPropertyId: null,
propertiesById: {},
scoresById: {},
waitingPropertyInfo: false
};
const renderScore = (property) => {
const propertyScore = state.scoresById[property.id];
const coordinatePair = propertyCoordinatePair(property);
const groceryListView = _.compact(propertyScore.scores?.groceries).map((grocery) => {
return `
<div class="grocery-item">
<span class="material-icons">shopping_cart</span>
<a href="https://www.google.com/maps/dir/${coordinatePair}/${grocery.latitude},${grocery.longitude}/data=!4m2!4m1!3e2" target="_blank">
<span class="score-text">${grocery.store_name} (${grocery.postcode}, ${grocery.size_band})</span>
</a>
</div>`;
}).join('');
$('.score-section').remove();
$(
`
<div class="score-section">
<div class="imd-list">
<span class="material-icons">paid</span><span class="score-text">${propertyScore.scores?.income_score?.toFixed(1)}</span>
<span class="material-icons">work</span><span class="score-text">${propertyScore.scores?.employment_score?.toFixed(1)}</span>
<span class="material-icons">school</span><span class="score-text">${propertyScore.scores?.skill_score?.toFixed(1)}</span>
<span class="material-icons">security</span><span class="score-text">${propertyScore.scores?.crime_score?.toFixed(1)}</span>
<span class="material-icons">apartment</span><span class="score-text">${propertyScore.scores?.housing_barrier_score?.toFixed(1)}</span>
<span class="material-icons">park</span><span class="score-text">${propertyScore.scores?.environment_score?.toFixed(1)}</span>
IMD<span class="score-text">${propertyScore.scores?.imd_score?.toFixed(1)}</span>
</div>
<div class="grocery-list">${groceryListView}</div>
<a href="https://www.google.com/maps/dir/${coordinatePair}/birmingham+new+street+station/data=!4m2!4m1!3e3" target="_blank">
<span class="material-icons">directions_bus_filled</span><span class="score-text">${propertyScore.scores?.distance_to_town_center?.toFixed(1)}</span>
</a>
<a href="https://www.google.com/maps/search/${coordinatePair}" target="_blank"><span class="material-icons">map</span><span class="score-text">${propertyScore.postcode}</span></a>
<a href="https://epc.opendatacommunities.org/domestic/search?postcode=${propertyScore.postcode}" target="_blank"><span class="material-icons">bolt</span><span class="score-text">Cert</span></a>
<a href="https://www.streetcheck.co.uk/postcode/${propertyScore.postcode.split(' ').join('')}" target="_blank"><span class="material-icons">groups</span></a>
</div>
`
).insertAfter('.expPropCardDescription');
};
const loadScore = function(propertyId) {
if (!propertyId) { return; }
state.currentPropertyId = propertyId;
const property = state.propertiesById[propertyId];
if (!property) {
// propertyInfo is not ready, waiting for ajax
state.waitingPropertyInfo = true;
return;
}
state.waitinfForPropertyInfo = false;
const scoreParams = {
coordinate_pairs: [property].map(propertyCoordinatePair),
expand: ['groceries'],
};
if (state.scoresById[property.id]) {
renderScore(property);
return;
}
GM_xmlhttpRequest({
method: 'GET',
url: `http://bestmove.loophole.site/scores?${$.param(scoreParams)}`,
responseType: 'json',
onload: function(res) {
const payload = res.response;
const coordinatePair = propertyCoordinatePair(property);
const propertyScore = payload[coordinatePair];
state.scoresById[property.id] = propertyScore;
renderScore(property);
}
});
};
(function() {
'use strict';
GM_addStyle(GM_getResourceText("MATERIAL_ICONS"));
GM_addStyle(
`
.score-section .material-icons {
font-size: 18px;
}
.score-section .score-text {
vertical-align: top;
margin-right: 5px;
}
.score-section a {
color: inherit;
}
`
);
$('.mapSidePanel').bind('DOMSubtreeModified', function(e) {
if (e.target.innerHTML.length === 0) {
return;
}
const propertyId = $('.expPropCardPropertyLinkButton a').attr('href')?.split('/')[2];
if (propertyId && propertyId !== state.currentPropertyId) {
loadScore(propertyId);
}
});
})();
(function(open) {
XMLHttpRequest.prototype.open = function() {
this.addEventListener("readystatechange", function() {
if(this.readyState !== 4 || !Array.isArray(this.response) || !this.response[0].id) {
return;
}
this.response.forEach((property) => {
state.propertiesById[property.id.toString()] = property;
if (state.waitingPropertyInfo && property.id == state.currentPropertyId) {
loadScore(property.id.toString());
}
});
}, false);
open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);