Amazon Video ASIN Display

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

目前為 2025-03-05 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Amazon Video ASIN Display
  3. // @namespace sac@libidgel.com
  4. // @version 0.3.6
  5. // @description Show unique ASINs for episodes and movies/seasons on Amazon Prime Video
  6. // @author ReiDoBrega
  7. // @license MIT
  8. // @match https://www.amazon.com/*
  9. // @match https://www.amazon.co.uk/*
  10. // @match https://www.amazon.de/*
  11. // @match https://www.amazon.co.jp/*
  12. // @match https://www.primevideo.com/*
  13. // @run-at document-idle
  14. // @grant none
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. "use strict";
  19.  
  20. // Add styles for ASIN display and pop-up
  21. let style = document.createElement("style");
  22. style.textContent = `
  23. .x-asin-container {
  24. margin: 0.5em 0 1em 0;
  25. }
  26. .x-asin-item, .x-episode-asin {
  27. color: #1399FF; /* Green color */
  28. cursor: pointer;
  29. margin: 5px 0;
  30. }
  31. .x-copy-popup {
  32. position: fixed;
  33. bottom: 20px;
  34. right: 20px;
  35. background-color: rgba(0, 0, 0, 0); /* Transparent background */
  36. color: #1399FF; /* Green text */
  37. padding: 10px 20px;
  38. border-radius: 5px;
  39. font-family: Arial, sans-serif;
  40. font-size: 14px;
  41. box-shadow: 0 2px 10px rgba(0, 0, 0, 0);
  42. z-index: 1000;
  43. animation: fadeInOut 2.5s ease-in-out;
  44. }
  45. @keyframes fadeOut {
  46. 0% { opacity: 1; }
  47. 100% { opacity: 0; }
  48. }
  49. `;
  50. document.head.appendChild(style);
  51.  
  52. // Function to extract ASIN from URL
  53. function extractASINFromURL() {
  54. const url = window.location.href;
  55. const asinRegex = /\/gp\/video\/detail\/([A-Z0-9]{10})/;
  56. const match = url.match(asinRegex);
  57. return match ? match[1] : null;
  58. }
  59.  
  60. // Function to find and display unique ASINs
  61. function findUniqueASINs() {
  62. // Extract ASIN from URL first
  63. const urlASIN = extractASINFromURL();
  64. if (urlASIN) {
  65. return { urlASIN };
  66. }
  67.  
  68. // Object to store one unique ASIN/ID for each type
  69. let uniqueIds = {};
  70.  
  71. // List of ID patterns to find
  72. const idPatterns = [
  73. {
  74. name: 'titleID',
  75. regex: /"titleID":"([^"]+)"/
  76. },
  77. // {
  78. // name: 'pageTypeId',
  79. // regex: /pageTypeId: "([^"]+)"/
  80. // },
  81. // {
  82. // name: 'pageTitleId',
  83. // regex: /"pageTitleId":"([^"]+)"/
  84. // },
  85. // {
  86. // name: 'catalogId',
  87. // regex: /catalogId":"([^"]+)"/
  88. // }
  89. ];
  90.  
  91. // Search through patterns
  92. idPatterns.forEach(pattern => {
  93. let match = document.body.innerHTML.match(pattern.regex);
  94. if (match && match[1]) {
  95. uniqueIds[pattern.name] = match[1];
  96. }
  97. });
  98.  
  99. return uniqueIds;
  100. }
  101.  
  102. // Function to find ASINs from JSON response
  103. function findUniqueASINsFromJSON(jsonData) {
  104. let uniqueIds = {};
  105.  
  106. // Comprehensive search paths for ASINs
  107. const searchPaths = [
  108. { name: 'titleId', paths: [
  109. ['titleID'],
  110. ['page', 0, 'assembly', 'body', 0, 'args', 'titleID'],
  111. ['titleId'],
  112. ['detail', 'titleId'],
  113. ['data', 'titleId']
  114. ]},
  115. ];
  116.  
  117. // Deep object traversal function
  118. function traverseObject(obj, paths) {
  119. for (let pathSet of paths) {
  120. try {
  121. let value = obj;
  122. for (let key of pathSet) {
  123. value = value[key];
  124. if (value === undefined) break;
  125. }
  126.  
  127. if (value && typeof value === 'string' && value.trim() !== '') {
  128. return value;
  129. }
  130. } catch (e) {
  131. // Silently ignore traversal errors
  132. }
  133. }
  134. return null;
  135. }
  136.  
  137. // Search through all possible paths
  138. searchPaths.forEach(({ name, paths }) => {
  139. const value = traverseObject(jsonData, paths);
  140. if (value) {
  141. uniqueIds[name] = value;
  142. console.log(`[ASIN Display] Found ${name} in JSON: ${value}`);
  143. }
  144. });
  145.  
  146. return uniqueIds;
  147. }
  148.  
  149. // Function to add episode ASINs
  150. function addEpisodeASINs() {
  151. try {
  152. document.querySelectorAll("[id^='selector-'], [id^='av-episode-expand-toggle-']").forEach(el => {
  153. // Skip if ASIN already added
  154. if (el.parentNode.querySelector(".x-episode-asin")) {
  155. return;
  156. }
  157.  
  158. // Extract ASIN from the element ID
  159. let asin = el.id.replace(/^(?:selector|av-episode-expand-toggle)-/, "");
  160.  
  161. // Create ASIN element
  162. let asinEl = document.createElement("div");
  163. asinEl.className = "x-episode-asin";
  164. asinEl.textContent = asin;
  165. asinEl.addEventListener("click", () => copyToClipboard(asin));
  166.  
  167. // Insert ASIN element after the episode title
  168. let epTitle = el.parentNode.querySelector("[data-automation-id^='ep-title']");
  169. if (epTitle) {
  170. epTitle.parentNode.insertBefore(asinEl, epTitle.nextSibling);
  171. }
  172. });
  173. return true; // Episode ASINs added successfully
  174. } catch (e) {
  175. console.error("ASIN Display - Error in addEpisodeASINs:", e);
  176. return false; // Error occurred
  177. }
  178. }
  179.  
  180. // Function to add ASIN display
  181. function addASINDisplay(uniqueIds = null) {
  182. try {
  183. // If no IDs provided, find them from HTML
  184. if (!uniqueIds) {
  185. uniqueIds = findUniqueASINs();
  186. }
  187.  
  188. // Remove existing ASIN containers
  189. document.querySelectorAll(".x-asin-container").forEach(el => el.remove());
  190.  
  191. // If no IDs found, return
  192. if (Object.keys(uniqueIds).length === 0) {
  193. console.log("ASIN Display: No ASINs found");
  194. return false;
  195. }
  196.  
  197. // Create ASIN container
  198. let asinContainer = document.createElement("div");
  199. asinContainer.className = "x-asin-container";
  200.  
  201. // Add each unique ID as a clickable element
  202. Object.entries(uniqueIds).forEach(([type, id]) => {
  203. let asinEl = document.createElement("div");
  204. asinEl.className = "x-asin-item";
  205. asinEl.textContent = id;
  206. asinEl.addEventListener("click", () => copyToClipboard(id));
  207. asinContainer.appendChild(asinEl);
  208. });
  209.  
  210. // Insert the ASIN container after the synopsis
  211. let after = document.querySelector(".dv-dp-node-synopsis, .av-synopsis");
  212. if (!after) {
  213. console.log("ASIN Display: Could not find element to insert after");
  214. return false;
  215. }
  216.  
  217. after.parentNode.insertBefore(asinContainer, after.nextSibling);
  218. return true;
  219. } catch (e) {
  220. console.error("ASIN Display - Error in addASINDisplay:", e);
  221. return false;
  222. }
  223. }
  224.  
  225. // Function to copy text to clipboard and show pop-up
  226. function copyToClipboard(text) {
  227. const input = document.createElement("textarea");
  228. input.value = text;
  229. document.body.appendChild(input);
  230. input.select();
  231. document.execCommand("copy");
  232. document.body.removeChild(input);
  233.  
  234. // Show pop-up
  235. const popup = document.createElement("div");
  236. popup.className = "x-copy-popup";
  237. popup.textContent = `Copied: ${text}`;
  238. document.body.appendChild(popup);
  239.  
  240. // Remove pop-up after 1.5 seconds
  241. setTimeout(() => {
  242. popup.remove();
  243. }, 1500);
  244. }
  245.  
  246. // Intercept fetch requests for JSON responses
  247. const originalFetch = window.fetch;
  248. window.fetch = function(...args) {
  249. const [url] = args;
  250.  
  251. // Check if the URL matches the detail page pattern
  252. if (url.includes('/detail/') && url.includes('primevideo.com')) {
  253. return originalFetch.apply(this, args).then(response => {
  254. try {
  255. const contentType = response.headers.get('content-type');
  256.  
  257. if (contentType?.includes('application/json')) {
  258. return response.clone().json().then(jsonResponse => {
  259. // Find unique IDs using comprehensive search paths
  260. const jsonIds = findUniqueASINsFromJSON(jsonResponse);
  261.  
  262. if (Object.keys(jsonIds).length > 0) {
  263. // Wait for the page to settle before displaying ASINs
  264. setTimeout(() => addASINDisplay(jsonIds), 1000);
  265. }
  266.  
  267. return response;
  268. });
  269. }
  270. return response;
  271. } catch (error) {
  272. console.error('Error in fetch interception:', error);
  273. return response;
  274. }
  275. });
  276. }
  277.  
  278. return originalFetch.apply(this, args);
  279. };
  280.  
  281. // Track the current URL
  282. let currentURL = window.location.href;
  283.  
  284. // Function to check for URL changes
  285. function checkForURLChange() {
  286. if (window.location.href !== currentURL) {
  287. currentURL = window.location.href;
  288. console.log("URL changed. Updating IDs...");
  289. // Wait for the page to settle before displaying ASINs
  290. setTimeout(() => {
  291. addASINDisplay(); // Display main ASINs
  292. addEpisodeASINs(); // Display episode ASINs
  293. }, 1000);
  294. }
  295. }
  296.  
  297. // Run the URL change checker every 500ms
  298. setInterval(checkForURLChange, 500);
  299.  
  300. // Initial run after the page has fully loaded
  301. window.addEventListener("load", () => {
  302. setTimeout(() => {
  303. addASINDisplay(); // Display main ASINs
  304. addEpisodeASINs(); // Display episode ASINs
  305. }, 1000);
  306. });
  307. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址