Last active
October 3, 2022 16:55
-
-
Save jricardo27/561af2381cb858e09d8d3db476ff05ab to your computer and use it in GitHub Desktop.
Calculate total price by adding shipping price and tax in Australia
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Aliexpress Full Price | |
// @author Ricardo Perez | |
// @namespace jricardo27/AliexpressFullPrice | |
// @version 1.2 | |
// @license GPL-3.0 | |
// @description Show full price (including shipping and Australian Taxes) on item list. | |
// @include *://*.aliexpress.* | |
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
var CURRENCY_SELECTOR = "span.currency"; // Currency used in the website. | |
var LIST_SELECTOR = ".JIIxO"; // Container with the list of items when searching. | |
var LIST_ITEM_SELECTOR = "._3t7zg"; // Container of each item in the list. | |
var ITEMS_PER_PAGE = 4 * 12; // When the number of items has been loaded it will trigger a sorting. | |
var ITEM_LIST_SHIPPING_SELECTOR = "span._2jcMA"; // Container with the shipping price. | |
var ITEM_LIST_PRICE_SELECTOR = "div.mGXnE"; // Container with the item price. | |
var PAGINATION_SELECTOR = ".list-pagination"; | |
var CURRENT_PAGE_SELECTOR = ".next-current"; | |
var PRODUCT_MAIN_CONTAINER_SELECTOR = ".product-main"; | |
var QUANTITY_SELECTOR = ".product-number-picker input"; | |
var PRODUCT_SHIPPING_OBSERVER_SELECTOR = ".dynamic-shipping-line"; | |
var PRODUCT_SHIPPING_SELECTOR = ".dynamic-shipping-line > span > span"; | |
var PRODUCT_PRICE_SELECTOR = ".product-price-value"; | |
var RIGHT_HEADER_SELECTOR = ".header-right-content"; | |
var MESSAGE_ELEMENT_ID = "aliexpressfullprice-message"; | |
var DATA_LOWER_PRICE = 'data-lower-price'; | |
var DATA_UPPER_PRICE = 'data-upper-price'; | |
var obsConfig = { | |
childList: true, | |
characterData: true, | |
attributes: true, | |
subtree: true | |
}; | |
var itemsAlreadySorted = false; | |
var newItemsLoaded = false; | |
function log(msg) { | |
console.info(`Aliexpress Fullprice: ${msg}`); | |
} | |
function showMessage(message) { | |
var html = "<span id='" + MESSAGE_ELEMENT_ID + "' " + | |
"style='float: left; font-size: 14px; color: blueviolet'>" + | |
message + | |
"</span>"; | |
var container = $(RIGHT_HEADER_SELECTOR); | |
container.find('#' + MESSAGE_ELEMENT_ID).remove(); | |
container.append(html); | |
} | |
function clearMessage() { | |
$(RIGHT_HEADER_SELECTOR).find('#' + MESSAGE_ELEMENT_ID).remove(); | |
} | |
function getCurrency() { | |
var currency = ""; | |
var element = $(CURRENCY_SELECTOR); | |
if (element && element.text()) { | |
currency = element.text(); | |
} | |
return currency; | |
} | |
function getPrices(item, price_selector) { | |
var prices = []; | |
var textPrice = item.find(price_selector).text(); | |
if (!textPrice) { | |
log("Couldn't extract price: " + item.find("span.price-current").html()); | |
} | |
var matches = textPrice.match(/\$(\d+\.?\d*)(?: \- )?(\d+\.?\d*)?/); | |
if (matches && matches[1]) { | |
prices.push(Number(matches[1])); | |
if (matches[2]) { | |
prices.push(Number(matches[2])); | |
} | |
} else { | |
log("Couldn't find prices for item. [" + textPrice + "]"); | |
} | |
return prices; | |
} | |
function getShipping(item, shipping_selector) { | |
var shippingCost = 0; | |
var shippingItem = item.find(shipping_selector); | |
if (shippingItem) { | |
var textValue = shippingItem.text(); | |
var matches = textValue.match(/\$(\d+\.?\d*)/); | |
if (matches) { | |
shippingCost = Number(matches[1]); | |
} else { | |
// Free shipping. | |
log("Couldn't find shipping price for item. [" + shippingItem.html() + "]"); | |
} | |
} | |
return shippingCost; | |
} | |
function addNewPrice(item, currency, prices, quantity, shipping, tax, priceSelector, shippingSelector) { | |
var priceTag = $(item.find(priceSelector)[0]); | |
var shippingTag = $(item.find(shippingSelector)[0]); | |
var newPrices = []; | |
// Make original price and shipping price small and grey. | |
$.each([priceTag, shippingTag], function (index, tag) { | |
tag.css("font-size", "8px"); | |
tag.css("color", "lightgrey"); | |
}); | |
$(PRODUCT_SHIPPING_OBSERVER_SELECTOR).each(function () { | |
var observer = new customObserver(this, obsConfig, function (obs, mutations) { | |
log("Shipping price changed..."); | |
obs.disconnect(); | |
execute(getCurrency(), false); | |
obs.connect(); | |
}); | |
observer.connect(); | |
}); | |
var newContent = "<span class='total-price-updated'>"; | |
newContent += "<span style='font-size: 8px; color: blueviolet'>" + | |
"AliexpressFullPrice plugin activated" + | |
"</span>" + | |
"<br>"; | |
if (tax > 1) { | |
newContent += "<span style='font-size: 10px'>" + | |
"(Shipping + AU Tax included)" + | |
"</span>" + | |
"<br>"; | |
} else { | |
newContent += "<span style='font-size: 10px'>" + | |
"(Shipping included)" + | |
"</span>" + | |
"<br>"; | |
} | |
$.each(prices, function (index, price) { | |
var singlePrice = ""; | |
var newPrice = ((price * quantity) + shipping) * tax; | |
newPrices.push(newPrice); | |
if (quantity > 1) { | |
singlePrice = newPrice / quantity; | |
singlePrice = "<span style='font-size: 10px'>" + | |
" (1 pc: " + singlePrice.toFixed(2) + ")" + | |
"</span>"; | |
} | |
newContent += "<span class='price-current'>" + | |
currency + " $" + newPrice.toFixed(2) + | |
singlePrice + | |
"</span>" + | |
"<br>"; | |
}); | |
newContent += "</span>"; | |
priceTag.before(newContent); | |
return newPrices; | |
} | |
function taxForCurrency(currency) { | |
var tax = 1; // Default value means no tax applied. | |
if (currency === "AU" || currency === "A" || currency === "AUD") { | |
tax = 1.1; | |
} | |
return tax; | |
} | |
function processItems(currency, refresh = false) { | |
var tax = taxForCurrency(currency); | |
var listItems = $(LIST_ITEM_SELECTOR); | |
if (listItems.length == 0) { | |
log("Couldn't find list container. Unabled to proceed."); | |
showMessage("Document structure changed. Aliexpress Fullprice not longer works."); | |
} | |
listItems.each(function () { | |
var item = $(this); | |
var updatedPriceTag = item.find(".total-price-updated"); | |
if (updatedPriceTag.length) { | |
if (refresh) { | |
updatedPriceTag.remove(); | |
} else { | |
// Skip if already updated. | |
return; | |
} | |
} | |
var quantity = 1; | |
var prices = getPrices(item, ITEM_LIST_PRICE_SELECTOR); | |
var shipping = getShipping(item, ITEM_LIST_SHIPPING_SELECTOR); | |
var newPrices = addNewPrice( | |
item, currency, prices, quantity, shipping, tax, | |
ITEM_LIST_PRICE_SELECTOR, ITEM_LIST_SHIPPING_SELECTOR | |
); | |
// Add prices as attributes. | |
var lowerPrice = newPrices[0]; | |
var upperPrice = lowerPrice; | |
if (newPrices.length > 1) { | |
upperPrice = newPrices[1]; | |
} | |
item.attr(DATA_LOWER_PRICE, lowerPrice); | |
item.attr(DATA_UPPER_PRICE, upperPrice); | |
}); | |
} | |
function sortItems(container, items, attrName) { | |
var plainItems = items.toArray(); | |
var cloned = []; | |
plainItems.forEach(function (element) { | |
cloned.push($(element).clone()); | |
}); | |
cloned.sort(function (a, b) { | |
var aVal = Number(a.attr(attrName)), | |
bVal = Number(b.attr(attrName)); | |
return aVal - bVal; | |
}); | |
cloned.forEach(function (element, index) { | |
var oldElement = $(plainItems[index]); | |
oldElement.empty(); | |
oldElement.append(element.contents()); | |
}); | |
log("Items sorted"); | |
} | |
/** | |
Functions for updating a single product page | |
**/ | |
function getQuantity() { | |
return Number($(QUANTITY_SELECTOR)[0].value) | |
} | |
function updateSingleProductPrice(currency) { | |
var productElement = $(".product-info"); | |
var tax = taxForCurrency(currency); | |
var quantity = getQuantity(); | |
var shipping = getShipping(productElement, PRODUCT_SHIPPING_SELECTOR); | |
var prices = getPrices(productElement, PRODUCT_PRICE_SELECTOR); | |
var updatedPriceTag = productElement.find(".total-price-updated"); | |
if (updatedPriceTag.length) { | |
updatedPriceTag.remove(); | |
} | |
addNewPrice( | |
productElement, currency, prices, quantity, shipping, tax, | |
PRODUCT_PRICE_SELECTOR, PRODUCT_SHIPPING_SELECTOR | |
); | |
} | |
function execute(currency, refresh = false) { | |
if ($(".product-main").length) { | |
log("Updating price on a single item"); | |
updateSingleProductPrice(currency); | |
} else { | |
log("Updating price on multiple items"); | |
processItems(currency, refresh); | |
if (!itemsAlreadySorted) { | |
// Sort items using full price. | |
var container = $(LIST_SELECTOR); | |
var items = container.find(LIST_ITEM_SELECTOR); | |
if (items.length === ITEMS_PER_PAGE) { | |
itemsAlreadySorted = true; | |
sortItems(container, items, DATA_UPPER_PRICE); | |
var message = "Items sorted."; | |
if (newItemsLoaded) { | |
message = "Page " + initialPage + " was loaded.<br>" + | |
"Items sorted."; | |
} | |
showMessage(message); | |
newItemsLoaded = false; | |
} else { | |
showMessage('Keep scrolling to load all products and sort them'); | |
} | |
} | |
} | |
} | |
function customObserver(target, config, callback) { | |
this.target = target || document; | |
this.config = config || {childList: true, subtree: true}; | |
var that = this; | |
this.ob = new MutationObserver(function (mut, obsSelf) { | |
callback(that, mut, obsSelf); | |
}); | |
} | |
customObserver.prototype = { | |
connect: function () { | |
this.ob.observe(this.target, this.config); | |
}, | |
disconnect: function () { | |
this.ob.disconnect(); | |
} | |
}; | |
function totalPriceRunner() { | |
// Place observers on different HTML elements that way there's no need to | |
// manually monitor for changes done by Aliexpress. | |
var currencyObserver = new MutationObserver(function (mutationRecords, self) { | |
if ($(CURRENCY_SELECTOR).length) { | |
execute(getCurrency(), true); | |
// Stop observing. | |
self.disconnect(); | |
return; | |
} | |
}); | |
var initialPage = ""; | |
var productObserver = new MutationObserver(function (mutationRecords) { | |
execute(getCurrency(), false); | |
}); | |
$(".nav-global").each(function () { | |
currencyObserver.observe(this, obsConfig); | |
}); | |
if ($(PRODUCT_MAIN_CONTAINER_SELECTOR).length) { | |
execute(getCurrency(), false); | |
$(QUANTITY_SELECTOR).each(function () { | |
productObserver.observe(this, obsConfig); | |
}); | |
$(PRODUCT_PRICE_SELECTOR).each(function () { | |
productObserver.observe(this, obsConfig); | |
}); | |
} else { | |
$(LIST_SELECTOR).each(function () { | |
var observer = new customObserver(this, obsConfig, function (obs, mutations) { | |
obs.disconnect(); | |
if (initialPage === "") { | |
initialPage = Number($(CURRENT_PAGE_SELECTOR).text()); | |
} | |
execute(getCurrency(), false); | |
obs.connect(); | |
}); | |
observer.connect(); | |
}); | |
$(PAGINATION_SELECTOR).each(function () { | |
var observer = new customObserver(this, obsConfig, function (obs, mutations) { | |
var currentPage = Number($(CURRENT_PAGE_SELECTOR).text()); | |
if (initialPage === currentPage) { | |
return; | |
} else { | |
initialPage = currentPage; | |
} | |
obs.disconnect(); | |
clearMessage(); | |
itemsAlreadySorted = false; | |
newItemsLoaded = true; | |
obs.connect(); | |
}); | |
observer.connect(); | |
}); | |
} | |
} | |
function removePopups() { | |
var popupsRemoved = 0; | |
var popupSelectors = [ | |
".drogue-poplayer-modal", | |
"._3KrBP", | |
".coupon-poplayer-modal", | |
]; | |
$.each(popupSelectors, function (index, selector) { | |
$(selector).each(function () { | |
popupRemoved += 1; | |
$(this).remove(); | |
}); | |
}); | |
log(`removed ${popupsRemoved} popups`); | |
} | |
/** | |
Userscript will run from here. | |
**/ | |
document.onreadystatechange = function() { | |
removePopups(); | |
totalPriceRunner(); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment