Skip to content

Instantly share code, notes, and snippets.

@jricardo27
Last active October 3, 2022 16:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jricardo27/561af2381cb858e09d8d3db476ff05ab to your computer and use it in GitHub Desktop.
Save jricardo27/561af2381cb858e09d8d3db476ff05ab to your computer and use it in GitHub Desktop.
Calculate total price by adding shipping price and tax in Australia
// ==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