Twitter 图片查看增强

让推特图片浏览更加人性化

安装此脚本?
作者推荐脚本

您可能也喜欢Twitter 移除内容警告

安装此脚本
  1. // ==UserScript==
  2. // @name Twitter image viewing enhancement
  3. // @name:zh-CN Twitter 图片查看增强
  4. // @name:zh-TW Twitter 圖像查看增強
  5. // @icon https://twitter.com/favicon.ico
  6. // @namespace https://moe.best/
  7. // @version 1.4.0
  8. // @description Make Twitter photo viewing more humane
  9. // @description:zh-CN 让推特图片浏览更加人性化
  10. // @description:zh-TW 讓 Twitter 照片瀏覽更人性化
  11. // @author Jindai Kirin
  12. // @include https://x.com/*
  13. // @include https://twitter.com/*
  14. // @license MIT
  15. // @grant GM_addStyle
  16. // @grant GM_getValue
  17. // @grant GM_setValue
  18. // @grant GM_registerMenuCommand
  19. // @run-at document-end
  20. // ==/UserScript==
  21.  
  22. (() => {
  23. 'use strict';
  24.  
  25. // 滑动切换图片
  26. let enableDragToSwitch = GM_getValue('enableDragToSwitch', false);
  27. GM_registerMenuCommand('Drag to switch images', () => {
  28. enableDragToSwitch = confirm(`Do you want to enable drag to switch images?
  29. Current: ${enableDragToSwitch ? 'Enabled' : 'Disabled'}
  30.  
  31. Please refresh to take effect after modification.`);
  32. GM_setValue('enableDragToSwitch', enableDragToSwitch);
  33. });
  34.  
  35. if (enableDragToSwitch) GM_addStyle('img{-webkit-user-drag:none}');
  36.  
  37. const labels = {};
  38.  
  39. const setLabels = labelsStr => {
  40. const list = (labelsStr || '').split(',');
  41. if (list.length !== 3) return;
  42. labels.close = list[0];
  43. labels.prev = list[1];
  44. labels.next = list[2];
  45. };
  46.  
  47. setLabels(GM_getValue('labels_v1', ''));
  48.  
  49. // 手动设置 aria-label
  50. GM_registerMenuCommand('Set aria-label', () => {
  51. let input, list;
  52. let error = false;
  53. do {
  54. const current = GM_getValue('labels_v1', '');
  55. input = prompt(
  56. `Please input the aria-label of Close, Previous, Next button and join them by commas(,). Submit an empty string will disable it.${
  57. error ? '\n\nINPUT ERROR' : ''
  58. }`,
  59. input || current
  60. );
  61. if (input === null) return;
  62. input = input.trim();
  63. if (input.length === 0) {
  64. GM_setValue('labels_v1', '');
  65. return;
  66. } else list = input.split(',').map(label => label.trim());
  67. error = list.length !== 3;
  68. } while (error);
  69. const value = list.join(',');
  70. setLabels(value);
  71. GM_setValue('labels_v1', value);
  72. });
  73.  
  74. if (!Object.values(labels).length) {
  75. try {
  76. const kv = {
  77. af8fa2ad: 'close',
  78. af8fa2ae: 'close',
  79. c4d53ba2: 'prev',
  80. d70740d9: 'next',
  81. d70740da: 'next',
  82. };
  83. const i18nModule = webpackChunk_twitter_responsive_web.find(([[name]]) =>
  84. name.startsWith('i18n')
  85. );
  86. Object.values(i18nModule[1]).forEach(fn => {
  87. if (fn.length < 3) return;
  88. try {
  89. fn(undefined, undefined, () => ({
  90. _register: () => (k, v) => {
  91. if (k in kv) labels[kv[k]] = v;
  92. },
  93. }));
  94. } catch (e) {}
  95. });
  96. } catch (error) {
  97. console.error(error);
  98. }
  99. }
  100.  
  101. const getBtnByLabel = label => document.querySelector(`[aria-label="${label}"]`);
  102. const clickBtn = name => {
  103. const $btn = getBtnByLabel(labels[name]);
  104. if ($btn) {
  105. $btn.click();
  106. return true;
  107. }
  108. return false;
  109. };
  110.  
  111. const closeImgView = () => clickBtn('close');
  112. const prevImg = () => clickBtn('prev');
  113. const nextImg = () => clickBtn('next');
  114.  
  115. /**
  116. * @param {HTMLElement} el
  117. */
  118. const isTwitterImg = el => el.tagName == 'IMG' && el.baseURI.includes('/photo/');
  119.  
  120. window.addEventListener(
  121. 'wheel',
  122. ({ deltaY, target }) => {
  123. if (isTwitterImg(target) || target.dataset.testid === 'swipe-to-dismiss') {
  124. if (deltaY < 0) prevImg();
  125. else if (deltaY > 0) nextImg();
  126. }
  127. },
  128. { passive: true }
  129. );
  130.  
  131. if (enableDragToSwitch) {
  132. let x = 0;
  133. let y = 0;
  134. window.addEventListener('mousedown', ({ clientX, clientY }) => {
  135. x = clientX;
  136. y = clientY;
  137. });
  138. window.addEventListener(
  139. 'mouseup',
  140. ({ button, clientX, clientY, target }) => {
  141. if (button !== 0 || !isTwitterImg(target)) return;
  142. const [sx, sy] = [clientX - x, clientY - y].map(Math.abs);
  143. const mx = clientX - x;
  144. if (sx <= 10 && sy <= 10) closeImgView();
  145. if (sy <= sx) {
  146. if (mx > 0) prevImg();
  147. else if (mx < 0) nextImg();
  148. }
  149. },
  150. { passive: true }
  151. );
  152. } else {
  153. document.addEventListener(
  154. 'click',
  155. e => {
  156. if (!isTwitterImg(e.target)) return;
  157. closeImgView();
  158. e.stopPropagation();
  159. },
  160. { capture: true, passive: true }
  161. );
  162. }
  163. })();