NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Sbermarket price per unit calculator
// @namespace http://tampermonkey.net/
// @version 1.6
// @description Добавление цены за единицу объема
// @author anador
// @license MIT
// @include https://sbermarket.ru/*
// @grant none
// ==/UserScript==
(function () {
function getPageType() {
// любая страница со списком продуктов
if (document.querySelectorAll('.product') && document.querySelectorAll('.product').length > 0) {
return "productList";
}
// страница избранного
else if (location.pathname === '/user/favorites') {
//console.log('favorites found');
return 'favorites';
}
else {
//console.log('nothing found');
return false;
}
}
//вычисление цены за ед. измерения
function countPricePerUnit(price, unit, volumeValue) {
if (unit === 'г') {
return {
outputPrice: price / (volumeValue * 0.001),
outputUnit: "кг",
}
}
else if (unit === 'кг') {
return {
outputPrice: price / (volumeValue),
outputUnit: "кг",
}
}
else if (unit === 'л') {
return {
outputPrice: price / (volumeValue),
outputUnit: "л",
}
}
else if (unit === 'мл') {
return {
outputPrice: price / (volumeValue * 0.001),
outputUnit: "л",
}
}
else {
return false;
}
};
//вставка элемента с ценой за ед. измерения
function insertCountedPrice(elem, price, unit) {
elem.insertAdjacentHTML('afterend', `<p class="pricePerUnit" style="
bottom: 0;
color: #9fabb7;
font-size: 13px;
line-height: 1;
position: absolute;
right: 0px;
">
${price} ₽ / ${unit}
</p>`)
}
//вставка элемента с ценой за ед. измерения для favorites
function insertCountedPriceForFavorites(elem, price, unit) {
elem.insertAdjacentHTML('afterend', `<p class="pricePerUnit" style="
position: absolute;
bottom: 10px;
font-size: 13px;
color: #8f8e94;
right: 12px;
// font-weight: 700;
">
${price} ₽ / ${unit}
</p>`)
}
//вставка элемента с ценой за ед. измерения для popup
function insertCountedPriceForPopup(elem, price, unit) {
elem.insertAdjacentHTML('afterend', `<p class="pricePerUnit" style="
position: absolute;
margin-top: -38px;
right: 0px;
color: #8f8e94;
font-size: 15px;
font-weight: 400;
letter-spacing: .2px;
">
${price} ₽ / ${unit}
</p>`)
}
//вставка элемента с ценой за ед. измерения для выпадающих результатов поиска
function insertCountedPriceForLiveSearchResults(elem, price, unit) {
elem.insertAdjacentHTML('afterend', `<span class="pricePerUnit" style="
color: #888;
font-size: 13px;
line-height: 1;
margin-left: 1em;
">
${price} ₽ / ${unit}
</p>`)
}
function productsHandler() {
productsList = document.querySelectorAll('.product');
productsList.forEach(el => {
if (el.querySelector('div[class="price price--default"]') && el.querySelector('div[class="price price--default"]').innerText) {
priceString = el.querySelector('div[class="price price--default"]').innerText.replace(/ /g, '').replace(/,/g, '.');
price = parseFloat(priceString);
volumeString = el.querySelector('.product__volume').innerText;
volumeValue = volumeString.split(' ')[0].replace(/,/, '.');
volumeUnit = volumeString.split(' ')[1];
if (countPricePerUnit(price, volumeUnit, volumeValue) !== false) {
pricePerUnit = countPricePerUnit(price, volumeUnit, volumeValue).outputPrice.toFixed(2);
finalUnit = countPricePerUnit(price, volumeUnit, volumeValue).outputUnit;
//вставка элемента
pricePerUnitWithComma = pricePerUnit.replace(/\./, ','); // замена точки между разрядами на запятую обратно
if (!el.querySelector('.pricePerUnit')) {
insertCountedPrice(el.querySelector('.product__volume'), pricePerUnitWithComma, finalUnit);
}
}
}
});
//обсервер для отслеживания подрузки товаров при скролле
observer = new MutationObserver((mutationsList) => {
//if (mutationsList[0].removedNodes.length > 0) { //пока убрал, сейчас работает и без этого
observer.disconnect();
//console.log(mutationsList)
console.log('Products changed');
productsHandler();
//}
});
if (document.querySelector('.load_container')) {
observer.observe(document.querySelector('.load_container'), {
childList: true,
//subtree: true,
})
}
}
function favoritesHandler() {
productsList = document.querySelectorAll('a[class^="favorites_"]');
productsList.forEach(el => {
if (el.querySelectorAll('div>div>div>span')[0] && el.querySelectorAll('div>div>div>span')[0].innerText) {
priceString = el.querySelectorAll('div>div>div>span')[0].innerText.replace(/ /g, '').replace(/,/g, '.').replace(/ /g,'');
console.log(priceString)
price = parseFloat(priceString);
volumeString = el.lastChild.lastChild.innerText;
volumeValue = volumeString.split(' ')[0].replace(/,/, '.');
volumeUnit = volumeString.split(' ')[1];
if (countPricePerUnit(price, volumeUnit, volumeValue) !== false) {
pricePerUnit = countPricePerUnit(price, volumeUnit, volumeValue).outputPrice.toFixed(2);
finalUnit = countPricePerUnit(price, volumeUnit, volumeValue).outputUnit;
//вставка элемента
pricePerUnitWithComma = pricePerUnit.replace(/\./, ','); // замена точки между разрядами на запятую обратно
if (!el.querySelector('.pricePerUnit')) {
insertCountedPriceForFavorites(el.lastChild.lastChild, pricePerUnitWithComma, finalUnit);
}
}
}
});
//обсервер для отслеживания подгрузки товаров/перезагрузки списка
observerFavoritesList = new MutationObserver((mutationsList) => {
//дополнительная проверка, что в мутации добавляется элемент для случая удаления из избранного, когда элементы только удаляются
if (mutationsList[0].addedNodes.length > 0) {
console.log('Favorites changed');
favoritesHandler();
}
});
if (document.querySelectorAll('.favorite-product') && document.querySelectorAll('.favorite-product').length > 0) {
observerFavoritesList.observe(document.querySelector('.ui-content-wrapper').firstChild.lastChild, {
childList: true,
//subtree: true,
})
observerFavoritesList.observe(document.querySelector('.favorites-list'), {
childList: true,
//subtree: true,
})
}
}
function popupHandler() {
el = document.querySelector('div[class^=frames_module]');
if (el.querySelector('meta[itemprop="price"]')) { //проверка для случая открытия напрямую товара не в наличии
// priceString = el.querySelector('meta[itemprop="price"]').content.replace(/ /g, '').replace(/,/g, '.'); // строка убрана, стоимость получается сразу в верном формате
priceString = el.querySelector('meta[itemprop="price"]').content;
price = parseFloat(priceString);
volumeString = el.querySelector('div[class^="product_cards"] div[itemprop="offers"]>p').innerText;
volumeValue = volumeString.split(' ')[0].replace(/,/, '.');
volumeUnit = volumeString.split(' ')[1];
if (countPricePerUnit(price, volumeUnit, volumeValue) !== false) {
pricePerUnit = countPricePerUnit(price, volumeUnit, volumeValue).outputPrice.toFixed(2);
finalUnit = countPricePerUnit(price, volumeUnit, volumeValue).outputUnit;
//вставка элемента
pricePerUnitWithComma = pricePerUnit.replace(/\./, ','); // замена точки между разрядами на запятую обратно
if (!el.querySelector('.pricePerUnit')) {
insertCountedPriceForPopup(el.querySelector('div[class^="product_cards"] div[itemprop="offers"]>p'), pricePerUnitWithComma, finalUnit);
}
}
}
//снова вешаем обсервер, потому что прошлый закрыли дисконнектом
if (typeof observerPopup !== 'undefined') { //проверяем, объявлен ли обсервер для случая загрузки страницы товара, когда запущен продактс обсервер, а попап открыт сразу
observerPopup.observe(document.querySelector('div[class^=frames_module]'), {
childList: true,
subtree: true,
})
}
}
function liveSearchResultsHandler() {
productsList = document.querySelectorAll('.header-search-list-product');
productsList.forEach(el => {
if (el.querySelector('div[class="header-search-list-product__price"]') && el.querySelector('div[class="header-search-list-product__price"]').innerText) {
priceString = el.querySelector('div[class="header-search-list-product__price"]').innerText.replace(/ /g, '').replace(/,/g, '.'); //изменен символ пробела на  
price = parseFloat(priceString);
volumeString = el.querySelector('span[class="header-search-list-product__price-unit"]').innerText.trim(); //добавлен trim
volumeValue = volumeString.split(' ')[0].replace(/,/, '.');
volumeUnit = volumeString.split(' ')[1].replace(/\./g, ''); //добавлена замена для точки
if (countPricePerUnit(price, volumeUnit, volumeValue) !== false) {
pricePerUnit = countPricePerUnit(price, volumeUnit, volumeValue).outputPrice.toFixed(2);
finalUnit = countPricePerUnit(price, volumeUnit, volumeValue).outputUnit;
//вставка элемента
pricePerUnitWithComma = pricePerUnit.replace(/\./, ','); // замена точки между разрядами на запятую обратно
if (!el.querySelector('.pricePerUnit')) {
insertCountedPriceForLiveSearchResults(el.querySelector('span[class="header-search-list-product__price-unit"]'), pricePerUnitWithComma, finalUnit);
}
else {
el.querySelector('.pricePerUnit').innerText = `${pricePerUnitWithComma} ₽ / ${finalUnit}`; //если уже цена есть, перезаписываем ее
}
}
else { //обработка случая, когда прошлый товар для этой позиции был в подходящих единицах, а новый нет, тогда убираем рассчитанную старую цену
if (el.querySelector('.pricePerUnit')) {
el.querySelector('.pricePerUnit').remove();
}
}
}
});
//обсервер для отслеживания изменения выпадающих резульатов поиска
observerLiveSearchResultUpdates = new MutationObserver(() => {
console.log('live search results changed');
observerLiveSearchResultUpdates.disconnect();
liveSearchResultsHandler();
});
if (document.querySelector('div[data-qa="result-list"]')) { //такая проверка, чтобы скрипт не падал при неудаче запуска обсервера
observerLiveSearchResultUpdates.observe(document.querySelector('div[data-qa="result-list"]'), {
childList: true,
subtree: true,
characterData: true, //обязательно, для отслеживания изменния текста в элементах
})
}
}
function init() {
// проверка типа содержимого страницы
if (getPageType() == 'productList') {
productsHandler();
//проверка, есть открытый при загрузке ли попап с товаром
if (document.querySelector('div[itemscope]')) {
popupHandler();
}
}
else if (getPageType() == 'favorites') {
// обсервер для body, чтобы поймать момент загрузки элемента списка товаров
observerFavoritesListInit = new MutationObserver(() => {
if (document.querySelectorAll('a[class^="favorites_"]') && document.querySelectorAll('a[class^="favorites_"]').length > 0) {
observerFavoritesListInit.disconnect();
console.log('favorites-list found');
favoritesHandler();
}
});
observerFavoritesListInit.observe(document.body, {
childList: true,
subtree: true,
})
}
// обсервер для появления попапа
observerPopup = new MutationObserver(() => {
if (document.querySelector('meta[itemprop="price"]')) {
observerPopup.disconnect();
console.log('popup changed');
popupHandler()
}
});
if (document.querySelector('div[class^=frames_module]')) {
observerPopup.observe(document.querySelector('div[class^=frames_module]'), {
childList: true,
subtree: true,
})
}
// обсервер для подсказок из поиска
observerLiveSearchResults = new MutationObserver(() => {
//observerFavoritesListInit.disconnect();
console.log('search results changed');
liveSearchResultsHandler();
});
if (document.querySelector('.header-search-wrapper')) {
observerLiveSearchResults.observe(document.querySelector('.header-search-wrapper'), {
childList: true,
//subtree: true,
})
}
};
init();
})();