Greasy Fork

Greasy Fork is available in English.

AliExpress Improve

Разные мелкие улучшения AliExpress: подсчёт общей цены, возвращение breadcrumb на верх

// ==UserScript==
// @name        AliExpress Improve
// @description Разные мелкие улучшения AliExpress: подсчёт общей цены, возвращение breadcrumb на верх
// @version     1.9.4
// @author      BaNru
// @run-at      document-end
// @include     https://*.aliexpress.com/*
// @match       https://*.aliexpress.com/*
// @grant       GM_xmlhttpRequest
// @namespace http://greasyfork.icu/users/374664
// ==/UserScript==


// Глобавльная переменная URLSearchParams
var url = new URLSearchParams(document.location.search);


/* Страница товара */
if(~document.location.href.indexOf('/item/')){


// Регулярка получения цен из строки
var REGEXP = new RegExp(/[\d\s]+(?:\.|,)\d+/),
	REGEXP2 = new RegExp(/\d+/),
	TOTALPRICE = 0;

// Вытаскиваем цены из строки и преобразуем в числа
function normaliseInt(str){
	return parseFloat(
		( str.match(REGEXP) && str.match(REGEXP)[0] || str.match(REGEXP2) && str.match(REGEXP2)[0] || 0	).replace(/\s/,'').replace(',', '.')
	);
}

// Основная функция подсчёта
function RunTotalPrise() {

	// Основная цена
	var price = document.querySelector('.product-price-value[itemprop="price"]');
	if (price && (price.textContent.match(REGEXP) || price.textContent.match(REGEXP2))) {
		price = normaliseInt(price.textContent);
	} else {
		price = 0;
	}

	// Доставка
	var shippingPrice = document.querySelector('.product-shipping-price .bold');
	if (shippingPrice && (shippingPrice.textContent.match(REGEXP) || shippingPrice.textContent.match(REGEXP2))) {
		shippingPrice = normaliseInt(shippingPrice.textContent);
	} else {
		shippingPrice = 0;
	}

	// Высчитываем
	if (price) {
		var INPUT_ = document.querySelector('.product-number-picker input').value;
		if (shippingPrice) {
			return (price * INPUT_ + shippingPrice).toFixed(2);
		} else {
			return (price * INPUT_).toFixed(2);
		}
	}
	return 0;
}

// Функция вывода цен на страницу
function ReloadTotalPrise() {
	TOTALPRICE = RunTotalPrise();
	document.querySelector('.USER_totalPrice').textContent = 'Общая сумма: ' + TOTALPRICE;
}

// Отрисовываем блок для вывода цены и кнопку "Пересчитать"
document.querySelector('.product-action').insertAdjacentHTML('beforebegin',
	'<span class="USER_totalPrice" style="color:#FF4747;font:bold 1.2em/2em serif;padding-right:20px;">Общая сумма: ' + RunTotalPrise() + '</span><span class="USER_RunTotalPrice" style="background:#FF4747;color:#FFF;cursor:pointer;padding:2px 10px;">Пересчитать</span>');
// Создаём блок для подсчёта за единицу
document.querySelector('.product-sku').insertAdjacentHTML('afterend','<small class="USER_SiglePrice" style="top:-6px;position:relative;"></small>');


// Клик по кнопки "Пересчитать"
document.querySelector('.USER_RunTotalPrice').addEventListener('click', ReloadTotalPrise);
// Инициализация скрипта подсчёта при выборе характеристик товара и количества
// Отслеживание изменения доставки пользователем не предусмотрено
document.querySelectorAll('.product-quantity button,.sku-property-item').forEach(item => {
	item.addEventListener('click', e=>{
		setTimeout(()=>{
			ReloadTotalPrise();
			// Подсчёт за единицу. Пока оставим тут.
			if(e.target.textContent.match(REGEXP2)){
				document.querySelector('.USER_SiglePrice').textContent = parseFloat( TOTALPRICE / e.target.textContent.match(REGEXP2)[0] ).toFixed(2);
			}
		}, 500); // Увеличить цифру 2+ раза, если не будет успевать считать
	});
});


// Возавращение breadcrumb (хлебные крошек, категорий) в верх страницы
var breadcrumb = document.querySelector('.breadcrumb'),
	productMain = document.querySelector('.product-main');
if(breadcrumb && productMain){
	productMain.insertAdjacentHTML('afterbegin', '<style>.breadcrumb_ {text-align: center; margin: -15px 0 15px;}.breadcrumb_ a {padding: 0 5px;color:#666;font-size:0.8em;} .breadcrumb_ a:hover {color:#ff4747}</style><div class="breadcrumb_"></div>');
	var breadcrumb_ = document.querySelector('.breadcrumb_');
	breadcrumb.querySelectorAll('a').forEach( function(element) {
	let el = element.cloneNode(true);
		breadcrumb_.append(el);
	});
}


// Показать оригинальное (на английском) название
// ?isOrigTitle=true
if(!document.querySelector('.product-title-switch')){
	url.set("isOrigTitle", "true");
	// Отрисовываем ссылку переключения
	document.querySelector('.product-title').insertAdjacentHTML('afterend','<div class="product-title-switch"><a href="'+ window.location.pathname + '?' + url.toString() +'"><svg class="svg-icon m product-title-icon" aria-hidden="true"><use xlink:href="#icon-translate"></use></svg>Посмотреть оригинальное название</a></div>');
}


}
/* / Страница товара */


// Дополнительный поиск
var breadcrumb_search = document.querySelector('.product-container .nav-breadcrumb');
if (breadcrumb_search) {
	breadcrumb_search.insertAdjacentHTML('afterend', '<div class="next-input next-small""><input placeholder="Дополнить" autocomplete="off" value="" class="AEsearch"></div>')
	var AEsearch = document.querySelector('.AEsearch');
	AEsearch.addEventListener('keyup', e => {
		if (e.keyCode == 13) {
			let oldS = url.get("SearchText");
			oldS = oldS ? oldS + "+" : "";
			url.delete("SearchText");
			var u = window.location.pathname;
			if (~document.location.href.indexOf('w/wholesale')) {
				oldS = u.replace('/w/wholesale-', '').replace('.html', '') + '+';
				u = '/wholesale';
			}
			url.set("SearchText", oldS + e.target.value);
			window.location.href = u + '?' + url.toString();
		}
	})
}


// Переключение на старый дизайн
var navtop = document.querySelector('#nav-global');
url.delete("switch_new_app");
url.set("switch_new_app", 'n');
navtop.insertAdjacentHTML('afterbegin', `<style>
.ng-item-wrap.oldD a {
	background: #ff4747;
	color:#fff;
	margin: 0px 5px;
	display: inline-block;
	padding: 0 10px;
	font-weight: bold;
}
.ng-item-wrap.oldD a:hover {
	background: #a63c24;
	color:#fff;
}
</style>
<div class="ng-item-wrap oldD"><div class="ng-item"><a href="${window.location.pathname}?${url.toString()}">&#8822; старый дизайна</a></div></div>
`);


// Скрытие надоедливой Евы
document.querySelector('body').insertAdjacentHTML('afterend', `<style>
#J_xiaomi_dialog {visibility: hidden;bottom: 20px!important;}
#J_xiaomi_dialog .J_weak, #J_xiaomi_dialog .J_weak:hover {
	width: auto;
	height: auto;
	background: transparent;
	box-shadow: none;
}
#J_xiaomi_dialog .J_weak .alime-avatar {
	width: 32px !important;
	height: auto;
	opacity: 0.5;
	transition: all;
	position:relative;
	top:0;
}
#J_xiaomi_dialog .J_weak .alime-avatar:hover {
	opacity: 1;
}
#J_xiaomi_dialog .J_weak .alime-text {
	display:none;
}
</style>`);
window.onload = function() {
	var eva = document.querySelector('#J_xiaomi_dialog');
	if (eva) {
		var eva2 = eva.querySelector('.close-icon');
		if (eva2) {
			eva2.click();
			setTimeout(() => { eva.style.visibility = "visible"; }, 2000);
		}
	}
}


/* Отключение aotoplay у карусели при добавление в корзину */
/* Спасибо за помощь Джентльменам */
/*
	НЕ РАБОТАЕТ в Greasemonkey! Он не хочет читать Object.key у элемента.
	Проверена работа в Tampermonkey
*/
function stopSlider() {
	var tI = 0;
	var timerCard = setInterval(() => {
		tI > 20 ? clearInterval(timerCard) : tI++; // Останавливаем таймер, если элемент не найден в течение 10 секунд
		// console.log('Ищем слайдер');
		if (document.querySelector('.next-slick')) {
			setTimeout(()=>{
				document.querySelector('.next-slick')[Object.keys(document.querySelector('.next-slick'))[0]].alternate.memoizedProps.children.props.autoplay = false;
			}, 500); // Хак на дни распродаж. Если не поможет, то увеличить в x2-x4 раза и на медленных компьютерах
			clearInterval(timerCard);
		}
	}, 500);
}
document.querySelector('.product-action').addEventListener('click', (e) => {
	if (e.target.classList.contains('addcart')) {
		if (document.querySelector('.product-action .addcart-wrap:not([aria-expanded]) button')) {
			stopSlider();
		}
	}
	if (e.target.closest('.add-wishlist-wrap')) {
		stopSlider();
	}
});


// Номера треков на странице заказов
// Генерация цветов (на входе 0-255 - яркость цвета)
function randomColor(brightness) {
	function randomChannel(brightness) {
		var r = 255 - brightness;
		var n = 0 | ((Math.random() * r) + brightness);
		var s = n.toString(16);
		return (s.length == 1) ? '0' + s : s;
	}
	return '#' + randomChannel(brightness) + randomChannel(brightness) + randomChannel(brightness);
}
// Удаление дубликатов в массиве (треков)
function removeDublicate(tracknumber) {
	return tracknumber.filter((v, i) => tracknumber.indexOf(v) === i);
}
// Получение тректов
function gettrack(el, ordernumber, refresh) {
	return new Promise((resolve, reject) => {
		var inLS = localStorage.getItem(ordernumber);
		if (inLS && !refresh) {
			console.log('local', JSON.parse(inLS));
			return resolve([el, JSON.parse(inLS)]);
		}
		GM_xmlhttpRequest({
			method: "GET",
			url: 'https://track.aliexpress.com/logisticsdetail.htm?tradeId=' + ordernumber,
			withCredentials: true,
			onload: response => {
				var tracknumber = [];
				if (/"logisticsNo":"(.*?)"/.exec(response.responseText)) {
					tracknumber.push(/"logisticsNo":"(.*?)"/.exec(response.responseText)[1]);
				}
				if (/"interMailNo":"(.*?)"/.exec(response.responseText)) {
					tracknumber.push(/"interMailNo":"(.*?)"/.exec(response.responseText)[1]);
				}
				if (/"lgOrderCode":"(.*?)"/.exec(response.responseText)) {
					tracknumber.push(/"lgOrderCode":"(.*?)"/.exec(response.responseText)[1]);
				}
				if (/"realMailNo":"(.*?)"/.exec(response.responseText)) {
					tracknumber.push(/"realMailNo":"(.*?)"/.exec(response.responseText)[1]);
				}
				if (/"mailNo":"(.*?)"/.exec(response.responseText)) {
					tracknumber.push(/"mailNo":"(.*?)"/.exec(response.responseText)[1]);
				}
				if (tracknumber.length > 0) {
					tracknumber = removeDublicate(tracknumber);
					localStorage.setItem(ordernumber, JSON.stringify(tracknumber));
					return resolve([el, tracknumber]);
				} else {
					return resolve([el, []]);
				}
			},
			onerror: error => {
				console.log(error);
				return reject('Ошибка получения страницы отслеживания');
			}
		});

	});
}
// Вставка треков на страницу (с поиском и подсветкой дублей/консолидированных)
function insertTrack(el, track) {
	var thisBTN = el.closest('.order-item-wraper').querySelector('.order-action button');
	track.forEach(c => {
		var color = 'transparent';
		document.querySelectorAll('.inproveTrack').forEach(ie => {
			if (ie.textContent == c) {
				if(color == 'transparent'){color = randomColor(160);}
				ie.style.backgroundColor = color;
			}
		});
		thisBTN.insertAdjacentHTML('afterend', `
<a class="inproveTrack" href="https://gdeposylka.ru/${c}" target="_blank" style="background:${color};">${c}</a>
`);
	});
	var trackrefresh = document.createElement('span');
	trackrefresh.className = 'trackrefresh';
	trackrefresh.textContent = '↺';
	trackrefresh.addEventListener('click', () => {
		el.closest('.order-item-wraper').querySelectorAll('.inproveTrack, .trackrefresh').forEach(r => {
			r.remove();
		});
		gettrack(el, el.textContent.trim(), true).then(e => {
			insertTrack(e[0], e[1]);
		});
	});
	thisBTN.parentNode.insertBefore(trackrefresh, thisBTN.nextSibling);
}
// Запуск скрипта отображения треков
if (~document.location.href.indexOf('orderList.htm')) {
	document.querySelector('body').insertAdjacentHTML('afterend', `<style>
		.inproveTrack {
			font-size: .8em;
			display: inline-block;
			position: relative;top: -6px;
			padding: 1px 5px 2px;
		}
		.trackrefresh {
			float: right;
			font-size: 1.3em;
			cursor: pointer;
			margin-top: -7px;
		}
		.trackrefresh:hover {
			color:#f60;
		}
		</style>`);
	var ordernumbers = [];
	document.querySelectorAll('.order-info .first-row .info-body').forEach(el => {
		//localStorage.clear();
		ordernumbers.push(
			gettrack(el, el.textContent.trim())
			.then(r => {
				return r;
			}).catch(e => {
				return e;
			})
		);
	});
	Promise.all(ordernumbers).then(t => {
		t.forEach(e => {
			if (e[0] && e[1]) {
				insertTrack(e[0], e[1]);
			}
		});
	}, e => {
		console.log(e);
	});
}


/*
 * CHANGELOG
 * 1.0   - Первая версия
 * 1.0.1 - Добавил поддержку разных валют. Были только доллары.
 * 1.1   - Переименовал в AliExpress Improve
 *       - Добавил навигацию по категориям (скопировал с низу в верх)
 * 1.2   - Исправлена ошибка получения цен: теперь извлекаются цены с отбивкой тысячных пробелом "$1 000" или "1 000 рублей"
 * 1.2.1 - Исправлена ошибка получения цен: теперь извлекаются целые цены "$2" или "100 руб."
 * 1.2.2 - Исправлена ошибка получения объекта: TypeError: price/shippingPrice is null
 * 1.3.0 - Добавлен подсчёт цены за единицу товара, если есть возможность выбирать количество товара
 * 1.4.0 - Добавлена ссылка переключения на оригинальный заголовок
 * 1.4.1 - Обновлено получение цены доставки. Теперь снова суммируется.
 * 1.5.0 - Попытка восстановить дополнительный поиск
 * 1.6.0 - Переключение на старый дизайн (переключает не на всех страницах)
 * 1.7.0 - Скрытие надоедливой Евы
 * 1.8.0 - Отключение aotoplay у карусели при добавление в корзину
 * 1.8.1 - Отключение aotoplay у карусели при добавление в избранное
 * 1.9.0 - Добавлены номера треков на странице заказов с ссылкой на гдепосылка
 * 1.9.1 - Удалён дубль кода
 * 1.9.2 - Заменена функция генерации цветов для подсветки консолидированных заказов
 * 1.9.3 - Добавлен хак на остановку карусели во время распродаж
 * 1.9.4 - Исправлены цвета для консолидированных посылок. Теперь все одинаковые треки одного цвета.
 */