ebaytotalprice-userscript

Add the total eBay auction price including postage in the auction listing

当前为 2021-03-22 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ebaytotalprice-userscript
  3. // @namespace https://github.com/subz390
  4. // @version 2.2.0.210321235612
  5. // @description Add the total eBay auction price including postage in the auction listing
  6. // @author SubZ390
  7. // @license MIT
  8. // @run-at document-idle
  9. // @grant none
  10. // @noframes
  11. // @include /^https?://(ar|b[ory]|c[lor]|do|ec|gt|hn|il|kz|mx|ni|p[aerty]|ru|sv|uy|ve|www)\.ebay\.com/
  12. // @include /^https?://www\.ebay\.com\.au/
  13. // @include /^https?://www\.ebay\.co\.uk/
  14. // @include /^https?://www\.ebay\.(at|ca|de|es|fr|ie|it|nl|ph|pl)/
  15. // @include /^https?://www\.be(nl|fr)\.ebay\.be/
  16. //
  17. //
  18. // ==/UserScript==
  19.  
  20. function realTypeOf(object, lowerCase = true) {
  21. if (typeof object !== 'object') {return typeof object}
  22. if (object === null) {return 'null'}
  23. const internalClass = Object.prototype.toString.call(object).slice(8, -1);
  24. return lowerCase === true ? internalClass.toLowerCase() : internalClass
  25. }
  26.  
  27. function waitForMini({tryFor = 3, every = 100, test = () => false, success = () => null, timeout = () => null} = {}) {
  28. function leadingEdge() {
  29. const testResult = test();
  30. if (testResult) {
  31. success(testResult);
  32. return true
  33. }
  34. return false
  35. }
  36. if (leadingEdge() === false) {
  37. const intervalReference = setInterval(() => {
  38. const testResult = test();
  39. if (testResult) {
  40. clearInterval(intervalReference);
  41. clearTimeout(setTimeoutReference);
  42. success(testResult);
  43. }
  44. }, every);
  45. const setTimeoutReference = setTimeout(() => {
  46. clearInterval(intervalReference);
  47. timeout();
  48. }, tryFor * 1000);
  49. }
  50. }
  51.  
  52. function getNode(node = '', debug = undefined, scope = document) {
  53. try {
  54. scope = scope === null ? document : scope;
  55. const nodeType = realTypeOf(node);
  56. if (nodeType == 'string') {
  57. if (node == '') {return null}
  58. let scopeType = realTypeOf(scope);
  59. if (scopeType == 'text') {
  60. return null
  61. }
  62. if (scopeType == 'string') {
  63. const tempScope = document.querySelector(scope);
  64. if (tempScope == null) {
  65. return null
  66. }
  67. scope = tempScope;
  68. }
  69. scopeType = realTypeOf(scope);
  70. if (scopeType.search(/array|nodelist|html/i) !== -1) {
  71. nodeType;
  72. const element = scope.querySelector(node);
  73. return element
  74. }
  75. else {
  76. return null
  77. }
  78. }
  79. else if (nodeType.search(/array|nodelist|html/i) !== -1) {
  80. return node
  81. }
  82. else {
  83. return null
  84. }
  85. }
  86. catch (error) {
  87. console.error(error);
  88. }
  89. }
  90.  
  91. function appendStyle({style: styleString, className = undefined, whereAdjacent = 'afterend', whereTarget = 'body', tryFor = 5, failMessage = undefined}) {
  92. return new Promise((resolve, reject) => {
  93. const styleElement = document.createElement('style');
  94. styleElement.appendChild(document.createTextNode(styleString));
  95. if (className) {styleElement.className = className;}
  96. function appendTarget(targetNode, styleElement) {
  97. if (whereAdjacent !== undefined) {
  98. return targetNode.insertAdjacentElement(whereAdjacent, styleElement)
  99. }
  100. else {
  101. return targetNode.appendChild(styleElement)
  102. }
  103. }
  104. waitForMini({
  105. tryFor: tryFor,
  106. every: 100,
  107. test: () => getNode(whereTarget),
  108. success: (targetNode) => {resolve(appendTarget(targetNode, styleElement));},
  109. timeout: () => reject(Error(failMessage || `appendStyle timed out whilst waiting for targetNode: ${whereTarget}`))
  110. });
  111. })
  112. }
  113.  
  114. function findMatch(string, regex, index = 1) {
  115. if (string === null) return null
  116. const m = string.match(regex);
  117. return (m) ? (index=='all' ? m : (m[index] ? m[index] : m[0])) : null
  118. }
  119.  
  120. function setDefault(paramOptions, paramDefault = undefined, paramAction = undefined, debug = undefined) {
  121. let globalObject;
  122. let globalOptions;
  123. let globalAction;
  124. let globalDefault;
  125. if (realTypeOf(paramOptions) === 'object') {
  126. function getValue(object, array) {
  127. const name = array.find(name => object.hasOwnProperty(name));
  128. if (typeof name !== 'undefined') {
  129. return object[name]
  130. }
  131. else {
  132. return undefined
  133. }
  134. }
  135. globalOptions = getValue(paramOptions, ['option', 'options', 'property', 'props', 'properties']);
  136. globalObject = getValue(paramOptions, ['object']);
  137. globalDefault = getValue(paramOptions, ['default']);
  138. globalAction = getValue(paramOptions, ['action', 'callback']);
  139. }
  140. else {
  141. globalOptions = paramOptions;
  142. globalDefault = paramDefault;
  143. globalAction = paramAction;
  144. }
  145. if (globalOptions === undefined) {
  146. return globalDefault
  147. }
  148. if (typeof globalAction === 'string' && globalAction === 'set') {
  149. return globalDefault
  150. }
  151. function doAction(option, action = undefined, object = {}) {
  152. if (typeof action === 'function') {
  153. return action(option, object)
  154. }
  155. else {
  156. return option
  157. }
  158. }
  159. if (realTypeOf(globalObject) === 'object') {
  160. if (realTypeOf(globalOptions) === 'array') {
  161. for (let i=0; i < globalOptions.length; i++) {
  162. if (globalObject.hasOwnProperty(globalOptions[i])) {
  163. const result = doAction(globalObject[globalOptions[i]], globalAction, globalObject);
  164. if (result !== undefined) {
  165. return result
  166. }
  167. }
  168. }
  169. }
  170. else if (typeof globalOptions === 'string' && globalObject.hasOwnProperty(globalOptions)) {
  171. const result = doAction(globalObject[globalOptions], globalAction, globalObject);
  172. if (result !== undefined) {
  173. return result
  174. }
  175. }
  176. }
  177. else if (realTypeOf(globalOptions) === 'array') {
  178. for (let i=0; i < globalOptions.length; i++) {
  179. if (globalOptions[i] !== undefined) {
  180. const result = doAction(globalOptions[i], globalAction, globalObject);
  181. if (result !== undefined) {
  182. return result
  183. }
  184. }
  185. }
  186. }
  187. else {
  188. const result = doAction(globalOptions, globalAction, globalObject);
  189. if (result !== undefined) {
  190. return result
  191. }
  192. }
  193. return globalDefault
  194. }
  195.  
  196. function qs({selector = null, scope = document, array = false, all = false, contains = null, unittest = false, debugTag = ''} = {}) {
  197. const language = {
  198. en: {
  199. selectorUndefined: `${debugTag}selector is undefined`,
  200. scopeNotUseable: `${debugTag}scope is not useable`,
  201. }
  202. };
  203. if (unittest === 'language') {return language}
  204. try {
  205. if (selector === null) {
  206. console.error(language.en.selectorUndefined);
  207. return null
  208. }
  209. if (scope !== document) {
  210. scope = getNode(scope);
  211. if (scope === null) {
  212. return null
  213. }
  214. }
  215. if (unittest === 'scope') {return scope}
  216. if (unittest === 'options') {
  217. return {
  218. selector: selector,
  219. scope: scope,
  220. array: array,
  221. all: all,
  222. contains: contains,
  223. unittest: unittest
  224. }
  225. }
  226. if (all === true) {
  227. const staticNodeList = scope.querySelectorAll(selector);
  228. if (staticNodeList.length === 0) {return null}
  229. if (array === true) {
  230. if (contains !== null) {
  231. const tempArray = [];
  232. staticNodeList.forEach((element) => {
  233. if (element.textContent.search(contains) !== -1) {
  234. tempArray.push(element);
  235. }
  236. });
  237. if (tempArray.length === 0) {return null}
  238. else {return tempArray}
  239. }
  240. return Array.from(staticNodeList)
  241. }
  242. else {
  243. if (contains !== null) {
  244. for (let index = 0; index < staticNodeList.length; index++) {
  245. if (staticNodeList[index].textContent.search(contains) !== -1) {
  246. return staticNodeList
  247. }
  248. }
  249. return null
  250. }
  251. return staticNodeList
  252. }
  253. }
  254. else {
  255. const qsHTMLElement = scope.querySelector(selector);
  256. if (qsHTMLElement === null) {return null}
  257. if (typeof contains === 'string' || contains instanceof RegExp) {
  258. if (qsHTMLElement.textContent.search(contains) === -1) {return null}
  259. }
  260. if (array === true) {return [qsHTMLElement]}
  261. else {return qsHTMLElement}
  262. }
  263. }
  264. catch (error) {
  265. console.error(error);
  266. }
  267. }
  268.  
  269. function sprintf(...args) {
  270. if (Object.keys(args).length == 0) {
  271. return null
  272. }
  273. if (args[0] === '') {
  274. return null
  275. }
  276. const options = setDefault({
  277. options: [realTypeOf(args[0]) == 'object' ? args[0] : undefined],
  278. default: {regex: /{([^{}]+)}/g, template: args[0]},
  279. });
  280. if (realTypeOf(args[1]) == 'object') {
  281. return options.template.replace(options.regex, (match, n) => {
  282. for (let key = 1; args[key]; key++) {
  283. if (typeof args[key][n] == 'string' && args[key][n].length == 0) {
  284. return ''
  285. }
  286. else if (args[key][n]) {
  287. if (typeof args[key][n] == 'function') {
  288. return args[key][n]().toString()
  289. }
  290. return args[key][n]
  291. }
  292. }
  293. return match
  294. })
  295. }
  296. else {
  297. return options.template.replace(options.regex, (match, n) => {return args[n] || match})
  298. }
  299. }
  300.  
  301. const globals = {
  302. priceMatchRegExp: /((\d+[,\.])+\d+)/,
  303. currencySymbolsRegExp: /((((AU|C|US) )?\$)|EUR|PHP|zł|£)/,
  304. itemPriceElementTemplate: '<span class="total-price">{currencySymbol}{totalPrice}</span>'
  305. };
  306.  
  307. function processMethod(options) {
  308. try {
  309. function getMethod() {
  310. for (const [, method] of Object.entries(options)) {
  311. for (let index = 0; index < method.identifierSelector.length; index++) {
  312. const selector = method.identifierSelector[index];
  313. const identifierNode = getNode(selector);
  314. if (identifierNode !== null) {return method}
  315. }
  316. }
  317. return null
  318. }
  319. const method = getMethod(options);
  320. if (method !== null) {method.process();}
  321. }
  322. catch (error) {console.error(error);}
  323. }
  324.  
  325. function getValue(element) {
  326. try {
  327. let value = findMatch(element.textContent.trim(), globals.priceMatchRegExp);
  328. value = value.replace(/[,\.]/g, '');
  329. value = parseFloat(value);
  330. return value
  331. }
  332. catch (error) {
  333. console.error(error);
  334. return null
  335. }
  336. }
  337.  
  338. function processItemListing({listItemsSelector, itemPriceElementSelector, convertPriceElementSelector, itemPriceElementTemplate = null, itemShippingElementSelector, convertShippingElementSelector, itemShippingElementTemplate = null}) {
  339. const content = qs({selector: listItemsSelector});
  340. if (content) {
  341. const itemPriceElement = qs({selector: convertPriceElementSelector, scope: content, contains: /\d/}) || qs({selector: itemPriceElementSelector, scope: content, contains: /\d/});
  342. const itemShippingElement = qs({selector: convertShippingElementSelector, scope: content, contains: /\d/}) || qs({selector: itemShippingElementSelector, scope: content, contains: /\d/});
  343. if (itemPriceElement && itemShippingElement) {
  344. const priceCurrencySymbol = findMatch(itemPriceElement.textContent.trim(), globals.currencySymbolsRegExp);
  345. const shippingCurrencySymbol = findMatch(itemShippingElement.textContent.trim(), globals.currencySymbolsRegExp);
  346. if (shippingCurrencySymbol && (shippingCurrencySymbol === priceCurrencySymbol)) {
  347. const totalPrice = ((getValue(itemPriceElement) + getValue(itemShippingElement)) / 100).toFixed(2);
  348. const HTML = sprintf(
  349. itemShippingElementTemplate || itemPriceElementTemplate, {
  350. itemPrice: itemPriceElement.textContent.trim(),
  351. itemShippingAmount: itemShippingElement.textContent.trim(),
  352. currencySymbol: shippingCurrencySymbol,
  353. totalPrice: totalPrice});
  354. if (itemPriceElementTemplate) {
  355. itemPriceElement.insertAdjacentHTML('afterend', HTML);
  356. }
  357. else {
  358. itemShippingElement.innerHTML = HTML;
  359. }
  360. }
  361. }
  362. }
  363. }
  364.  
  365. function processListGallery({listItemsSelector, itemPriceElementSelector, itemPriceElementTemplate = null, itemShippingElementSelector, itemShippingElementTemplate = null}) {
  366. const listItems = qs({selector: listItemsSelector, all: true, array: true});
  367. if (listItems) {
  368. for (let i=0; listItems[i]; i++) {
  369. const itemPriceElement = qs({selector: itemPriceElementSelector, scope: listItems[i]});
  370. const itemShippingElement = qs({selector: itemShippingElementSelector, scope: listItems[i], contains: /\d/});
  371. if (itemPriceElement && itemShippingElement) {
  372. const priceCurrencySymbol = findMatch(itemPriceElement.textContent.trim(), globals.currencySymbolsRegExp);
  373. const shippingCurrencySymbol = findMatch(itemShippingElement.textContent.trim(), globals.currencySymbolsRegExp);
  374. if (shippingCurrencySymbol && (shippingCurrencySymbol === priceCurrencySymbol)) {
  375. const totalPrice = ((getValue(itemPriceElement) + getValue(itemShippingElement)) / 100).toFixed(2);
  376. const HTML = sprintf(
  377. itemShippingElementTemplate || itemPriceElementTemplate, {
  378. itemPrice: itemPriceElement.textContent.trim(),
  379. itemShippingAmount: itemShippingElement.textContent.trim(),
  380. currencySymbol: shippingCurrencySymbol,
  381. totalPrice: totalPrice});
  382. if (itemPriceElementTemplate) {
  383. itemPriceElement.insertAdjacentHTML('afterend', HTML);
  384. }
  385. else {
  386. itemShippingElement.innerHTML = HTML;
  387. }
  388. }
  389. }
  390. }
  391. }
  392. }
  393.  
  394. var stylesheet = ".total-price{background:#bf0;color:#f00!important;outline:2px solid;padding:1px 4px;margin-left:5px;font-size:20px!important;font-weight:400!important;}.s-item__detail{overflow:visible!important;}";
  395.  
  396. appendStyle({style: stylesheet});
  397. processMethod({
  398. search: {
  399. identifierSelector: ['#mainContent ul.srp-results', '#mainContent ul.b-list__items_nofooter'],
  400. process: () => processListGallery({
  401. listItemsSelector: '#mainContent li.s-item',
  402. itemPriceElementSelector: '.s-item__price',
  403. itemShippingElementSelector: '.s-item__shipping',
  404. itemPriceElementTemplate: globals.itemPriceElementTemplate
  405. })
  406. },
  407. sch: {
  408. identifierSelector: ['#mainContent ul#ListViewInner'],
  409. process: () => processListGallery({
  410. listItemsSelector: '#mainContent li',
  411. itemPriceElementSelector: '.lvprice span',
  412. itemShippingElementSelector: '.lvshipping span.fee',
  413. itemPriceElementTemplate: globals.itemPriceElementTemplate
  414. })
  415. },
  416. itm: {
  417. identifierSelector: ['#mainContent form[name="viactiondetails"]'],
  418. process: () => processItemListing({
  419. listItemsSelector: '#mainContent',
  420. itemPriceElementSelector: 'span[itemprop="price"]',
  421. convertPriceElementSelector: '#prcIsumConv',
  422. itemShippingElementSelector: '#fshippingCost',
  423. convertShippingElementSelector: '#convetedPriceId',
  424. itemPriceElementTemplate: globals.itemPriceElementTemplate
  425. })
  426. }
  427. });