// ==UserScript==
// @name Amazon Video ASIN Display
// @namespace [email protected]
// @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);
});
})();