BasicNullification

Amazon Vine: Order Status → Vine Reviews

Aug 25th, 2025 (edited)
17
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Amazon Vine: Order Status → Vine Reviews
  3. // @namespace    https://www.reddit.com/r/VineHelper/comments/1mz5jr6/comment/nagt886/
  4. // @version      1.0.0
  5. // @description  Capture delivery status from order history and display it in Vine Reviews table.
  6. // @author       u/Ball_Catcher
  7. // @license      MIT
  8. // @match        https://www.amazon.com/your-orders*
  9. // @match        https://www.amazon.com/*/order-history*
  10. // @match        https://www.amazon.com/vine/vine-reviews*
  11. // @grant        GM_getValue
  12. // @grant        GM_setValue
  13. // @run-at       document-idle
  14. // ==/UserScript==
  15.  
  16. (function () {
  17.     "use strict";
  18.  
  19.     // ---------- Storage helpers (GM_* wrappers per-ASIN) ----------
  20.     async function saveAsinData(asin, data) {
  21.         if (!asin) return;
  22.         const key = `asin:${asin}`;
  23.         await GM_setValue(key, data);
  24.     }
  25.  
  26.     async function getAsinData(asin) {
  27.         if (!asin) return null;
  28.         const key = `asin:${asin}`;
  29.         return (await GM_getValue(key)) || null;
  30.     }
  31.  
  32.     // ---------- Page detection ----------
  33.     function isOrderHistory() {
  34.         const p = location.pathname;
  35.         return /\/your-orders(?:\/|$)/.test(p) || /\/order-history(?:\/|$)/.test(p);
  36.     }
  37.  
  38.     function isVineReviews() {
  39.         return /\/vine\/vine-reviews(?:\/|$)/.test(location.pathname);
  40.     }
  41.  
  42.     // ---------- Core features (converted from your module code) ----------
  43.     async function identifyReceivedOrders() {
  44.         if (!isOrderHistory()) return;
  45.  
  46.         const orderCards = document.querySelectorAll(".order-card__list");
  47.         if (!orderCards.length) return;
  48.  
  49.         orderCards.forEach(async (card) => {
  50.             const deliveryEl = card.querySelector(".delivery-box__primary-text");
  51.             const deliveryStatus = deliveryEl ? deliveryEl.innerText.trim() : null;
  52.  
  53.             // Find review link
  54.             const reviewAnchor = card.querySelector(
  55.                 'a[href*="/review/review-your-purchases?asins="]'
  56.             );
  57.             if (!reviewAnchor) return;
  58.  
  59.             const href = reviewAnchor.getAttribute("href") || "";
  60.             const asinMatch = href.match(/asins=([^&]+)/);
  61.             if (!asinMatch) return;
  62.             const productAsin = asinMatch[1];
  63.  
  64.             // Find tracking link
  65.             const trackAnchor = card.querySelector(
  66.                 'a[href*="/gp/your-account/ship-track"]'
  67.             );
  68.             const trackPackageLink = trackAnchor
  69.             ? trackAnchor.getAttribute("href")
  70.             : null;
  71.  
  72.             const data = {
  73.                 deliveryStatus,
  74.                 trackPackageLink,
  75.                 productAsin,
  76.             };
  77.  
  78.             await saveAsinData(productAsin, data);
  79.             console.debug(
  80.                 `[VineHelper] Saved order-history data for ASIN ${productAsin}`,
  81.                 data
  82.             );
  83.         });
  84.     }
  85.  
  86.     async function insertOrderDeliveryStatus() {
  87.         if (!isVineReviews()) return;
  88.  
  89.         const reviewsTable = document.querySelector(".vvp-reviews-table");
  90.         if (!reviewsTable) return;
  91.  
  92.         // --- Insert header column ---
  93.         const reviewHeading = reviewsTable.querySelector(
  94.             "#vvp-reviews-table--review-content-heading"
  95.         );
  96.         const existingDeliveryHeading = reviewsTable.querySelector(
  97.             ".vvp-reviews-table--delivery-status-heading"
  98.         );
  99.  
  100.         if (reviewHeading && !existingDeliveryHeading) {
  101.             const th = document.createElement("th");
  102.             th.className = "vvp-reviews-table--delivery-status-heading";
  103.             th.textContent = "Delivery status";
  104.  
  105.             const actionsHeading = reviewsTable.querySelector(
  106.                 "th.vvp-reviews-table--actions-col"
  107.             );
  108.             if (actionsHeading) {
  109.                 actionsHeading.insertAdjacentElement("beforebegin", th);
  110.             } else {
  111.                 reviewHeading.insertAdjacentElement("afterend", th); // fallback
  112.             }
  113.         }
  114.  
  115.         // --- Insert row data ---
  116.         const rows = reviewsTable.querySelectorAll(".vvp-reviews-table--row");
  117.  
  118.         rows.forEach(async (row) => {
  119.             // Avoid duplicating cells on re-runs
  120.             if (row.querySelector(".vvp-reviews-table--delivery-status-col")) return;
  121.  
  122.             let asin = null;
  123.  
  124.             // Try to get ASIN from product detail link
  125.             const detailLink = row.querySelector(
  126.                 'td.vvp-reviews-table--text-col #vvp-reviews-product-detail-page-link'
  127.             );
  128.             if (detailLink) {
  129.                 const href = detailLink.getAttribute("href") || "";
  130.                 const asinMatch = href.match(/\/dp\/([^/?]+)/);
  131.                 if (asinMatch) asin = asinMatch[1];
  132.             }
  133.  
  134.             // Fallback: check innerText of 2nd <td>
  135.             if (!asin) {
  136.                 const tds = row.querySelectorAll("td");
  137.                 if (tds.length >= 2) {
  138.                     const text = tds[1].innerText.trim();
  139.                     const asinMatch = text.match(/\b[A-Z0-9]{10}\b/); // typical ASIN format
  140.                     if (asinMatch) asin = asinMatch[0];
  141.                 }
  142.             }
  143.  
  144.             const insertCell = (contentCb) => {
  145.                 const td = document.createElement("td");
  146.                 td.className = "vvp-reviews-table--delivery-status-col";
  147.                 contentCb(td);
  148.  
  149.                 const actionsCol = row.querySelector("td.vvp-reviews-table--actions-col");
  150.                 if (actionsCol && actionsCol.parentNode) {
  151.                     actionsCol.insertAdjacentElement("beforebegin", td);
  152.                 }
  153.             };
  154.  
  155.             // If no ASIN at all, just insert Status Unknown immediately
  156.             if (!asin) {
  157.                 insertCell((td) => {
  158.                     td.textContent = "Status Unknown";
  159.                     td.style.fontStyle = "italic";
  160.                     td.style.color = "#777";
  161.                 });
  162.                 return;
  163.             }
  164.  
  165.             // Lookup ASIN in storage
  166.             const stored = await getAsinData(asin);
  167.             let deliveryStatus = "Status Unknown";
  168.             let trackPackageLink = null;
  169.  
  170.             if (stored) {
  171.                 if (stored.deliveryStatus) deliveryStatus = stored.deliveryStatus;
  172.                 if (stored.trackPackageLink) {
  173.                     // normalize relative to full URL
  174.                     trackPackageLink = new URL(stored.trackPackageLink, location.origin).href;
  175.                 }
  176.             }
  177.  
  178.             insertCell((td) => {
  179.                 if (deliveryStatus.includes("Delivered")) {
  180.                     td.textContent = deliveryStatus;
  181.                     td.style.fontWeight = "bold";
  182.                     td.style.color = "#00796b";
  183.                 } else if (deliveryStatus === "Status Unknown") {
  184.                     td.textContent = deliveryStatus;
  185.                     td.style.fontStyle = "italic";
  186.                     td.style.color = "#777";
  187.                 } else {
  188.                     if (trackPackageLink) {
  189.                         const a = document.createElement("a");
  190.                         a.href = trackPackageLink;
  191.                         a.target = "_blank";
  192.                         a.rel = "noopener noreferrer";
  193.                         a.textContent = deliveryStatus;
  194.                         td.appendChild(a);
  195.                     } else {
  196.                         td.textContent = deliveryStatus; // fallback if no link available
  197.                     }
  198.                 }
  199.             });
  200.         });
  201.     }
  202.  
  203.     // ---------- Observers to handle dynamic loads ----------
  204.     const moOpts = { childList: true, subtree: true };
  205.  
  206.     function observeForOrders() {
  207.         if (!isOrderHistory()) return;
  208.         // Initial pass
  209.         identifyReceivedOrders();
  210.         // Re-check as cards load/expand
  211.         const root = document.body;
  212.         const mo = new MutationObserver(() => {
  213.             // Throttle lightly with requestAnimationFrame
  214.             window.requestAnimationFrame(identifyReceivedOrders);
  215.         });
  216.         mo.observe(root, moOpts);
  217.     }
  218.  
  219.     function observeForVineReviews() {
  220.         if (!isVineReviews()) return;
  221.         // Initial pass
  222.         insertOrderDeliveryStatus();
  223.         // Re-check when table updates due to filters/pagination
  224.         const root = document.querySelector(".vvp-reviews-table") || document.body;
  225.         const mo = new MutationObserver(() => {
  226.             window.requestAnimationFrame(insertOrderDeliveryStatus);
  227.         });
  228.         mo.observe(root, moOpts);
  229.     }
  230.  
  231.     // ---------- Bootstrap ----------
  232.     if (isOrderHistory()) observeForOrders();
  233.     if (isVineReviews()) observeForVineReviews();
  234.  
  235. })();
  236.  
Tags: AmazonVine
Advertisement
Add Comment
Please, Sign In to add comment