// ==UserScript== // @name Amazon Video ASIN Display // @namespace sac@libidgel.com // @version 0.3.7 // @description Show unique ASINs for episodes and movies/seasons on Amazon Prime Video // @author ReiDoBrega // @license MIT // @match https://www.amazon.com/* // @match https://www.amazon.co.uk/* // @match https://www.amazon.de/* // @match https://www.amazon.co.jp/* // @match https://www.primevideo.com/* // @run-at document-idle // @grant none // @downloadURL none // ==/UserScript== (function () { "use strict"; // Add styles for ASIN display and pop-up let style = document.createElement("style"); style.textContent = ` .x-asin-container { margin: 0.5em 0 1em 0; } .x-asin-item, .x-episode-asin { color: #1399FF; /* Green color */ cursor: pointer; margin: 5px 0; } .x-copy-popup { position: fixed; bottom: 20px; right: 20px; background-color: rgba(0, 0, 0, 0); /* Transparent background */ color: #1399FF; /* Green text */ padding: 10px 20px; border-radius: 5px; font-family: Arial, sans-serif; font-size: 14px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0); z-index: 1000; animation: fadeInOut 2.5s ease-in-out; } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } `; document.head.appendChild(style); // Function to extract ASIN from URL function extractASINFromURL() { const url = window.location.href; const asinRegex = /\/gp\/video\/detail\/([A-Z0-9]{10})/; const match = url.match(asinRegex); return match ? match[1] : null; } // Function to find and display unique ASINs function findUniqueASINs() { // Extract ASIN from URL first const urlASIN = extractASINFromURL(); if (urlASIN) { return { urlASIN }; } // Object to store one unique ASIN/ID for each type let uniqueIds = {}; // List of ID patterns to find const idPatterns = [ { name: 'titleID', regex: /"titleID":"([^"]+)"/ }, // { // name: 'pageTypeId', // regex: /pageTypeId: "([^"]+)"/ // }, // { // name: 'pageTitleId', // regex: /"pageTitleId":"([^"]+)"/ // }, // { // name: 'catalogId', // regex: /catalogId":"([^"]+)"/ // } ]; // Search through patterns idPatterns.forEach(pattern => { let match = document.body.innerHTML.match(pattern.regex); if (match && match[1]) { uniqueIds[pattern.name] = match[1]; } }); return uniqueIds; } // Function to find ASINs from JSON response function findUniqueASINsFromJSON(jsonData) { let uniqueIds = {}; // Comprehensive search paths for ASINs const searchPaths = [ { name: 'titleId', paths: [ ['titleID'], ['page', 0, 'assembly', 'body', 0, 'args', 'titleID'], ['titleId'], ['detail', 'titleId'], ['data', 'titleId'] ]}, ]; // Deep object traversal function function traverseObject(obj, paths) { for (let pathSet of paths) { try { let value = obj; for (let key of pathSet) { value = value[key]; if (value === undefined) break; } if (value && typeof value === 'string' && value.trim() !== '') { return value; } } catch (e) { // Silently ignore traversal errors } } return null; } // Search through all possible paths searchPaths.forEach(({ name, paths }) => { const value = traverseObject(jsonData, paths); if (value) { uniqueIds[name] = value; console.log(`[ASIN Display] Found ${name} in JSON: ${value}`); } }); return uniqueIds; } // Function to add episode ASINs function addEpisodeASINs() { try { document.querySelectorAll("[id^='selector-'], [id^='av-episode-expand-toggle-']").forEach(el => { // Skip if ASIN already added if (el.parentNode.querySelector(".x-episode-asin")) { return; } // Extract ASIN from the element ID let asin = el.id.replace(/^(?:selector|av-episode-expand-toggle)-/, ""); // Create ASIN element let asinEl = document.createElement("div"); asinEl.className = "x-episode-asin"; asinEl.textContent = asin; asinEl.addEventListener("click", () => copyToClipboard(asin)); // Insert ASIN element after the episode title let epTitle = el.parentNode.querySelector("[data-automation-id^='ep-title']"); if (epTitle) { epTitle.parentNode.insertBefore(asinEl, epTitle.nextSibling); } }); return true; // Episode ASINs added successfully } catch (e) { console.error("ASIN Display - Error in addEpisodeASINs:", e); return false; // Error occurred } } // Function to add ASIN display function addASINDisplay(uniqueIds = null) { try { // If no IDs provided, find them from HTML if (!uniqueIds) { uniqueIds = findUniqueASINs(); } // Remove existing ASIN containers document.querySelectorAll(".x-asin-container").forEach(el => el.remove()); // If no IDs found, return if (Object.keys(uniqueIds).length === 0) { console.log("ASIN Display: No ASINs found"); return false; } // Create ASIN container let asinContainer = document.createElement("div"); asinContainer.className = "x-asin-container"; // Add each unique ID as a clickable element Object.entries(uniqueIds).forEach(([type, id]) => { let asinEl = document.createElement("div"); asinEl.className = "_1jWggM v2uvTa fbl-btn _2Pw7le"; asinEl.textContent = id; asinEl.addEventListener("click", () => copyToClipboard(id)); asinContainer.appendChild(asinEl); }); // Insert the ASIN container after the synopsis let after = document.querySelector(".dv-dp-node-synopsis, .av-synopsis"); if (!after) { console.log("ASIN Display: Could not find element to insert after"); return false; } after.parentNode.insertBefore(asinContainer, after.nextSibling); return true; } catch (e) { console.error("ASIN Display - Error in addASINDisplay:", e); return false; } } // Function to copy text to clipboard and show pop-up function copyToClipboard(text) { const input = document.createElement("textarea"); input.value = text; document.body.appendChild(input); input.select(); document.execCommand("copy"); document.body.removeChild(input); // Show pop-up const popup = document.createElement("div"); popup.className = "x-copy-popup"; popup.textContent = `Copied: ${text}`; document.body.appendChild(popup); // Remove pop-up after 1.5 seconds setTimeout(() => { popup.remove(); }, 1500); } // Intercept fetch requests for JSON responses const originalFetch = window.fetch; window.fetch = function(...args) { const [url] = args; // Check if the URL matches the detail page pattern if (url.includes('/detail/') && url.includes('primevideo.com')) { return originalFetch.apply(this, args).then(response => { try { const contentType = response.headers.get('content-type'); if (contentType?.includes('application/json')) { return response.clone().json().then(jsonResponse => { // Find unique IDs using comprehensive search paths const jsonIds = findUniqueASINsFromJSON(jsonResponse); if (Object.keys(jsonIds).length > 0) { // Wait for the page to settle before displaying ASINs setTimeout(() => addASINDisplay(jsonIds), 1000); } return response; }); } return response; } catch (error) { console.error('Error in fetch interception:', error); return response; } }); } return originalFetch.apply(this, args); }; // Track the current URL let currentURL = window.location.href; // Function to check for URL changes function checkForURLChange() { if (window.location.href !== currentURL) { currentURL = window.location.href; console.log("URL changed. Updating IDs..."); // Wait for the page to settle before displaying ASINs setTimeout(() => { addASINDisplay(); // Display main ASINs addEpisodeASINs(); // Display episode ASINs }, 1000); } } // Run the URL change checker every 500ms setInterval(checkForURLChange, 500); // Initial run after the page has fully loaded window.addEventListener("load", () => { setTimeout(() => { addASINDisplay(); // Display main ASINs addEpisodeASINs(); // Display episode ASINs }, 1000); }); })();