Add the total eBay auction price including postage in the auction listing
// ==UserScript== // @name ebaytotalprice-userscript // @namespace https://github.com/subz390 // @version 2.3.3.230215200537 // @description Add the total eBay auction price including postage in the auction listing // @author SubZ390 // @license MIT // @run-at document-idle // @grant none // @noframes // @include /^https?://(ar|b[ory]|c[lor]|do|ec|gt|hn|il|kz|mx|ni|p[aerty]|ru|sv|uy|ve|www)\.ebay\.com/ // @include /^https?://www\.ebay\.com\.au/ // @include /^https?://www\.ebay\.co\.uk/ // @include /^https?://www\.ebay\.(at|ca|de|es|fr|ie|it|nl|ph|pl)/ // @include /^https?://www\.be(nl|fr)\.ebay\.be/ // // // ==/UserScript== function waitForMini({tryFor = 3, every = 100, test = () => false, success = () => null, timeout = () => null} = {}) { function leadingEdge() { const testR###lt = test(); if (testR###lt) { success(testR###lt); return true } return false } if (leadingEdge() === false) { const intervalReference = setInterval(() => { const testR###lt = test(); if (testR###lt) { clearInterval(intervalReference); clearTimeout(setTimeoutReference); success(testR###lt); } }, every); const setTimeoutReference = setTimeout(() => { clearInterval(intervalReference); timeout(); }, tryFor * 1000); } } function realTypeOf(object, lowerCase = true) { if (typeof object !== 'object') return typeof object if (object === null) return 'null' if (Array.isArray(object)) return 'array' const internalClass = Object.prototype.toString.call(object).slice(8, -1); return lowerCase === true ? internalClass.toLowerCase() : internalClass } function getNode(node = '', debug = undefined, scope = document) { try { scope = scope === null ? document : scope; const nodeType = realTypeOf(node); if (nodeType == 'string') { if (node == '') {return null} let scopeType = realTypeOf(scope); if (scopeType == 'text') { return null } if (scopeType == 'string') { const tempScope = document.querySelector(scope); if (tempScope == null) { return null } scope = tempScope; } scopeType = realTypeOf(scope); if (scopeType.search(/array|nodelist|svgsvgelement|html|document/i) !== -1) { nodeType; const element = scope.querySelector(node); return element } else { return null } } else if (nodeType.search(/array|nodelist|svgsvgelement|html/i) !== -1) { return node } else if (nodeType.search(/null/) !== -1) { return null } else { return null } } catch (error) { console.error(error); } } function appendStyle({style: styleString, className = undefined, whereAdjacent = 'afterend', whereTarget = 'body', tryFor = 5, failMessage = undefined}) { return new Promise((resolve, reject) => { const styleElement = document.createElement('style'); styleElement.appendChild(document.createTextNode(styleString)); if (className) {styleElement.className = className;} function appendTarget(targetNode, styleElement) { if (whereAdjacent !== undefined) { return targetNode.insertAdjacentElement(whereAdjacent, styleElement) } else { return targetNode.appendChild(styleElement) } } waitForMini({ tryFor: tryFor, every: 100, test: () => getNode(whereTarget), success: (targetNode) => {resolve(appendTarget(targetNode, styleElement));}, timeout: () => reject(Error(failMessage || `appendStyle timed out whilst waiting for targetNode: ${whereTarget}`)) }); }) } function findMatch(string, regex, index = 1) { if (string === null) return null const m = string.match(regex); return (m) ? (index=='all' ? m : (m[index] ? m[index] : m[0])) : null } function sprintf2({template = '', regex = /{([^{}]+)}/g, values} = {}) { if (template === '') { console.warn('template is an empty string'); return null } function templateReplace(replaceTemplate, replaceValues) { return replaceTemplate.replace(regex, (match, name) => { if (replaceValues[name]) { if (typeof replaceValues[name] === 'function') { return replaceValues[name]().toString() } return replaceValues[name] || match } if (replaceValues[name] === 0) { return replaceValues[name].toString() } if (typeof replaceValues[name] === 'string' && replaceValues[name].length == 0) { return '' } return match }) } if (Array.isArray(values)) { values.forEach((object) => {template = templateReplace(template, object);}); return template } else { return templateReplace(template, values) } } function qs({selector = null, scope = document, array = false, all = false, contains = null, unittest = false, debugTag = ''} = {}) { const language = { en: { selectorUndefined: `${debugTag}selector is undefined`, scopeNotUseable: `${debugTag}scope is not useable`, } }; if (unittest === 'language') {return language} try { if (selector === null) { console.error(language.en.selectorUndefined); return null } if (scope !== document) { scope = getNode(scope); if (scope === null) { return null } } if (unittest === 'scope') {return scope} if (unittest === 'options') { return { selector: selector, scope: scope, array: array, all: all, contains: contains, unittest: unittest } } if (all === true) { const staticNodeList = scope.querySelectorAll(selector); if (staticNodeList.length === 0) {return null} if (array === true) { if (contains !== null) { const tempArray = []; staticNodeList.forEach((element) => { if (element.textContent.search(contains) !== -1) { tempArray.push(element); } }); if (tempArray.length === 0) {return null} else {return tempArray} } return Array.from(staticNodeList) } else { if (contains !== null) { for (let index = 0; index < staticNodeList.length; index++) { if (staticNodeList[index].textContent.search(contains) !== -1) { return staticNodeList } } return null } return staticNodeList } } else { const qsHTMLElement = scope.querySelector(selector); if (qsHTMLElement === null) {return null} if (typeof contains === 'string' || contains instanceof RegExp) { if (qsHTMLElement.textContent.search(contains) === -1) {return null} } if (array === true) {return [qsHTMLElement]} else {return qsHTMLElement} } } catch (error) { console.error(error); } } const globals = { priceMatchRegExp: /((\d+[,\.])+\d+)/, currencySymbolsRegExp: /(\$|EUR|PHP|zł|£)/, itemPriceElementTemplate: '<span class="total-price">{currencySymbol}{totalPrice}</span>', itemPriceElementTemplateSelector: 'span.total-price', itemPriceElementInnerTextTemplate: '{currencySymbol}{totalPrice}', }; function processMethod(options) { try { function getMethod() { for (const [type, method] of Object.entries(options)) { for (let index = 0; index < method.identifierSelector.length; index++) { const selector = method.identifierSelector[index]; const identifierNode = getNode(selector); if (identifierNode !== null) {return method} } } return null } const method = getMethod(options); if (method !== null) {method.process();} } catch (error) {console.error(error);} } function getValue(element) { try { let value = findMatch(element.textContent.trim(), globals.priceMatchRegExp); value = value.replace(/[,\.]/g, ''); value = parseFloat(value); return value } catch (error) { console.error(error); return null } } function findParent({child, contains = null}) { let parentNodeElement = child; for (let i = 1; parentNodeElement.isEqualNode(document) === false; i++) { if (parentNodeElement.textContent.search(contains) !== -1) { return parentNodeElement } parentNodeElement = parentNodeElement.parentNode; } return null } function processItemListing({listItemsSelector, itemPriceElementSelector, convertPriceElementSelector, itemPriceElementTemplate = null, itemShippingElementSelector, convertShippingElementSelector, itemShippingElementTemplate = null}) { const content = qs({selector: listItemsSelector}); if (content) { const itemPriceElement = qs({selector: convertPriceElementSelector, scope: content, contains: /\d/}) || qs({selector: itemPriceElementSelector, scope: content, contains: /\d/}); let itemShippingElement = qs({selector: convertShippingElementSelector, scope: content, contains: /\d/}) || qs({selector: itemShippingElementSelector, scope: content, contains: /\d/}); if (itemShippingElement === null) { const postageSpan = qs({selector: 'span', scope: content, contains: 'Postage:', all: true, array: true}); itemShippingElement = findParent({child: postageSpan[0], contains: /\d/}); } if (itemPriceElement && itemShippingElement) { const priceCurrencySymbol = findMatch(itemPriceElement.textContent.trim(), globals.currencySymbolsRegExp); const shippingCurrencySymbol = findMatch(itemShippingElement.textContent.trim(), globals.currencySymbolsRegExp); if (shippingCurrencySymbol && (shippingCurrencySymbol === priceCurrencySymbol)) { const totalPrice = ((getValue(itemPriceElement) + getValue(itemShippingElement)) / 100).toFixed(2); const HTML = sprintf2({ template: itemShippingElementTemplate || itemPriceElementTemplate, values: { itemPrice: itemPriceElement.textContent.trim(), itemShippingAmount: itemShippingElement.textContent.trim(), currencySymbol: shippingCurrencySymbol, totalPrice: totalPrice } }); if (itemPriceElementTemplate) { itemPriceElement.insertAdjacentHTML('afterend', HTML); } else { itemShippingElement.innerHTML = HTML; } const itemPriceElementObserver = new MutationObserver((mutationList, observer) => { mutationList.forEach((mutation) => { mutation.addedNodes.forEach((element) => { if (element.nodeName == '#text') { const totalPriceElement = getNode(globals.itemPriceElementTemplateSelector); const totalPrice = ((getValue(itemPriceElement) + getValue(itemShippingElement)) / 100).toFixed(2); const totalPriceText = sprintf2({ template: globals.itemPriceElementInnerTextTemplate, values: { itemPrice: itemPriceElement.textContent.trim(), itemShippingAmount: itemShippingElement.textContent.trim(), currencySymbol: shippingCurrencySymbol, totalPrice: totalPrice } }); totalPriceElement.textContent = totalPriceText; } }); }); }); itemPriceElementObserver.observe(itemPriceElement, {childList: true}); } } } } function processListGallery({listItemsSelector, itemPriceElementSelector, itemPriceElementTemplate = null, itemShippingElementSelector, itemShippingElementTemplate = null}) { const listItems = qs({selector: listItemsSelector, all: true, array: true}); if (listItems) { for (let i = 0; listItems[i]; i++) { const itemPriceElement = qs({selector: itemPriceElementSelector, scope: listItems[i]}); const itemShippingElement = qs({selector: itemShippingElementSelector, scope: listItems[i], contains: /\d/}); if (itemPriceElement && itemShippingElement) { const priceCurrencySymbol = findMatch(itemPriceElement.textContent.trim(), globals.currencySymbolsRegExp); const shippingCurrencySymbol = findMatch(itemShippingElement.textContent.trim(), globals.currencySymbolsRegExp); if (shippingCurrencySymbol && (shippingCurrencySymbol === priceCurrencySymbol)) { const totalPrice = ((getValue(itemPriceElement) + getValue(itemShippingElement)) / 100).toFixed(2); const HTML = sprintf2({ template: itemShippingElementTemplate || itemPriceElementTemplate, values: { itemPrice: itemPriceElement.textContent.trim(), itemShippingAmount: itemShippingElement.textContent.trim(), currencySymbol: shippingCurrencySymbol, totalPrice: totalPrice } }); if (itemPriceElementTemplate) { itemPriceElement.insertAdjacentHTML('afterend', HTML); } else { itemShippingElement.innerHTML = HTML; } } } } } } var stylesheet = ".total-price{background:hsl(76,100%,50%)!important;color:hsl(0,0%,9%)!important;padding:1px 4px;margin-left:5px;font-size:20px!important;font-weight:400!important;}.s-item__detail{overflow:visible!important;}"; appendStyle({style: stylesheet}); processMethod({ search: { identifierSelector: ['#mainContent ul.srp-r###lts', '#mainContent ul.b-list__items_nofooter'], process: () => processListGallery({ listItemsSelector: '#mainContent li.s-item', itemPriceElementSelector: '.s-item__price', itemShippingElementSelector: '.s-item__shipping', itemPriceElementTemplate: globals.itemPriceElementTemplate }) }, sch: { identifierSelector: ['#mainContent ul#ListViewInner'], process: () => processListGallery({ listItemsSelector: '#mainContent li', itemPriceElementSelector: '.lvprice span', itemShippingElementSelector: '.lvshipping span.fee', itemPriceElementTemplate: globals.itemPriceElementTemplate }) }, itm: { identifierSelector: ['#mainContent form[name="viactiondetails"]'], process: () => processItemListing({ listItemsSelector: '#mainContent', itemPriceElementSelector: 'span[itemprop="price"]', convertPriceElementSelector: '#prcIsumConv', itemShippingElementSelector: 'div[class*="shipping"]', convertShippingElementSelector: '#convetedPriceId', itemPriceElementTemplate: globals.itemPriceElementTemplate }) } });