X Gallery with Fancybox

Replace X gallery with Fancybox v5.0.36, context-aware galleries and improved media tab support

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

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/535121/1584069/X%20Gallery%20with%20Fancybox.js

  1. // ==UserScript==
  2. // @name X Gallery with Fancybox
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.7
  5. // @description Replace X gallery with Fancybox v5.0.36, context-aware galleries and improved media tab support
  6. // @author You
  7. // @match https://x.com/*
  8. // @require https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0.36/dist/fancybox/fancybox.umd.js
  9. // @resource fancyboxCSS https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0.36/dist/fancybox/fancybox.css
  10. // @grant GM_addStyle
  11. // @grant GM_getResourceText
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Inject Fancybox CSS
  18. GM_addStyle(GM_getResourceText('fancyboxCSS'));
  19.  
  20. // Function to generate the original image URL
  21. function getOriginalImageUrl(src) {
  22. if (src.includes('?')) {
  23. const url = new URL(src);
  24. const params = new URLSearchParams(url.search);
  25. params.set('name', 'orig');
  26. return `${url.origin}${url.pathname}?${params.toString()}`;
  27. } else {
  28. return src + ':orig';
  29. }
  30. }
  31.  
  32. // Function to collect all images and videos in the relevant container
  33. function getGalleryImages(target) {
  34. let mediaContainer;
  35. const tweet = target.closest('article[role="article"]');
  36.  
  37. if (tweet) {
  38. mediaContainer = tweet;
  39. } else {
  40. mediaContainer = target.closest('[aria-label*="Timeline:"]');
  41. if (!mediaContainer) {
  42. mediaContainer = target.closest('[data-testid="tweetPhoto"]')?.parentElement?.parentElement || target.closest('[data-testid="videoPlayer"]')?.parentElement;
  43. }
  44. }
  45.  
  46. if (!mediaContainer) return [];
  47.  
  48. const items = [];
  49. mediaContainer.querySelectorAll('img[src*="pbs.twimg.com"]:not([src*="profile_images"])').forEach(img => {
  50. const src = getOriginalImageUrl(img.src);
  51. items.push({ src: src, type: 'image' });
  52. });
  53. mediaContainer.querySelectorAll('video[src*="video.twimg.com"]').forEach(video => {
  54. items.push({ src: video.src, type: 'video' });
  55. });
  56. return items;
  57. }
  58.  
  59. // Function to open media in Fancybox
  60. function openFancybox(e) {
  61. e.preventDefault();
  62. e.stopPropagation();
  63.  
  64. if (e.target.closest('.fancybox__container')) {
  65. return;
  66. }
  67.  
  68. const target = e.target;
  69. const galleryItems = getGalleryImages(target);
  70. let startIndex = 0;
  71.  
  72. if (target.tagName === 'IMG' && target.src.includes('pbs.twimg.com') && !target.src.includes('profile_images')) {
  73. const clickedSrc = getOriginalImageUrl(target.src);
  74. startIndex = galleryItems.findIndex(item => item.src === clickedSrc);
  75. } else if (target.tagName === 'VIDEO' && target.src.includes('video.twimg.com')) {
  76. const clickedSrc = target.src;
  77. startIndex = galleryItems.findIndex(item => item.src === clickedSrc);
  78. }
  79.  
  80. if (galleryItems.length > 0) {
  81. console.log('Opening Fancybox with gallery:', galleryItems);
  82. Fancybox.show(galleryItems.map(item => ({
  83. src: item.src,
  84. type: item.type
  85. })), {
  86. Html: { loop: false },
  87. startIndex: startIndex,
  88. hideScrollbar: false,
  89. autoFocus: false,
  90. trapFocus: false,
  91. Carousel: {
  92. infinite: false,
  93. transition: "slide",
  94. },
  95. Thumbs: false,
  96. Media: {
  97. video: {
  98. contentType: 'video/mp4',
  99. }
  100. },
  101. Images: {
  102. Panzoom: {
  103. maxScale: 5,
  104. mouseMoveFactor: 1.03,
  105. mouseMoveFriction: 0.07
  106. },
  107. },
  108. Toolbar: {
  109. display: {
  110. left: ["infobar"],
  111. middle: [],
  112. right: ["slideshow", "download", "thumbs", "close"],
  113. },
  114. }
  115. });
  116. }
  117. }
  118.  
  119. // Function to apply event listeners to media elements
  120. function addMediaListeners() {
  121. const mediaSelectors = 'img[src*="pbs.twimg.com"]:not([src*="profile_images"]), video[src*="video.twimg.com"]';
  122.  
  123. // Target media within articles (for main timeline)
  124. document.querySelectorAll('article[role="article"] ' + mediaSelectors).forEach(media => {
  125. const parent = media.closest('a') || media.parentElement;
  126. if (parent) {
  127. parent.removeEventListener('click', openFancybox);
  128. parent.addEventListener('click', openFancybox);
  129. }
  130. });
  131.  
  132. // Target media within the Media timeline directly
  133. const mediaTimeline = document.querySelector('[aria-label*="Timeline:"]');
  134. if (mediaTimeline) {
  135. mediaTimeline.querySelectorAll(mediaSelectors).forEach(media => {
  136. // Attach listener directly to the img/video
  137. media.removeEventListener('click', openFancybox);
  138. media.addEventListener('click', openFancybox);
  139. }
  140. );
  141. }
  142. }
  143.  
  144. // Observe DOM changes for dynamically loaded content
  145. const observer = new MutationObserver(() => {
  146. addMediaListeners();
  147. });
  148. const primaryColumn = document.querySelector('[data-testid="primaryColumn"]');
  149. observer.observe(primaryColumn || document.body, { childList: true, subtree: true });
  150.  
  151. // Run initially
  152. addMediaListeners();
  153. })();

QingJ © 2025

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