- // ==UserScript==
- // @name Amazon Video ASIN Display
- // @namespace sac@libidgel.com
- // @version 0.3.6
- // @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
- // ==/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 = "x-asin-item";
- 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);
- });
- })();