ebaytotalprice-userscript

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

目前为 2021-01-31 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name ebaytotalprice-userscript
  3. // @namespace https://github.com/subz390
  4. // @version 2.1.5.210131122553
  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).match(/\[object\s(\w+)\]/)[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 styleInject(style, className = undefined) {
  53. const el = document.createElement('style');
  54. el.appendChild(document.createTextNode(style));
  55. if (className) {el.className = className;}
  56. waitForMini({
  57. tryFor: 2,
  58. every: 100,
  59. test: () => document.querySelector('head'),
  60. success: (testResult) => {testResult.appendChild(el);}
  61. });
  62. }
  63.  
  64. function findMatch(string, regex, index) {
  65. if (string === null) return null
  66. index = index || 1;
  67. const m = string.match(regex);
  68. return (m) ? (index=='all' ? m : (m[index] ? m[index] : m[0])) : null
  69. }
  70.  
  71. function getNode(node = '', debug = undefined, scope = document) {
  72. try {
  73. scope = scope === null ? document : scope;
  74. if (typeof node == 'string') {
  75. if (node == '') {return null}
  76. if (typeof scope == 'string') {
  77. const tempScope = document.querySelector(scope);
  78. if (tempScope == null) {
  79. return null
  80. }
  81. scope = tempScope;
  82. }
  83. const element = scope.querySelector(node);
  84. return element
  85. }
  86. return node
  87. }
  88. catch (error) {
  89. console.error(error);
  90. }
  91. }
  92.  
  93. function setDefault(paramOptions, paramDefault = undefined, paramAction = undefined, debug = undefined) {
  94. let globalObject;
  95. let globalOptions;
  96. let globalAction;
  97. let globalDefault;
  98. if (realTypeOf(paramOptions) === 'object') {
  99. function getValue(object, array) {
  100. const name = array.find(name => object.hasOwnProperty(name));
  101. if (typeof name !== 'undefined') {
  102. return object[name]
  103. }
  104. else {
  105. return undefined
  106. }
  107. }
  108. globalOptions = getValue(paramOptions, ['option', 'options', 'property', 'props', 'properties']);
  109. globalObject = getValue(paramOptions, ['object']);
  110. globalDefault = getValue(paramOptions, ['default']);
  111. globalAction = getValue(paramOptions, ['action', 'callback']);
  112. }
  113. else {
  114. globalOptions = paramOptions;
  115. globalDefault = paramDefault;
  116. globalAction = paramAction;
  117. }
  118. if (globalOptions === undefined) {
  119. return globalDefault
  120. }
  121. if (typeof globalAction === 'string' && globalAction === 'set') {
  122. return globalDefault
  123. }
  124. function doAction(option, action = undefined, object = {}) {
  125. if (typeof action === 'function') {
  126. return action(option, object)
  127. }
  128. else {
  129. return option
  130. }
  131. }
  132. if (realTypeOf(globalObject) === 'object') {
  133. if (realTypeOf(globalOptions) === 'array') {
  134. for (let i=0; i < globalOptions.length; i++) {
  135. if (globalObject.hasOwnProperty(globalOptions[i])) {
  136. const result = doAction(globalObject[globalOptions[i]], globalAction, globalObject);
  137. if (result !== undefined) {
  138. return result
  139. }
  140. }
  141. }
  142. }
  143. else if (typeof globalOptions === 'string' && globalObject.hasOwnProperty(globalOptions)) {
  144. const result = doAction(globalObject[globalOptions], globalAction, globalObject);
  145. if (result !== undefined) {
  146. return result
  147. }
  148. }
  149. }
  150. else if (realTypeOf(globalOptions) === 'array') {
  151. for (let i=0; i < globalOptions.length; i++) {
  152. if (globalOptions[i] !== undefined) {
  153. const result = doAction(globalOptions[i], globalAction, globalObject);
  154. if (result !== undefined) {
  155. return result
  156. }
  157. }
  158. }
  159. }
  160. else {
  161. const result = doAction(globalOptions, globalAction, globalObject);
  162. if (result !== undefined) {
  163. return result
  164. }
  165. }
  166. return globalDefault
  167. }
  168.  
  169. function qs({selector = null, scope = document, array = false, all = false, contains = null, unittest = false, debugTag = ''} = {}) {
  170. const language = {
  171. en: {
  172. selectorUndefined: `${debugTag}selector is undefined`,
  173. scopeNotFound: `${debugTag}scope not found`,
  174. }
  175. };
  176. if (unittest === 'language') {return language}
  177. try {
  178. if (selector === null) {
  179. console.error(language.en.selectorUndefined);
  180. return null
  181. }
  182. if (scope !== document) {
  183. scope = getNode(scope);
  184. if (scope === null) {
  185. return null
  186. }
  187. }
  188. if (unittest === 'scope') {return scope}
  189. if (unittest === 'options') {
  190. return {
  191. selector: selector,
  192. scope: scope,
  193. array: array,
  194. all: all,
  195. contains: contains,
  196. unittest: unittest
  197. }
  198. }
  199. if (all === true) {
  200. const staticNodeList = scope.querySelectorAll(selector);
  201. if (staticNodeList.length === 0) {return null}
  202. if (array === true) {
  203. if (contains !== null) {
  204. const tempArray = [];
  205. staticNodeList.forEach((element) => {
  206. if (element.textContent.search(contains) !== -1) {
  207. tempArray.push(element);
  208. }
  209. });
  210. if (tempArray.length === 0) {return null}
  211. else {return tempArray}
  212. }
  213. return Array.from(staticNodeList)
  214. }
  215. else {
  216. if (contains !== null) {
  217. for (let index = 0; index < staticNodeList.length; index++) {
  218. if (staticNodeList[index].textContent.search(contains) !== -1) {
  219. return staticNodeList
  220. }
  221. }
  222. return null
  223. }
  224. return staticNodeList
  225. }
  226. }
  227. else {
  228. const qsHTMLElement = scope.querySelector(selector);
  229. if (qsHTMLElement === null) {return null}
  230. if (typeof contains === 'string' || contains instanceof RegExp) {
  231. if (qsHTMLElement.textContent.search(contains) === -1) {return null}
  232. }
  233. if (array === true) {return [qsHTMLElement]}
  234. else {return qsHTMLElement}
  235. }
  236. }
  237. catch (error) {
  238. console.error(error);
  239. }
  240. }
  241.  
  242. function sprintf(...args) {
  243. if (Object.keys(args).length == 0) {
  244. return null
  245. }
  246. if (args[0] === '') {
  247. return null
  248. }
  249. const options = setDefault({
  250. options: [realTypeOf(args[0]) == 'object' ? args[0] : undefined],
  251. default: {regex: /{([^{}]+)}/g, template: args[0]},
  252. });
  253. if (realTypeOf(args[1]) == 'object') {
  254. return options.template.replace(options.regex, (match, n) => {
  255. for (let key = 1; args[key]; key++) {
  256. if (args[key][n]) {
  257. if (typeof args[key][n] == 'function') {
  258. return args[key][n]().toString()
  259. }
  260. return args[key][n]
  261. }
  262. }
  263. return match
  264. })
  265. }
  266. else {
  267. return options.template.replace(options.regex, (match, n) => {return args[n] || match})
  268. }
  269. }
  270.  
  271. const globals = {
  272. priceMatchRegExp: /((\d+[,\.])+\d+)/,
  273. currencySymbolsRegExp: /((((AU|C|US) )?\$)|EUR|PHP|zł|£)/,
  274. itemPriceElementTemplate: '<span class="total-price">{currencySymbol}{totalPrice}</span>'
  275. };
  276.  
  277. function processMethod(options) {
  278. try {
  279. function getMethod() {
  280. for (const [, method] of Object.entries(options)) {
  281. for (let index = 0; index < method.identifierSelector.length; index++) {
  282. const selector = method.identifierSelector[index];
  283. const identifierNode = getNode(selector);
  284. if (identifierNode !== null) {return method}
  285. }
  286. }
  287. return null
  288. }
  289. const method = getMethod(options);
  290. if (method !== null) {method.process();}
  291. }
  292. catch (error) {console.error(error);}
  293. }
  294.  
  295. function getValue(element) {
  296. try {
  297. let value = findMatch(element.textContent.trim(), globals.priceMatchRegExp);
  298. value = value.replace(/[,\.]/g, '');
  299. value = parseFloat(value);
  300. return value
  301. }
  302. catch (error) {
  303. console.error(error);
  304. return null
  305. }
  306. }
  307.  
  308. function processItemListing({listItemsSelector, itemPriceElementSelector, convertPriceElementSelector, itemPriceElementTemplate = null, itemShippingElementSelector, convertShippingElementSelector, itemShippingElementTemplate = null}) {
  309. const content = qs({selector: listItemsSelector});
  310. if (content) {
  311. const itemPriceElement = qs({selector: convertPriceElementSelector, scope: content, contains: /\d/}) || qs({selector: itemPriceElementSelector, scope: content, contains: /\d/});
  312. const itemShippingElement = qs({selector: convertShippingElementSelector, scope: content, contains: /\d/}) || qs({selector: itemShippingElementSelector, scope: content, contains: /\d/});
  313. if (itemPriceElement && itemShippingElement) {
  314. const priceCurrencySymbol = findMatch(itemPriceElement.textContent.trim(), globals.currencySymbolsRegExp);
  315. const shippingCurrencySymbol = findMatch(itemShippingElement.textContent.trim(), globals.currencySymbolsRegExp);
  316. if (shippingCurrencySymbol && (shippingCurrencySymbol === priceCurrencySymbol)) {
  317. const totalPrice = ((getValue(itemPriceElement) + getValue(itemShippingElement)) / 100).toFixed(2);
  318. const HTML = sprintf(
  319. itemShippingElementTemplate || itemPriceElementTemplate, {
  320. itemPrice: itemPriceElement.textContent.trim(),
  321. itemShippingAmount: itemShippingElement.textContent.trim(),
  322. currencySymbol: shippingCurrencySymbol,
  323. totalPrice: totalPrice});
  324. if (itemPriceElementTemplate) {
  325. itemPriceElement.insertAdjacentHTML('afterend', HTML);
  326. }
  327. else {
  328. itemShippingElement.innerHTML = HTML;
  329. }
  330. }
  331. }
  332. }
  333. }
  334.  
  335. function processListGallery({listItemsSelector, itemPriceElementSelector, itemPriceElementTemplate = null, itemShippingElementSelector, itemShippingElementTemplate = null}) {
  336. const listItems = qs({selector: listItemsSelector, all: true, array: true});
  337. if (listItems) {
  338. for (let i=0; listItems[i]; i++) {
  339. const itemPriceElement = qs({selector: itemPriceElementSelector, scope: listItems[i]});
  340. const itemShippingElement = qs({selector: itemShippingElementSelector, scope: listItems[i], contains: /\d/});
  341. if (itemPriceElement && itemShippingElement) {
  342. const priceCurrencySymbol = findMatch(itemPriceElement.textContent.trim(), globals.currencySymbolsRegExp);
  343. const shippingCurrencySymbol = findMatch(itemShippingElement.textContent.trim(), globals.currencySymbolsRegExp);
  344. if (shippingCurrencySymbol && (shippingCurrencySymbol === priceCurrencySymbol)) {
  345. const totalPrice = ((getValue(itemPriceElement) + getValue(itemShippingElement)) / 100).toFixed(2);
  346. const HTML = sprintf(
  347. itemShippingElementTemplate || itemPriceElementTemplate, {
  348. itemPrice: itemPriceElement.textContent.trim(),
  349. itemShippingAmount: itemShippingElement.textContent.trim(),
  350. currencySymbol: shippingCurrencySymbol,
  351. totalPrice: totalPrice});
  352. if (itemPriceElementTemplate) {
  353. itemPriceElement.insertAdjacentHTML('afterend', HTML);
  354. }
  355. else {
  356. itemShippingElement.innerHTML = HTML;
  357. }
  358. }
  359. }
  360. }
  361. }
  362. }
  363.  
  364. 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;}";
  365.  
  366. styleInject(stylesheet);
  367. processMethod({
  368. search: {
  369. identifierSelector: ['#mainContent ul.srp-results', '#mainContent ul.b-list__items_nofooter'],
  370. process: () => processListGallery({
  371. listItemsSelector: '#mainContent li.s-item',
  372. itemPriceElementSelector: '.s-item__price',
  373. itemShippingElementSelector: '.s-item__shipping',
  374. itemPriceElementTemplate: globals.itemPriceElementTemplate
  375. })
  376. },
  377. sch: {
  378. identifierSelector: ['#mainContent ul#ListViewInner'],
  379. process: () => processListGallery({
  380. listItemsSelector: '#mainContent li',
  381. itemPriceElementSelector: '.lvprice span',
  382. itemShippingElementSelector: '.lvshipping span.fee',
  383. itemPriceElementTemplate: globals.itemPriceElementTemplate
  384. })
  385. },
  386. itm: {
  387. identifierSelector: ['#mainContent form[name="viactiondetails"]'],
  388. process: () => processItemListing({
  389. listItemsSelector: '#mainContent',
  390. itemPriceElementSelector: 'span[itemprop="price"]',
  391. convertPriceElementSelector: '#prcIsumConv',
  392. itemShippingElementSelector: '#fshippingCost',
  393. convertShippingElementSelector: '#convetedPriceId',
  394. itemPriceElementTemplate: globals.itemPriceElementTemplate
  395. })
  396. }
  397. });