GreasyFork Media Preview

Automatically show media previews (video, audio, images, gifs) on GreasyFork links. It supports: .mp3, .wav, .flac, .aac, .ogg, .m4a, .wma, .alac, .aiff, .amr, .mp4, .webm, .ogv, .avi, .mov, .mkv, .flv, .wmv, .m4v, .3gp, .jpg, .jpeg, .png, .bmp, .svg, .gif, .apng, .webp

目前为 2024-08-31 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GreasyFork Media Preview
  3. // @author NWP
  4. // @description Automatically show media previews (video, audio, images, gifs) on GreasyFork links. It supports: .mp3, .wav, .flac, .aac, .ogg, .m4a, .wma, .alac, .aiff, .amr, .mp4, .webm, .ogv, .avi, .mov, .mkv, .flv, .wmv, .m4v, .3gp, .jpg, .jpeg, .png, .bmp, .svg, .gif, .apng, .webp
  5. // @namespace https://greasyfork.org/users/877912
  6. // @version 0.1
  7. // @license MIT
  8. // @match *://greasyfork.org/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. const audioFormats = ['.mp3', '.wav', '.flac', '.aac', '.ogg', '.m4a', '.wma', '.alac', '.aiff', '.amr'];
  16. const videoFormats = ['.mp4', '.webm', '.ogv', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.m4v', '.3gp'];
  17. const imageFormats = ['.jpg', '.jpeg', '.png', '.bmp', '.svg'];
  18. const gifFormats = ['.gif', '.apng', '.webp'];
  19.  
  20. function createMediaElement(url) {
  21. let media;
  22. if (audioFormats.some(format => url.endsWith(format))) {
  23. media = document.createElement('audio');
  24. media.src = url;
  25. media.controls = true;
  26. media.autoplay = false;
  27. media.loop = true;
  28. media.muted = false;
  29. media.classList.add('added-by-script');
  30. } else if (videoFormats.some(format => url.endsWith(format))) {
  31. media = document.createElement('video');
  32. media.src = url;
  33. media.style.width = '20rem';
  34. media.style.height = '15rem';
  35. media.controls = true;
  36. media.autoplay = false;
  37. media.loop = true;
  38. media.muted = false;
  39. media.classList.add('added-by-script');
  40. } else if (gifFormats.some(format => url.endsWith(format))) {
  41. media = document.createElement('div');
  42. media.style.display = 'flex';
  43. media.style.flexDirection = 'column';
  44. media.style.alignItems = 'flex-start';
  45. media.style.width = '20rem';
  46. media.style.height = 'auto';
  47. media.style.marginBottom = '0.625rem';
  48.  
  49. const img = document.createElement('img');
  50. img.src = url;
  51. img.style.width = '20rem';
  52. img.style.height = '15rem';
  53. img.classList.add('added-by-script');
  54. img.dataset.pausedSrc = url;
  55. img.dataset.playing = 'false';
  56. img.style.display = 'none';
  57.  
  58. const canvas = document.createElement('canvas');
  59. canvas.style.width = '20rem';
  60. canvas.style.height = '15rem';
  61. media.appendChild(canvas);
  62.  
  63. const ctx = canvas.getContext('2d');
  64. const gif = new Image();
  65. gif.src = url;
  66. gif.onload = function() {
  67. ctx.drawImage(gif, 0, 0, canvas.width, canvas.height);
  68. };
  69.  
  70. const playButton = document.createElement('button');
  71. playButton.innerText = 'Play';
  72. playButton.style.display = 'block';
  73. playButton.style.marginTop = '0.625rem';
  74. playButton.style.alignSelf = 'flex-start';
  75.  
  76. playButton.onclick = function(event) {
  77. event.preventDefault();
  78. if (img.dataset.playing === 'true') {
  79. img.style.display = 'none';
  80. canvas.style.display = 'block';
  81. img.dataset.playing = 'false';
  82. playButton.innerText = 'Play';
  83. } else {
  84. img.style.display = 'block';
  85. canvas.style.display = 'none';
  86. img.dataset.playing = 'true';
  87. playButton.innerText = 'Stop';
  88. }
  89. };
  90.  
  91. media.appendChild(img);
  92. media.appendChild(playButton);
  93. media.classList.add('added-by-script');
  94. } else if (imageFormats.some(format => url.endsWith(format))) {
  95. media = document.createElement('img');
  96. media.src = url;
  97. media.style.width = '20rem';
  98. media.style.height = '15rem';
  99. media.classList.add('added-by-script');
  100. }
  101. return media;
  102. }
  103.  
  104. function showMediaPreviews(container) {
  105. const mediaLinks = container.querySelectorAll(
  106. audioFormats.map(format => `a[href$="${format}"]`)
  107. .concat(videoFormats.map(format => `a[href$="${format}"]`))
  108. .concat(imageFormats.map(format => `a[href$="${format}"]`))
  109. .concat(gifFormats.map(format => `a[href$="${format}"]`))
  110. .join(', ')
  111. );
  112. mediaLinks.forEach(link => {
  113. if (!link.dataset.processed) {
  114. const media = createMediaElement(link.href);
  115. if (media) {
  116. const paragraph = document.createElement('p');
  117. paragraph.appendChild(media);
  118. link.parentNode.insertBefore(paragraph, link.nextSibling);
  119. link.dataset.processed = 'true';
  120. }
  121. }
  122. });
  123. }
  124.  
  125. function initializeMediaPreviews() {
  126. const additionalInfo = document.querySelector('#additional-info');
  127. if (additionalInfo) {
  128. showMediaPreviews(additionalInfo);
  129. }
  130.  
  131. const previewResults = document.querySelectorAll('.preview-results.user-content[style*="display: block"]');
  132. previewResults.forEach(container => {
  133. showMediaPreviews(container);
  134. });
  135. }
  136.  
  137. window.onload = function() {
  138. initializeMediaPreviews();
  139. };
  140.  
  141. const observer = new MutationObserver((mutations) => {
  142. mutations.forEach(mutation => {
  143. if (mutation.attributeName === 'style' && mutation.target.style.display === 'block') {
  144. showMediaPreviews(mutation.target);
  145. }
  146. });
  147. });
  148.  
  149. document.querySelectorAll('.preview-results.user-content').forEach(container => {
  150. observer.observe(container, { attributes: true, attributeFilter: ['style'] });
  151. });
  152.  
  153. })();