Greasy Fork

Greasy Fork is available in English.

Amazon Video ASIN Display

Show unique ASINs for episodes and movies/seasons on Amazon Prime Video

当前为 2025-03-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
    });
})();