// ==UserScript== // @name Wishlist Steal Deal Checker // @namespace https://greasyfork.org/en/users/1019658-aayush-dutt // @version 0.11 // @description A user script to check if the wishlist item has best price // @author aayushdutt // @match https://www.amazon.com/hz/wishlist* // @match https://www.amazon.in/hz/wishlist* // @match https://www.amazon.de/hz/wishlist/ls* // @match https://www.amazon.fr/hz/wishlist/ls* // @match https://www.amazon.it/hz/wishlist/ls* // @match https://www.amazon.es/hz/wishlist/ls* // @match https://www.amazon.nl/hz/wishlist/ls* // @match https://www.amazon.se/hz/wishlist/ls* // @match https://www.amazon.co.jp/hz/wishlist/ls* // @match https://www.amazon.co.uk/hz/wishlist/ls* // @match https://www.amazon.com.mx/hz/wishlist/ls* // @match https://www.amazon.com.au/hz/wishlist/ls* // @match https://www.amazon.com.be/hz/wishlist/ls* // @grant none // @link https://greasyfork.org/en/scripts/468955-wishlist-steal-deal-checker // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/468955/Wishlist%20Steal%20Deal%20Checker.user.js // @updateURL https://update.greasyfork.icu/scripts/468955/Wishlist%20Steal%20Deal%20Checker.meta.js // ==/UserScript== (function () { "use strict"; /** * @returns {HTMLElement[]} An array of nodes. */ const getAllNodes = () => { return Array.from(document.querySelectorAll("ul#g-items > li[data-id]")); }; /** * Parses the nodes and extracts relevant information. * @returns {Object[]} An array of parsed node objects. */ const parseNodes = () => { /** * @param {HTMLElement} node - The node element. * @returns {number} The price value. */ const getPrice = (node) => { const priceStr = node.getAttribute("data-price"); const price = parseFloat(priceStr); return isNaN(price) ? null : price; }; /** * @param {HTMLElement} node - The node element. * @returns {HTMLElement|null} The comment node or null if not found. */ const getCommentNode = (node) => { return node.querySelector( "div.awl-ul-keyword-item-subtitle span.a-color-secondary.awl-ul-keyword-item-truncated-text" ); }; /** * @param {HTMLElement|null} node - The comment node. * @returns {number[]} An array of comment prices. */ const getCommentPrices = (node) => { const commentText = node ? node.innerText.trim() : ""; if (!commentText) return []; return commentText .split(/[\s,]+/) .map((e) => parseFloat(e)) .filter((e) => !isNaN(e)); }; /** * Checks if the given price is the best price among the comment prices. * @param {number} price - The price to compare. * @param {number[]} commentPrices - An array of comment prices. * @returns {number} -1 if bad price, 0 if equal to prev best, 1 if the price beats all comment prices */ const getIsBestPrice = (price, commentPrices) => { if (price === null || isNaN(price) || !commentPrices.length) return null; const minCommentPrice = Math.min(...commentPrices); if (price === minCommentPrice) return 0; return price < minCommentPrice ? 1 : -1; }; const nodes = getAllNodes(); return nodes.map((node) => { const price = getPrice(node); const commentNode = getCommentNode(node); const commentPrices = getCommentPrices(commentNode); const priceComparison = getIsBestPrice(price, commentPrices); return { node, price, commentPrices, priceComparison, commentNode, }; }); }; /** * @returns {string} */ const getCsrf = () => { const csrfInput = document.querySelector( 'input[name="anti-csrftoken-a2z"]' ); if (csrfInput) { return csrfInput.value; } else { console.error("CSRF token not found"); return ""; } }; const csrf = getCsrf(); const updateComment = async (node, updatedComment) => { const actionParams = JSON.parse( node.getAttribute("data-reposition-action-params") ); const body = JSON.stringify({ comment: updatedComment, desiredQuantity: 1, isJson: true, listType: "wishlist", priority: 0, hasQuantity: 0, viewType: "list", itemID: actionParams.itemExternalId, listID: node.getAttribute("data-id"), sid: actionParams.sid, }); const resp = await fetch("/hz/wishlist/updatecqp", { headers: { "content-type": "application/json", "anti-csrftoken-a2z": csrf, }, body, method: "POST", }); console.log(`Updated comment to: ${updatedComment}`, resp); if (!resp.ok) { throw new Error(resp.status); } }; const addUpdateButton = (item) => { if ( !item.price || (item.commentPrices && item.commentPrices.length && item.commentPrices.includes(item.price)) ) return; const handleUpdateButtonClick = async () => { const updatedPrices = [...item.commentPrices, item.price]; updatedPrices.sort((a, b) => b - a); const updatedComment = updatedPrices.join(", "); try { await updateComment(item.node, updatedComment); item.commentNode.innerText = updatedComment; } catch (e) { const errMsg = `Error updating comment to: ${updatedComment}, err=${e.message}`; console.error(errMsg); window.alert(errMsg); } }; const containerEl = item.node.querySelector( "div.awl-ul-keyword-item-subtitle" ); if (!containerEl) return; const existingButton = containerEl.querySelector(".add-current-price"); if (existingButton) return; const newButton = document.createElement("button"); newButton.innerText = "Add Current Price"; newButton.className = "a-button a-button-base a-button-small add-current-price"; newButton.style.padding = "2px 10px"; newButton.style.width = "180px"; newButton.onclick = handleUpdateButtonClick; containerEl.appendChild(newButton); }; /** * Updates the style of the best price nodes. * @param {Object[]} wishlistItems */ const updateBestNodes = (wishlistItems) => { wishlistItems.forEach((item) => { if (item.node.getAttribute("data-isdone") === "1") return; addUpdateButton(item); if (item.priceComparison === 0) { item.node.style.background = "#f1fffe"; } else if (item.priceComparison === 1) { item.node.style.background = "#d0fbe4"; } if (item.priceComparison < 0) { item.node.style.display = "none"; } item.node.setAttribute("data-isdone", "1"); }); }; /** * @param {MutationRecord[]} mutationList * @param {MutationObserver} observer */ const callback = (mutationList, observer) => { console.log("[Wishlist Steal Deal Checker] list changed"); const wishlistItems = parseNodes(); updateBestNodes(wishlistItems); }; callback([], null); const targetNode = document.getElementById("g-items"); if (!targetNode) { console.error("Could not find #g-items element"); return; } const config = { childList: true }; const observer = new MutationObserver(callback); observer.observe(targetNode, config); })();