ebaytotalprice-userscript

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

目前為 2020-12-25 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name ebaytotalprice-userscript
  3. // @namespace https://github.com/subz390
  4. // @version 2.0.2.201225055452
  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?://www\.ebay\.co\.uk/
  12. //
  13. //
  14. // ==/UserScript==
  15.  
  16. function realTypeOf(object, lowerCase = true) {
  17. if (typeof object !== 'object') {return typeof object}
  18. if (object === null) {return 'null'}
  19. const internalClass = Object.prototype.toString.call(object).match(/\[object\s(\w+)\]/)[1];
  20. return lowerCase === true ? internalClass.toLowerCase() : internalClass
  21. }
  22.  
  23. function styleInject(style, className = undefined) {
  24. const el = document.createElement('style');
  25. el.appendChild(document.createTextNode(style));
  26. if (className) {el.className = className;}
  27. document.head.appendChild(el);
  28. return el
  29. }
  30.  
  31. function findMatch(string, regex, index) {
  32. if (string === null) return null
  33. index = index || 1;
  34. let m = string.match(regex);
  35. return (m) ? (index=='all' ? m : (m[index] ? m[index] : m[0])) : null
  36. }
  37.  
  38. function getNode(node = '', debug = undefined, scope = document) {
  39. try {
  40. if (typeof node == 'string') {
  41. if (node == '') {return null}
  42. if (typeof scope == 'string') {
  43. const tempScope = document.querySelector(scope);
  44. if (tempScope == null) {
  45. return null
  46. }
  47. scope = tempScope;
  48. }
  49. const element = scope.querySelector(node);
  50. return element
  51. }
  52. return node
  53. }
  54. catch (error) {
  55. console.error(error);
  56. }
  57. }
  58.  
  59. function setDefault(paramOptions, paramDefault = undefined, paramAction = undefined, debugging = undefined) {
  60. let globalObject;
  61. let globalOptions;
  62. let globalAction;
  63. let globalDefault;
  64. if (realTypeOf(paramOptions) === 'object') {
  65. const getProp = (object, array) => {
  66. const name = array.find(name => object.hasOwnProperty(name));
  67. if (typeof name !== 'undefined') {
  68. return object[name]
  69. }
  70. else {
  71. return undefined
  72. }
  73. };
  74. globalOptions = getProp(paramOptions, ['option', 'options', 'property', 'props', 'properties']);
  75. globalObject = getProp(paramOptions, ['object']);
  76. globalDefault = getProp(paramOptions, ['default']);
  77. globalAction = getProp(paramOptions, ['action', 'callback']);
  78. }
  79. else {
  80. globalOptions = paramOptions;
  81. globalDefault = paramDefault;
  82. globalAction = paramAction;
  83. }
  84. if (globalOptions === undefined) {
  85. return globalDefault
  86. }
  87. if (typeof globalAction === 'string' && globalAction === 'set') {
  88. return globalDefault
  89. }
  90. function doAction(option, action = undefined, object = {}) {
  91. if (typeof action === 'function') {
  92. return action(option, object)
  93. }
  94. else {
  95. return option
  96. }
  97. }
  98. if (realTypeOf(globalObject) === 'object') {
  99. if (realTypeOf(globalOptions) === 'array') {
  100. for (let i=0; i < globalOptions.length; i++) {
  101. if (globalObject.hasOwnProperty(globalOptions[i])) {
  102. const result = doAction(globalObject[globalOptions[i]], globalAction, globalObject);
  103. if (result !== undefined) {
  104. return result
  105. }
  106. }
  107. }
  108. }
  109. else if (typeof globalOptions === 'string' && globalObject.hasOwnProperty(globalOptions)) {
  110. const result = doAction(globalObject[globalOptions], globalAction, globalObject);
  111. if (result !== undefined) {
  112. return result
  113. }
  114. }
  115. }
  116. else if (realTypeOf(globalOptions) === 'array') {
  117. for (let i=0; i < globalOptions.length; i++) {
  118. if (globalOptions[i] !== undefined) {
  119. const result = doAction(globalOptions[i], globalAction, globalObject);
  120. if (result !== undefined) {
  121. return result
  122. }
  123. }
  124. }
  125. }
  126. else {
  127. const result = doAction(globalOptions, globalAction, globalObject);
  128. if (result !== undefined) {
  129. return result
  130. }
  131. }
  132. return globalDefault
  133. }
  134.  
  135. function qs({selector = null, scope = document, array = false, all = false, contains = null, unittest = false} = {}) {
  136. try {
  137. if (selector === null) {
  138. return null
  139. }
  140. if (scope !== document) {
  141. scope = getNode(scope);
  142. if (scope === null) {
  143. return null
  144. }
  145. }
  146. if (unittest === 'scope') {return scope}
  147. if (all === true) {
  148. const qsNodeList = scope.querySelectorAll(selector);
  149. if (qsNodeList.length === 0) {return null}
  150. if (array === true) {
  151. if (contains !== null) {
  152. let tempArray = [];
  153. qsNodeList.forEach((element) => {
  154. if (element.textContent.search(contains) !== -1) {
  155. tempArray.push(element);
  156. }
  157. });
  158. if (tempArray.length === 0) {return null}
  159. else {return tempArray}
  160. }
  161. return Array.from(qsNodeList)
  162. }
  163. else {
  164. if (contains !== null) {
  165. for (let index = 0; index < qsNodeList.length; index++) {
  166. if (qsNodeList[index].textContent.search(contains) !== -1) {return qsNodeList[index]}
  167. }
  168. return null
  169. }
  170. return qsNodeList
  171. }
  172. }
  173. else {
  174. const qsHTMLElement = scope.querySelector(selector);
  175. if (qsHTMLElement === null) {return null}
  176. if (typeof contains === 'string' || contains instanceof RegExp) {
  177. if (qsHTMLElement.textContent.search(contains) === -1) {return null}
  178. }
  179. if (array === true) {return [qsHTMLElement]}
  180. else {return qsHTMLElement}
  181. }
  182. }
  183. catch (error) {
  184. console.error(error);
  185. }
  186. }
  187.  
  188. function sprintf(...args) {
  189. if (Object.keys(args).length == 0) {
  190. return null
  191. }
  192. if (args[0] === '') {
  193. return null
  194. }
  195. const options = setDefault({
  196. options: [realTypeOf(args[0]) == 'object' ? args[0] : undefined],
  197. default: {regex: /{([^{}]+)}/g, template: args[0]},
  198. });
  199. if (realTypeOf(args[1]) == 'object') {
  200. return options.template.replace(options.regex, function(match, n) {
  201. for (let k = 1; args[k]; k++) {
  202. if (args[k][n]) {
  203. if (typeof args[k][n] == 'function') {return args[k][n]().toString()}
  204. return args[k][n]
  205. }
  206. }
  207. return match
  208. })
  209. }
  210. else {
  211. return options.template.replace(options.regex, function(match, n) {
  212. return args[n] || match
  213. })
  214. }
  215. }
  216.  
  217. var stylesheet = ".total-price{background:#bf0;color:#111!important;outline:2px solid;padding:1px 4px;margin-left:5px;font-size:20px!important;font-weight:400!important;}.s-item__detail{overflow:visible!important;}";
  218.  
  219. styleInject(stylesheet);
  220. function processListGallery({listItemsSelector, itemPriceElementSelector, itemPriceElementTemplate = null, itemShippingElementSelector, itemShippingElementTemplate = null}) {
  221. const listItems = qs({selector: listItemsSelector, all: true, array: true});
  222. if (listItems) {
  223. for (let i=0; listItems[i]; i++) {
  224. const itemPriceElement = qs({selector: itemPriceElementSelector, scope: listItems[i]});
  225. const itemShippingElement = qs({selector: itemShippingElementSelector, scope: listItems[i], contains: /\d/});
  226. if (itemPriceElement && itemShippingElement) {
  227. const priceCurrencySymbol = findMatch(itemPriceElement.textContent.trim(), /(\$|£|EUR)/);
  228. const shippingCurrencySymbol = findMatch(itemShippingElement.textContent.trim(), /(\$|£|EUR)/);
  229. if (shippingCurrencySymbol && (shippingCurrencySymbol === priceCurrencySymbol)) {
  230. const totalPrice = parseFloat(findMatch(itemPriceElement.textContent.trim(), /(\d+[\.,]\d+)/).replace(',', '.')) +
  231. parseFloat(findMatch(itemShippingElement.textContent.trim(), /(\d+[\.,]\d+)/).replace(',', '.'));
  232. const HTML = sprintf(
  233. itemShippingElementTemplate || itemPriceElementTemplate, {
  234. itemPrice: itemPriceElement.textContent.trim(),
  235. itemShippingAmount: itemShippingElement.textContent.trim(),
  236. shippingCurrencySymbol: shippingCurrencySymbol,
  237. totalPrice: totalPrice.toFixed(2)});
  238. if (itemPriceElementTemplate) {
  239. itemPriceElement.insertAdjacentHTML('afterend', HTML);
  240. }
  241. else {
  242. itemShippingElement.innerHTML = HTML;
  243. }
  244. }
  245. }
  246. }
  247. }
  248. }
  249. function processItemListing({listItemsSelector, itemPriceElementSelector, convertPriceElementSelector, itemPriceElementTemplate = null, itemShippingElementSelector, convertShippingElementSelector, itemShippingElementTemplate = null}) {
  250. const content = qs({selector: listItemsSelector});
  251. if (content) {
  252. const priceElement = qs({selector: convertPriceElementSelector, scope: content}) || qs({selector: itemPriceElementSelector, scope: content});
  253. const shippingElement = qs({selector: convertShippingElementSelector, scope: content, contains: /\d/}) || qs({selector: itemShippingElementSelector, scope: content, contains: /\d/});
  254. if (priceElement && shippingElement) {
  255. const priceCurrencySymbol = findMatch(priceElement.textContent.trim(), /([^\d ]+) ?\d+\.\d+/);
  256. const shippingCurrencySymbol = findMatch(shippingElement.textContent.trim(), /([^\d ]+) ?\d+\.\d+/);
  257. if (shippingCurrencySymbol && (shippingCurrencySymbol === priceCurrencySymbol)) {
  258. const totalPrice = parseFloat(findMatch(priceElement.textContent.trim(), /(\d+\.\d+)/)) + parseFloat(findMatch(shippingElement.textContent.trim(), /(\d+\.\d+)/));
  259. const HTML = sprintf(
  260. itemShippingElementTemplate || itemPriceElementTemplate, {
  261. itemPrice: priceElement.textContent.trim(),
  262. itemShippingAmount: shippingElement.textContent.trim(),
  263. shippingCurrencySymbol: shippingCurrencySymbol,
  264. totalPrice: totalPrice.toFixed(2)});
  265. if (itemPriceElementTemplate) {
  266. priceElement.insertAdjacentHTML('afterend', HTML);
  267. }
  268. else {
  269. shippingElement.innerHTML = HTML;
  270. }
  271. }
  272. }
  273. }
  274. }
  275. const itemPriceElementTemplate = '<span class="total-price">{shippingCurrencySymbol}{totalPrice}</span>';
  276. const options = {
  277. search: {
  278. identifierSelector: ['#mainContent ul.srp-results', '#mainContent ul.b-list__items_nofooter'],
  279. process: () => processListGallery({
  280. listItemsSelector: '#mainContent li.s-item',
  281. itemPriceElementSelector: '.s-item__price',
  282. itemShippingElementSelector: '.s-item__shipping',
  283. itemPriceElementTemplate: itemPriceElementTemplate
  284. })
  285. },
  286. sch: {
  287. identifierSelector: ['#mainContent ul#ListViewInner'],
  288. process: () => processListGallery({
  289. listItemsSelector: '#mainContent li',
  290. itemPriceElementSelector: '.lvprice span',
  291. itemShippingElementSelector: '.lvshipping span.fee',
  292. itemPriceElementTemplate: itemPriceElementTemplate
  293. })
  294. },
  295. itm: {
  296. identifierSelector: ['#mainContent form[name="viactiondetails"]'],
  297. process: () => processItemListing({
  298. listItemsSelector: '#mainContent',
  299. itemPriceElementSelector: '#prcIsum_bidPrice',
  300. convertPriceElementSelector: '#prcIsumConv',
  301. itemShippingElementSelector: '#fshippingCost',
  302. convertShippingElementSelector: '#convetedPriceId',
  303. itemPriceElementTemplate: itemPriceElementTemplate
  304. })
  305. }
  306. };
  307. function identifyMethod(option, value) {
  308. for (const [option, value] of Object.entries(options)) {
  309. for (let index = 0; index < value.identifierSelector.length; index++) {
  310. const selector = value.identifierSelector[index];
  311. const identifierNode = getNode(selector);
  312. if (identifierNode !== null) {
  313. value.process();
  314. return
  315. }
  316. }
  317. }
  318. }
  319. try {
  320. identifyMethod();
  321. }
  322. catch (error) {console.error(error);}