Greasy Fork 支持简体中文。

Amazon Seller Product Price Loader

Optimized version to load Amazon product prices with customizable settings, minimized RAM usage, and UI.

  1. // ==UserScript==
  2. // @name Amazon Seller Product Price Loader
  3. // @namespace http://tampermonkey.net/
  4. // @version 4.8
  5. // @description Optimized version to load Amazon product prices with customizable settings, minimized RAM usage, and UI.
  6. // @license MIT https://opensource.org/licenses/MIT
  7. // @match https://sellercentral.amazon.com/*
  8. // @match https://sellercentral.amazon.co.uk/*
  9. // @match https://sellercentral.amazon.de/*
  10. // @match https://sellercentral.amazon.fr/*
  11. // @match https://sellercentral.amazon.it/*
  12. // @match https://sellercentral.amazon.es/*
  13. // @match https://sellercentral.amazon.ca/*
  14. // @match https://sellercentral.amazon.com.mx/*
  15. // @match https://sellercentral.amazon.com.br/*
  16. // @match https://sellercentral.amazon.co.jp/*
  17. // @match https://sellercentral.Amazon.com.br/*
  18. // @match https://sellercentral.amazon.com.au/*
  19. // @grant GM_getValue
  20. // @grant GM_setValue
  21. // @grant GM_registerMenuCommand
  22. // @grant GM_addStyle
  23. // @grant GM_getResourceText
  24. // @grant GM_xmlhttpRequest
  25. // @resource IMPORTED_CSS https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css
  26. // @run-at document-end
  27. // ==/UserScript==
  28.  
  29. (function () {
  30. 'use strict';
  31. function applySettings() {
  32. // Apply font size, font family, and bold settings
  33. document.body.style.fontSize = `${options.fontSize}px`;
  34. document.body.style.fontFamily = options.fontFamily;
  35. document.body.style.fontWeight = options.fontBold ? 'bold' : 'normal';
  36.  
  37. // Apply font color
  38. document.body.style.color = options.fontColor || 'initial';
  39.  
  40. // Apply glow settings if enabled
  41. if (options.glowEnabled) {
  42. document.body.style.textShadow = `0px 0px 5px ${options.glowColor}`;
  43. } else {
  44. document.body.style.textShadow = 'none';
  45. }
  46. }
  47.  
  48. document.addEventListener('DOMContentLoaded', () => {
  49. // Load saved options from GM_setValue
  50. options.fontSize = GM_getValue('fontSize', 14); // Default to 14px if not set
  51. options.fontFamily = GM_getValue('fontFamily', 'Arial'); // Default to Arial if not set
  52. options.fontBold = GM_getValue('fontBold', false); // Default to false if not set
  53. options.fontColor = GM_getValue('fontColor', '#000000'); // Default to black if not set
  54. options.glowEnabled = GM_getValue('glowEnabled', false); // Default to false if not set
  55. options.glowColor = GM_getValue('glowColor', '#ffffff'); // Default to white if not set
  56.  
  57. // Apply loaded settings
  58. applySettings();
  59.  
  60. // Create font color input and add event listener
  61. const fontColorInput = document.createElement('input');
  62. fontColorInput.type = 'color';
  63. fontColorInput.value = options.fontColor; // Set the current font color
  64. fontColorInput.addEventListener('change', (e) => {
  65. options.fontColor = e.target.value;
  66. GM_setValue('fontColor', options.fontColor);
  67. applySettings(); // Reapply settings to update font color immediately
  68. });
  69.  
  70. // Add the font color input to the settings UI (for example, adding to a settings container)
  71. document.body.appendChild(fontColorInput); // Adjust where you want it placed in your UI
  72. });
  73.  
  74. const defaultOptions = {
  75. fontSize: '12px',
  76. fontFamily: 'Arial, sans-serif',
  77. textColor: '#0066c0',
  78. priceColor: '#0066c0',
  79. reviewsColor: '#008800',
  80. sellerColor: '#cc5500',
  81. glowColor: '#ff00ff',
  82. showFrame: true,
  83. frameColor: '#cccccc',
  84. loadDelay: 10,
  85. currency: 'default',
  86. enableNotifications: false
  87. };
  88.  
  89. let options = Object.keys(defaultOptions).reduce((acc, key) => {
  90. acc[key] = GM_getValue(key, defaultOptions[key]);
  91. return acc;
  92. }, {});
  93.  
  94. const MAX_RETRIES = 2;
  95. const RETRY_DELAY = 40;
  96.  
  97. // Add Tailwind CSS
  98. GM_addStyle(GM_getResourceText("IMPORTED_CSS"));
  99.  
  100. function addCustomStyle() {
  101. let styles = [];
  102.  
  103. if (options.fontBold) {
  104. styles.push('font-bold');
  105. }
  106.  
  107. if (options.glowIntensity > 0) {
  108. styles.push(`text-shadow: 0 0 ${options.glowIntensity * 20}px ${options.glowColor}`);
  109. }
  110.  
  111. styles.push(`color: ${options.priceColor}`);
  112. styles.push(`font-family: ${options.fontFamily}`);
  113. styles.push(`font-size: ${options.fontSize}`);
  114.  
  115. const glowBlurRadius = options.glowIntensity * 10;
  116. const glowSpreadRadius = options.glowIntensity * 5;
  117. styles.push(`text-shadow: 0 0 ${glowBlurRadius}px ${options.glowColor}, 0 0 ${glowSpreadRadius}px ${options.glowColor}`);
  118.  
  119. styles.push(`color: ${options.priceColor}`);
  120. styles.push(`font-family: ${options.fontFamily}`);
  121. styles.push(`font-size: ${options.fontSize}`);
  122.  
  123. const customStyle = styles.join('; ');
  124. document.documentElement.style.setProperty('--custom-style', customStyle);
  125.  
  126. if (options.glowIntensity > 0) {
  127. styles.push(`text-shadow: 0 0 ${options.glowIntensity * 20}px ${options.glowColor}`);
  128. }
  129.  
  130. styles.push(`color: ${options.priceColor}`);
  131. styles.push(`font-family: ${options.fontFamily}`);
  132. styles.push(`font-size: ${options.fontSize}`);
  133. const isDarkMode = document.body.classList.contains('dark');
  134.  
  135. function getInvertedColor(color) {
  136. // Convert the hex color to RGB
  137. const r = parseInt(color.slice(1, 3), 16);
  138. const g = parseInt(color.slice(3, 5), 16);
  139. const b = parseInt(color.slice(5, 7), 16);
  140.  
  141. // Invert the RGB values
  142. const invR = 255 - r;
  143. const invG = 255 - g;
  144. const invB = 255 - b;
  145.  
  146. // Convert the inverted RGB values back to hex
  147. return `#${invR.toString(16).padStart(2, '0')}${invG.toString(16).padStart(2, '0')}${invB.toString(16).padStart(2, '0')}`;
  148. }
  149.  
  150. GM_addStyle(`
  151. .price-display-container {
  152. display: block;
  153. margin-top: 4px;
  154. }
  155. .price-display {
  156. display: block;
  157. font-size: ${options.fontSize};
  158. font-family: ${options.fontFamily};
  159. color: ${isDarkMode ? getInvertedColor(options.priceColor) : options.priceColor};
  160. text-shadow: 0 0 ${glowBlurRadius}px ${options.glowColor}, 0 0 ${glowSpreadRadius}px ${options.glowColor};
  161. font-weight: ${options.fontBold ? 'bold' : 'normal'};
  162. -webkit-text-stroke: 2px var(--glow-color);
  163. }
  164. .rating-display {
  165. display: block;
  166. font-size: ${options.fontSize};
  167. font-family: ${options.fontFamily};
  168. color: ${isDarkMode ? getInvertedColor(options.reviewsColor) : options.reviewsColor};
  169. text-shadow: 0 0 ${glowBlurRadius}px ${options.glowColor}, 0 0 ${glowSpreadRadius}px ${options.glowColor};
  170. font-weight: ${options.fontBold ? 'bold' : 'normal'};
  171. -webkit-text-stroke: 2px var(--glow-color);
  172. }
  173. .seller-display {
  174. display: block;
  175. font-size: ${options.fontSize};
  176. font-family: ${options.fontFamily};
  177. color: ${isDarkMode ? getInvertedColor(options.sellerColor) : options.sellerColor};
  178. text-shadow: 0 0 ${glowBlurRadius}px ${options.glowColor}, 0 0 ${glowSpreadRadius}px ${options.glowColor};
  179. font-weight: ${options.fontBold ? 'bold' : 'normal'};
  180. -webkit-text-stroke: 2px var(--glow-color);
  181. }
  182. .loading {
  183. opacity: 0.9;
  184. padding: 1px 8px;
  185. border: 1px solid ${options.frameColor};
  186. display: inline-block;
  187. }
  188.  
  189. #amazon-price-loader-settings {
  190. position: fixed;
  191. top: 50%;
  192. left: 50%;
  193. transform: translate(-50%, -50%);
  194. z-index: 10000;
  195. background: #f9f9f9;
  196. padding: 15px;
  197. border-radius: 8px;
  198. box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
  199. max-width: 400px;
  200. width: 90%;
  201. overflow: hidden;
  202. border: 1px solid #ccc;
  203. display: flex;
  204. flex-wrap: wrap;
  205. justify-content: space-between;
  206. }
  207.  
  208. .settings-overlay {
  209. position: fixed;
  210. top: 0;
  211. left: 0;
  212. right: 0;
  213. bottom: 0;
  214. background: rgba(0, 0, 0, 0.5);
  215. z-index: 9999;
  216. }
  217.  
  218. .tooltip {
  219. position: absolute;
  220. background: white;
  221. border: 1px solid black;
  222. padding: 10px;
  223. z-index: 1000;
  224. }
  225.  
  226. .setting-row {
  227. display: flex;
  228. flex-direction: column;
  229. width: 48%;
  230. margin-bottom: 10px;
  231. }
  232.  
  233. .setting-label {
  234. font-weight: bold;
  235. color: #333;
  236. margin-bottom: 5px;
  237. }
  238.  
  239. .setting-button {
  240. background-color: #007bff;
  241. color: white;
  242. border: none;
  243. border-radius: 5px;
  244. padding: 5px 10px;
  245. cursor: pointer;
  246. transition: background-color 0.3s;
  247. font-size: 14px;
  248. width: 100%;
  249. margin-top: 10px;
  250. }
  251.  
  252. .setting-button:hover {
  253. background-color: #0056b3;
  254. }
  255.  
  256. .color-picker {
  257. width: 100%;
  258. height: 30px;
  259. border: none;
  260. padding: 0;
  261. cursor: pointer;
  262. }
  263.  
  264. .font-picker {
  265. width: 100%;
  266. padding: 5px;
  267. border: 1px solid #ccc;
  268. border-radius: 5px;
  269. }
  270.  
  271. .font-size-input {
  272. width: 100%;
  273. padding: 5px;
  274. border: 1px solid #ccc;
  275. border-radius: 5px;
  276. }
  277. `);
  278. }
  279.  
  280. function createSettingsUI() {
  281. // Check if the settings overlay already exists
  282. if (document.querySelector('.settings-overlay')) {
  283. return; // Exit the function if the overlay is already present
  284. }
  285. const settingsContainer = document.createElement('div');
  286. settingsContainer.className = 'settings-container bg-white shadow-md rounded-lg p-2 w-48';
  287. const title = document.createElement('h2');
  288. title.textContent = 'Settings';
  289. title.classList.add('text-xs', 'font-bold', 'mb-2');
  290. settingsContainer.style.position = 'fixed';
  291. settingsContainer.style.top = '0px'; // Adjust the value to move the menu up or down
  292. settingsContainer.style.right = '70px'; // Adjust the value to move the menu left or right
  293.  
  294. settingsContainer.appendChild(title);
  295.  
  296. // Display Settings
  297. const displaySettings = document.createElement('div');
  298. displaySettings.classList.add('mb-2');
  299.  
  300. const fontSizeRow = document.createElement('div');
  301. fontSizeRow.classList.add('flex', 'items-center', 'justify-between');
  302.  
  303. const fontSizeLabel = document.createElement('label');
  304. fontSizeLabel.textContent = 'Size';
  305. fontSizeLabel.classList.add('text-xxs', 'font-medium');
  306.  
  307. const fontSizeInput = document.createElement('input');
  308. fontSizeInput.type = 'number';
  309. fontSizeInput.value = options.fontSize.replace('px', '');
  310. fontSizeInput.min = 8;
  311. fontSizeInput.max = 36;
  312. fontSizeInput.className = 'w-10 px-1 py-0.5 border rounded text-xxs';
  313. fontSizeInput.onchange = (e) => {
  314. options.fontSize = `${e.target.value}px`;
  315. addCustomStyle();
  316. };
  317.  
  318. fontSizeRow.appendChild(fontSizeLabel);
  319. fontSizeRow.appendChild(fontSizeInput);
  320. displaySettings.appendChild(fontSizeRow);
  321.  
  322. const fontFamilyRow = document.createElement('div');
  323. fontFamilyRow.classList.add('flex', 'items-center', 'justify-between');
  324.  
  325. const fontFamilyLabel = document.createElement('label');
  326. fontFamilyLabel.textContent = 'Font';
  327. fontFamilyLabel.classList.add('text-xxs', 'font-medium');
  328.  
  329. const fontFamilyInput = document.createElement('select');
  330. fontFamilyInput.className = 'w-full px-1 py-0.5 border rounded text-xxs';
  331. fontFamilyInput.onchange = (e) => {
  332. options.fontFamily = e.target.value;
  333. addCustomStyle();
  334. };
  335.  
  336. const fonts = [
  337. 'Arial, sans-serif',
  338. 'Times New Roman, serif',
  339. 'Verdana, sans-serif',
  340. 'Georgia, serif',
  341. 'Courier New, monospace',
  342. 'Roboto, sans-serif',
  343. 'Open Sans, sans-serif',
  344. 'Lato, sans-serif',
  345. 'Montserrat, sans-serif',
  346. 'Poppins, sans-serif'
  347. ];
  348. fonts.forEach(font => {
  349. const option = document.createElement('option');
  350. option.value = font;
  351. option.textContent = font;
  352. option.selected = options.fontFamily === font;
  353. fontFamilyInput.appendChild(option);
  354. });
  355.  
  356. // Bold functionality
  357. const fontBoldRow = document.createElement('div');
  358. fontBoldRow.classList.add('flex', 'items-center', 'justify-between');
  359.  
  360. const fontBoldLabel = document.createElement('label');
  361. fontBoldLabel.textContent = 'Bold';
  362. fontBoldLabel.classList.add('text-xxs', 'font-medium');
  363.  
  364. const fontBoldCheckbox = document.createElement('input');
  365. fontBoldCheckbox.type = 'checkbox';
  366. fontBoldCheckbox.checked = options.fontBold;
  367. fontBoldCheckbox.className = 'w-4 h-4 border rounded';
  368. fontBoldCheckbox.onchange = (e) => {
  369. options.fontBold = e.target.checked;
  370. addCustomStyle();
  371. };
  372.  
  373. fontBoldRow.appendChild(fontBoldLabel);
  374. fontBoldRow.appendChild(fontBoldCheckbox);
  375. displaySettings.appendChild(fontBoldRow);
  376.  
  377. fontFamilyRow.appendChild(fontFamilyLabel);
  378. fontFamilyRow.appendChild(fontFamilyInput);
  379. displaySettings.appendChild(fontFamilyRow);
  380. displaySettings.appendChild(fontBoldCheckbox);
  381. displaySettings.appendChild(fontBoldLabel);
  382.  
  383. settingsContainer.appendChild(displaySettings);
  384.  
  385. // Color Settings
  386. const colorSettings = document.createElement('div');
  387. colorSettings.classList.add('mb-2');
  388.  
  389. const priceColorRow = document.createElement('div');
  390. priceColorRow.classList.add('flex', 'items-center', 'justify-between');
  391.  
  392. const priceColorLabel = document.createElement('label');
  393. priceColorLabel.textContent = 'Price';
  394. priceColorLabel.classList.add('text-xxs', 'font-medium');
  395.  
  396. const priceColorInput = document.createElement('input');
  397. priceColorInput.type = 'color';
  398. priceColorInput.value = options.priceColor;
  399. priceColorInput.className = 'w-5 h-5 border rounded-full';
  400. priceColorInput.onchange = (e) => {
  401. options.priceColor = e.target.value;
  402. addCustomStyle();
  403. };
  404.  
  405. priceColorRow.appendChild(priceColorLabel);
  406. priceColorRow.appendChild(priceColorInput);
  407. colorSettings.appendChild(priceColorRow);
  408.  
  409. const reviewsColorRow = document.createElement('div');
  410. reviewsColorRow.classList.add('flex', 'items-center', 'justify-between');
  411.  
  412. const reviewsColorLabel = document.createElement('label');
  413. reviewsColorLabel.textContent = 'Reviews';
  414. reviewsColorLabel.classList.add('text-xxs', 'font-medium');
  415.  
  416. const reviewsColorInput = document.createElement('input');
  417. reviewsColorInput.type = 'color';
  418. reviewsColorInput.value = options.reviewsColor;
  419. reviewsColorInput.className = 'w-5 h-5 border rounded-full';
  420. reviewsColorInput.onchange = (e) => {
  421. options.reviewsColor = e.target.value;
  422. addCustomStyle();
  423. };
  424.  
  425. reviewsColorRow.appendChild(reviewsColorLabel);
  426. reviewsColorRow.appendChild(reviewsColorInput);
  427. colorSettings.appendChild(reviewsColorRow);
  428.  
  429. const sellerColorRow = document.createElement('div');
  430. sellerColorRow.classList.add('flex', 'items-center', 'justify-between');
  431.  
  432. const sellerColorLabel = document.createElement('label');
  433. sellerColorLabel.textContent = 'Seller';
  434. sellerColorLabel.classList.add('text-xxs', 'font-medium');
  435.  
  436. const sellerColorInput = document.createElement('input');
  437. sellerColorInput.type = 'color';
  438. sellerColorInput.value = options.sellerColor;
  439. sellerColorInput.className = 'w-5 h-5 border rounded-full';
  440. sellerColorInput.onchange = (e) => {
  441. options.sellerColor = e.target.value;
  442. addCustomStyle();
  443. };
  444.  
  445. sellerColorRow.appendChild(sellerColorLabel);
  446. sellerColorRow.appendChild(sellerColorInput);
  447. colorSettings.appendChild(sellerColorRow);
  448.  
  449. settingsContainer.appendChild(colorSettings);
  450.  
  451. // Global Glow Settings
  452. const glowSettings = document.createElement('div');
  453. glowSettings.classList.add('mb-2');
  454.  
  455. const glowColorRow = document.createElement('div');
  456. glowColorRow.classList.add('flex', 'items-center', 'justify-between');
  457.  
  458. const glowColorLabel = document.createElement('label');
  459. glowColorLabel.textContent = 'Glow Color';
  460. glowColorLabel.classList.add('text-xxs', 'font-medium');
  461.  
  462. const glowColorInput = document.createElement('input');
  463. glowColorInput.type = 'color';
  464. glowColorInput.value = options.glowColor;
  465. glowColorInput.className = 'w-5 h-5 border rounded-full';
  466. glowColorInput.onchange = (e) => {
  467. options.glowColor = e.target.value;
  468. GM_setValue('glowColor', options.glowColor); // Save to memory
  469. addCustomStyle();
  470. };
  471. const glowColor = options.glowColor || '#ff00ff';
  472. const glowIntensity = options.glowIntensity || 0.5;
  473. const isBold = options.isBold ? 'bold' : 'normal';
  474. const fontColor = options.fontColor || '#000000';
  475.  
  476. const style = document.createElement('style');
  477. style.innerHTML = `
  478. .price-display, .rating-display, .seller-display {
  479. text-shadow: 0 0 ${glowIntensity * 10}px ${glowColor}; /* Apply glow */
  480. color: ${fontColor}; /* Apply font color */
  481. font-weight: ${isBold}; /* Apply bold if set */
  482. }
  483. `;
  484. document.head.appendChild(style);
  485. addCustomStyle();
  486.  
  487. const glowIntensityInput = document.createElement('input');
  488. glowIntensityInput.type = 'range';
  489. glowIntensityInput.min = 0;
  490. glowIntensityInput.max = 1;
  491. glowIntensityInput.step = 0.01;
  492. glowIntensityInput.value = options.glowIntensity;
  493. glowIntensityInput.className = 'w-full';
  494. glowIntensityInput.onchange = (e) => {
  495. options.glowIntensity = parseFloat(e.target.value);
  496. GM_setValue('glowIntensity', options.glowIntensity); // Save to memory
  497. addCustomStyle();
  498. };
  499.  
  500. const glowIntensityLabel = document.createElement('label');
  501. glowIntensityLabel.textContent = 'Glow Intensity';
  502. glowIntensityLabel.classList.add('text-xxs', 'font-medium');
  503.  
  504. glowColorRow.appendChild(glowColorLabel);
  505. glowColorRow.appendChild(glowColorInput);
  506. glowSettings.appendChild(glowColorRow);
  507. glowSettings.appendChild(glowIntensityLabel);
  508. glowSettings.appendChild(glowIntensityInput);
  509.  
  510. settingsContainer.appendChild(glowSettings);
  511.  
  512. const buttonsContainer = document.createElement('div');
  513. buttonsContainer.classList.add('flex', 'justify-end', 'space-x-2');
  514.  
  515. const saveButton = document.createElement('button');
  516. saveButton.className = 'bg-blue-500 hover:bg-blue-600 text-white font-medium py-0.5 px-2 rounded text-xxs';
  517. saveButton.textContent = 'Close';
  518. saveButton.onclick = () => {
  519. options.fontSize = `${fontSizeInput.value}px`;
  520. options.fontFamily = fontFamilyInput.value;
  521. options.fontBold = fontBoldCheckbox.checked;
  522. options.priceColor = priceColorInput.value;
  523. options.reviewsColor = reviewsColorInput.value;
  524. options.sellerColor = sellerColorInput.value;
  525. options.glowColor = GM_getValue('glowColor', '#ff00ff'); // Default glow color
  526. options.glowIntensity = GM_getValue('glowIntensity', 0.5); // Default glow intensity
  527. options.isBold = GM_getValue('isBold', false); // Default bold setting (false = not bold)
  528. options.fontColor = GM_getValue('fontColor', '#000000'); // Default font color
  529.  
  530.  
  531. Object.keys(options).forEach(key => {
  532. GM_setValue(key, options[key]);
  533. });
  534.  
  535. addCustomStyle();
  536. document.body.removeChild(settingsOverlay);
  537. };
  538.  
  539.  
  540.  
  541. buttonsContainer.appendChild(saveButton);
  542. settingsContainer.appendChild(buttonsContainer);
  543.  
  544. const settingsOverlay = document.createElement('div');
  545. settingsOverlay.className = 'settings-overlay';
  546. settingsOverlay.appendChild(settingsContainer);
  547. settingsOverlay.style.backgroundColor = 'transparent'; // Ensure it's transparent
  548. document.body.appendChild(settingsOverlay);
  549. }
  550.  
  551. function getCurrencySymbol(currency) {
  552. const symbols = {
  553. 'USD': '$ ',
  554. 'EUR': '€ ',
  555. 'GBP': '£ ',
  556. 'NIS': '₪ ',
  557. 'JPY': '¥ '
  558. };
  559. return symbols[currency] || '';
  560. }
  561.  
  562. async function fetchPriceAndRating(url, retries = 0) {
  563. try {
  564. const response = await new Promise((resolve, reject) => {
  565. GM_xmlhttpRequest({
  566. method: 'GET',
  567. url: url,
  568. timeout: 5000,
  569. onload: resolve,
  570. onerror: reject,
  571. ontimeout: reject
  572. });
  573. });
  574.  
  575. const parser = new DOMParser();
  576. const doc = parser.parseFromString(response.responseText, 'text/html');
  577. const priceElement = doc.querySelector('.a-price .a-offscreen');
  578. let price = priceElement ? priceElement.textContent.trim() : 'No price found';
  579.  
  580. if (options.currency !== 'default') {
  581. price = `${getCurrencySymbol(options.currency)} ${price.replace(/[^\d.]/g, '')}`;
  582. }
  583.  
  584. const ratingElement = doc.querySelector('.a-icon-alt');
  585. let rating = ratingElement ? ratingElement.textContent : 'No rating found';
  586. const numberOfRatingsElement = doc.querySelector('#acrCustomerReviewText');
  587. let numberOfRatings = numberOfRatingsElement ? numberOfRatingsElement.textContent : '0 ratings';
  588.  
  589. return { price, rating, numberOfRatings };
  590. } catch (error) {
  591. if (retries < MAX_RETRIES) {
  592. await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
  593. return fetchPriceAndRating(url, retries + 1);
  594. }
  595. return { price: 'Error fetching price', rating: 'Error fetching rating', numberOfRatings: 'Error fetching ratings' };
  596. }
  597. }
  598.  
  599. async function fetchPriceRatingAndSeller(url, retries = 0) {
  600. try {
  601. const response = await new Promise((resolve, reject) => {
  602. GM_xmlhttpRequest({
  603. method: 'GET',
  604. url: url,
  605. timeout: 5000,
  606. onload: resolve,
  607. onerror: reject,
  608. ontimeout: reject
  609. });
  610. });
  611.  
  612. const parser = new DOMParser();
  613. const doc = parser.parseFromString(response.responseText, 'text/html');
  614.  
  615. const priceElement = doc.querySelector('.a-price .a-offscreen');
  616. let price = priceElement ? priceElement.textContent.trim() : 'No price found';
  617.  
  618. if (options.currency !== 'default') {
  619. price = `${getCurrencySymbol(options.currency)} ${price.replace(/[^\d.]/g, '')}`;
  620. }
  621.  
  622. const ratingElement = doc.querySelector('.a-icon-alt');
  623. let rating = ratingElement ? ratingElement.textContent : 'No rating found';
  624.  
  625. const numberOfRatingsElement = doc.querySelector('#acrCustomerReviewText');
  626. let numberOfRatings = numberOfRatingsElement ? numberOfRatingsElement.textContent : '0 ratings';
  627.  
  628. const sellerElement = doc.querySelector('#sellerProfileTriggerId');
  629. let sellerName = sellerElement ? sellerElement.textContent.trim() : 'Seller info not available';
  630.  
  631. return { price, rating, numberOfRatings, sellerName };
  632. } catch (error) {
  633. if (retries < MAX_RETRIES) {
  634. await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
  635. return fetchPriceRatingAndSeller(url, retries + 1);
  636. }
  637. return { price: 'Error fetching price', rating: 'Error fetching rating', numberOfRatings: 'Error fetching ratings', sellerName: 'Error fetching seller' };
  638. }
  639. }
  640.  
  641. async function displayPriceRatingAndSeller(link) {
  642. const existingDisplay = link.nextElementSibling;
  643. if (existingDisplay && existingDisplay.classList.contains('price-display-container')) return;
  644.  
  645. const parentContainer = document.createElement('div');
  646. parentContainer.className = 'price-link-container';
  647. link.parentNode.insertBefore(parentContainer, link);
  648. parentContainer.appendChild(link);
  649.  
  650. const container = document.createElement('div');
  651. container.className = 'price-display-container loading';
  652. container.style.border = options.showFrame ? `0.1px solid ${options.frameColor}` : 'none';
  653. container.style.padding = '0.1em';
  654.  
  655. const priceDisplay = document.createElement('div');
  656. priceDisplay.className = 'price-display';
  657. priceDisplay.textContent = 'Loading price...';
  658.  
  659. const ratingDisplay = document.createElement('div');
  660. ratingDisplay.className = 'rating-display';
  661. ratingDisplay.textContent = 'Loading rating...';
  662.  
  663. const sellerDisplay = document.createElement('div');
  664. sellerDisplay.className = 'seller-display';
  665. sellerDisplay.textContent = 'Loading seller...';
  666.  
  667. container.appendChild(priceDisplay);
  668. container.appendChild(ratingDisplay);
  669. container.appendChild(sellerDisplay);
  670. parentContainer.appendChild(container);
  671.  
  672. try {
  673. await new Promise(resolve => setTimeout(resolve, options.loadDelay));
  674. const { price, rating, numberOfRatings, sellerName } = await fetchPriceRatingAndSeller(link.href);
  675.  
  676. priceDisplay.textContent = price;
  677. ratingDisplay.textContent = `${rating} (${numberOfRatings})`;
  678. sellerDisplay.textContent = `Seller: ${sellerName}`;
  679.  
  680. priceDisplay.classList.remove('loading');
  681. ratingDisplay.classList.remove('loading');
  682. sellerDisplay.classList.remove('loading');
  683.  
  684. if (options.enableNotifications && price !== 'No price found' && price !== 'Error fetching price') {
  685. alert(`Price fetched: ${price}`);
  686. }
  687. } catch (error) {
  688. console.error('Failed to fetch price and rating', error);
  689. }
  690. }
  691.  
  692. function setupPriceAndRatingDisplay() {
  693. const productLinks = document.querySelectorAll('a[href*="/dp/"]');
  694. productLinks.forEach(link => displayPriceRatingAndSeller(link));
  695. }
  696.  
  697. addCustomStyle();
  698. setupPriceAndRatingDisplay();
  699.  
  700. // Register the command for settings
  701. GM_registerMenuCommand('Amazon Price Loader Settings', createSettingsUI);
  702.  
  703. // MutationObserver to monitor changes on the page
  704. const observer = new MutationObserver(setupPriceAndRatingDisplay);
  705. observer.observe(document.body, { childList: true, subtree: true });
  706. })();