::GOG-Games Links::(2025)

Adds a YouTube button per game card to search for no-commentary gameplay videos. It was made with deepseek AI.

ของเมื่อวันที่ 04-03-2025 ดู เวอร์ชันล่าสุด

  1. // ==UserScript==
  2. // @name ::GOG-Games Links::(2025)
  3. // @namespace masterofobzene-GOG-Games
  4. // @version 3.0
  5. // @description Adds a YouTube button per game card to search for no-commentary gameplay videos. It was made with deepseek AI.
  6. // @author masterofobzene
  7. // @license MIT
  8. // @match https://gog-games.to/*
  9. // @grant none
  10. // @icon https://files.mastodon.social/accounts/avatars/114/061/563/113/485/047/original/f9c6c7664af152f1.png
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const YT_BUTTON_CLASS = 'yt-search-unique';
  17. const PROCESSED_ATTR = 'data-yt-processed-v2';
  18. const PURPLE_COLOR = '#6a1b9a';
  19. const HOVER_COLOR = '#4a148c';
  20. let processing = false;
  21.  
  22. function createYouTubeButton(gameName) {
  23. const button = document.createElement('button');
  24. button.className = YT_BUTTON_CLASS;
  25. button.textContent = 'YouTube Search';
  26. button.style.cssText = `
  27. padding: 6px 12px !important;
  28. background: ${PURPLE_COLOR} !important;
  29. color: white !important;
  30. border: none !important;
  31. border-radius: 4px !important;
  32. cursor: pointer !important;
  33. margin: 8px 0 !important;
  34. font-family: Arial !important;
  35. transition: background 0.2s !important;
  36. display: inline-block !important;
  37. position: relative !important;
  38. z-index: 1000 !important;
  39. `;
  40.  
  41. const handleClick = (event) => {
  42. event.stopImmediatePropagation();
  43. event.preventDefault();
  44. window.open(`https://youtube.com/results?search_query=${encodeURIComponent(gameName + ' no commentary')}`, '_blank');
  45. };
  46.  
  47. button.addEventListener('mouseover', () => button.style.background = HOVER_COLOR);
  48. button.addEventListener('mouseout', () => button.style.background = PURPLE_COLOR);
  49. button.addEventListener('click', handleClick, true); // Use capturing phase
  50. button.addEventListener('auxclick', handleClick, true);
  51.  
  52. return button;
  53. }
  54.  
  55. function processCard(card) {
  56. if (processing || card.hasAttribute(PROCESSED_ATTR)) return;
  57.  
  58. processing = true;
  59. try {
  60. const existingButton = card.querySelector(`.${YT_BUTTON_CLASS}`);
  61. if (existingButton) {
  62. existingButton.remove();
  63. }
  64.  
  65. const gameName = [
  66. () => card.querySelector('img[alt]')?.alt?.trim(),
  67. () => card.querySelector('[class*="title"]')?.textContent?.trim(),
  68. () => card.querySelector('h3, h4')?.textContent?.trim()
  69. ].reduce((acc, fn) => acc || fn(), '');
  70.  
  71. if (!gameName) return;
  72.  
  73. const container = card.querySelector('.actions, .card-footer') || card.querySelector('a')?.parentElement || card;
  74. if (container && !container.querySelector(`.${YT_BUTTON_CLASS}`)) {
  75. container.prepend(createYouTubeButton(gameName));
  76. card.setAttribute(PROCESSED_ATTR, 'true');
  77. }
  78. } finally {
  79. processing = false;
  80. }
  81. }
  82.  
  83. function processAllCards() {
  84. const cards = document.querySelectorAll('[class*="card"]:not([${PROCESSED_ATTR}])');
  85. cards.forEach(card => {
  86. if (!card.hasAttribute(PROCESSED_ATTR)) {
  87. processCard(card);
  88. }
  89. });
  90. }
  91.  
  92. // Initial processing after full load
  93. window.addEventListener('load', () => {
  94. setTimeout(processAllCards, 2000);
  95. }, {once: true});
  96.  
  97. // Targeted mutation observation
  98. const mainContent = document.getElementById('main') || document.querySelector('main') || document.body;
  99. const observer = new MutationObserver((mutations) => {
  100. mutations.forEach(mutation => {
  101. if (mutation.type === 'childList') {
  102. mutation.addedNodes.forEach(node => {
  103. if (node.nodeType === Node.ELEMENT_NODE && node.matches('[class*="card"]')) {
  104. processCard(node);
  105. }
  106. });
  107. }
  108. });
  109. });
  110.  
  111. observer.observe(mainContent, {
  112. childList: true,
  113. subtree: true
  114. });
  115. })();