NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name amazon_calculate_subscription_total
// @namespace https://github.com/LeoCrayon/Monkeyscripts
// @version 0.5
// @description Amazon calculate subscription total.
// @author LeoCrayon
// @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt
// @match https://www.amazon.com/auto-deliveries/*
// @match https://www.amazon.com/gp/subscribe-and-save/*
// @grant none
// ==/UserScript==
/*jshint esversion: 8 */
(function() {
'use strict';
// =========================================
// Ajax utils.
const sendRequest = (url) => {
var xhr = new XMLHttpRequest();
return new Promise(function(resolve, reject) {
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status >= 300) {
reject("Error, status code = " + xhr.status)
} else {
resolve(xhr.responseText);
}
}
}
xhr.open('get', url, true)
xhr.send();
});
};
// ==========================================
// Price utils.
const parsePrice = (priceString) => {
const startPos = priceString.search(/\d/);
const leftBracketsPos = priceString.indexOf("(");
const endPos = leftBracketsPos < 0 ? priceString.length : leftBracketsPos;
return {
price: parseFloat(priceString.substring(startPos, endPos)),
currency: startPos - 1 >= 0 ? priceString.charAt(startPos - 1) : ""
};
};
const calculatePriceTotal = (priceObjs) => {
let totalPrice = 0;
let currency = "";
priceObjs.forEach((priceObj) => {
if (priceObj && priceObj.price) {
if (!currency) {
currency = priceObj.currency;
}
totalPrice += priceObj.price * (priceObj.quantity || 1);
}
});
return currency + totalPrice.toFixed(2);
};
const parseQuantity = (quantityString) => {
const startPos = quantityString.search(/\d/);
return parseInt(quantityString.substring(startPos));
}
// ==========================================
const getPriceFromProductPage = (productPageDom) => {
const productTitleEl = productPageDom.querySelector("#productTitle");
const snsContainerEl = productPageDom.querySelector("#snsAccordionRowMiddle");
let priceEl;
if (snsContainerEl) {
const pillContainerEl = snsContainerEl.querySelector(".discountPillWrapper");
const pillLightedUpEl = pillContainerEl.querySelector(".pillLightUp");
const tieredPriceEnabled = pillLightedUpEl.classList.contains("discountPillRight");
priceEl = tieredPriceEnabled ?
snsContainerEl.querySelector("#sns-tiered-price") : snsContainerEl.querySelector("#sns-base-price");
} else {
priceEl = productPageDom.querySelector("#priceblock_ourprice");
}
return {
productPrice: priceEl ? parsePrice(priceEl.innerText) : {price: 0},
notSns: !snsContainerEl,
unavailable: !priceEl,
productName: productTitleEl.innerText.trim()
};
}
const getProductPrice = async (productEl) => {
const subEditUrlEl = productEl.querySelector(".a-declarative");
if (!subEditUrlEl) {
return {productPrice: {price: 0}};
}
const subEditUrlJson = JSON.parse(subEditUrlEl.dataset.aModal);
const productSubEditUrl = subEditUrlJson.url;
const quantityEl = productEl.querySelector(".subscription-quantity-message");
const quantity = quantityEl ? parseQuantity(quantityEl.innerText) : 1;
const productSubEditHtml = await sendRequest(productSubEditUrl);
const productSubEditDom = new DOMParser().parseFromString(productSubEditHtml, "text/html");
const productPageUrlEl = productSubEditDom.querySelector(".a-link-normal"); // First link.
const productPageUrl = productPageUrlEl.getAttribute("href");
const productPageHtml = await sendRequest(productPageUrl);
const productPageDom = new DOMParser().parseFromString(productPageHtml, "text/html");
const priceObj = getPriceFromProductPage(productPageDom);
priceObj.productLink = productPageUrl;
priceObj.productPrice.quantity = quantity;
return priceObj;
};
const getIndirectPrice = async (deliveryCard) => {
const productEls = deliveryCard.querySelectorAll(".subscription-card");
const priceObjs = [];
const notSnsProducts = [];
const unavailableProducts = [];
await Promise.all(Array.from(productEls).map(async (productEl) => {
const priceObj = await getProductPrice(productEl);
priceObjs.push(priceObj.productPrice);
if (priceObj.notSns) {
const notSnsProduct = {
name: priceObj.productName,
link: priceObj.productLink
};
notSnsProducts.push(notSnsProduct);
}
if (priceObj.unavailable) {
const unavailableProduct = {
name: priceObj.productName,
link: priceObj.productLink
};
unavailableProducts.push(unavailableProduct);
}
}));
return {
price: calculatePriceTotal(priceObjs),
notSnsProducts,
unavailableProducts};
};
const getDirectPrice = (deliveryCard) => {
const priceEls = deliveryCard.querySelectorAll(".subscription-price");
const priceObjs = [];
priceEls.forEach((priceEl) => {
priceObjs.push(parsePrice(priceEl.innerText));
});
return calculatePriceTotal(priceObjs);
};
const process = () => {
const deliveryCardEls = document.querySelectorAll(".delivery-card");
console.log(deliveryCardEls.length);
deliveryCardEls.forEach(async (deliveryCardEl, index) => {
const informationContainerEl = deliveryCardEl.querySelector(".delivery-information-container");
const totalPriceContainerEl = document.createElement("DIV");
informationContainerEl.appendChild(totalPriceContainerEl);
totalPriceContainerEl.classList.add("deliveryTile");
Object.assign(totalPriceContainerEl.style, {
marginTop: "8px",
float: "none",
});
const totalPriceLabelEl = document.createElement("SPAN");
totalPriceContainerEl.appendChild(totalPriceLabelEl);
totalPriceLabelEl.classList.add("a-size-base-plus", "subscription-price");
const totalPriceEl = document.createElement("SPAN");
totalPriceContainerEl.appendChild(totalPriceEl);
totalPriceEl.classList.add("a-size-base-plus", "a-color-price", "subscription-price", "a-text-bold");
const spinnerEl = document.createElement("SPAN");
totalPriceContainerEl.appendChild(spinnerEl);
spinnerEl.classList.add("deliveryTileContent", "spinner", "cartSpinner");
Object.assign(spinnerEl.style,{
backgroundSize: "20px",
width: "20px",
height:"20px",
display: "inline-block",
verticalAlign: "middle"
});
if (index === 0) {
totalPriceLabelEl.innerText = "Total: ";
totalPriceEl.innerText = getDirectPrice(deliveryCardEl);
} else {
totalPriceLabelEl.innerText = "Total est: "
const priceObj = await getIndirectPrice(deliveryCardEl);
totalPriceEl.innerText = priceObj.price;
const createProductList = (products, label) => {
if (products.length === 0) {
return;
}
const productsContainerEl = document.createElement("DIV");
informationContainerEl.appendChild(productsContainerEl);
productsContainerEl.style.marginTop = "8px";
const productsLabelEl = document.createElement("SPAN");
productsContainerEl.appendChild(productsLabelEl);
productsLabelEl.classList.add("a-size-small", "a-text-bold");
productsLabelEl.innerText = label;
products.forEach((product) => {
const productEl = document.createElement("DIV");
productsContainerEl.appendChild(productEl);
productEl.classList.add("a-size-small");
productEl.style.marginTop = "6px";
const productLinkEl = document.createElement("A");
productEl.appendChild(productLinkEl);
productLinkEl.classList.add("a-link-normal", "product-title");
productLinkEl.innerText = product.name;
productLinkEl.setAttribute("href", product.link);
});
};
createProductList(priceObj.notSnsProducts, "Potential not Sns: ");
createProductList(priceObj.unavailableProducts, "Potential unavailable: ");
}
spinnerEl.remove();
});
};
const loadCheck = setInterval(() => {
const spinnerEl = document.querySelector(".spinner");
if (!spinnerEl) {
process();
clearInterval(loadCheck);
}
}, 300);
})();