Twitter image viewing enhancement

Make Twitter photo viewing more humane

Инсталирай този скрипт?
Препоръчано от автора

Може да харесате и Twitter remove content warning.

Инсталирай този скрипт

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Twitter image viewing enhancement
// @name:zh-CN   Twitter 图片查看增强
// @name:zh-TW   Twitter 圖像查看增強
// @icon         https://twitter.com/favicon.ico
// @namespace    https://moe.best/
// @version      1.4.0
// @description        Make Twitter photo viewing more humane
// @description:zh-CN  让推特图片浏览更加人性化
// @description:zh-TW  讓 Twitter 照片瀏覽更人性化
// @author       Jindai Kirin
// @include      https://x.com/*
// @include      https://twitter.com/*
// @license      MIT
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// ==/UserScript==

(() => {
  'use strict';

  // 滑动切换图片
  let enableDragToSwitch = GM_getValue('enableDragToSwitch', false);
  GM_registerMenuCommand('Drag to switch images', () => {
    enableDragToSwitch = confirm(`Do you want to enable drag to switch images?
Current: ${enableDragToSwitch ? 'Enabled' : 'Disabled'}

Please refresh to take effect after modification.`);
    GM_setValue('enableDragToSwitch', enableDragToSwitch);
  });

  if (enableDragToSwitch) GM_addStyle('img{-webkit-user-drag:none}');

  const labels = {};

  const setLabels = labelsStr => {
    const list = (labelsStr || '').split(',');
    if (list.length !== 3) return;
    labels.close = list[0];
    labels.prev = list[1];
    labels.next = list[2];
  };

  setLabels(GM_getValue('labels_v1', ''));

  // 手动设置 aria-label
  GM_registerMenuCommand('Set aria-label', () => {
    let input, list;
    let error = false;
    do {
      const current = GM_getValue('labels_v1', '');
      input = prompt(
        `Please input the aria-label of Close, Previous, Next button and join them by commas(,). Submit an empty string will disable it.${
          error ? '\n\nINPUT ERROR' : ''
        }`,
        input || current
      );
      if (input === null) return;
      input = input.trim();
      if (input.length === 0) {
        GM_setValue('labels_v1', '');
        return;
      } else list = input.split(',').map(label => label.trim());
      error = list.length !== 3;
    } while (error);
    const value = list.join(',');
    setLabels(value);
    GM_setValue('labels_v1', value);
  });

  if (!Object.values(labels).length) {
    try {
      const kv = {
        af8fa2ad: 'close',
        af8fa2ae: 'close',
        c4d53ba2: 'prev',
        d70740d9: 'next',
        d70740da: 'next',
      };
      const i18nModule = webpackChunk_twitter_responsive_web.find(([[name]]) =>
        name.startsWith('i18n')
      );
      Object.values(i18nModule[1]).forEach(fn => {
        if (fn.length < 3) return;
        try {
          fn(undefined, undefined, () => ({
            _register: () => (k, v) => {
              if (k in kv) labels[kv[k]] = v;
            },
          }));
        } catch (e) {}
      });
    } catch (error) {
      console.error(error);
    }
  }

  const getBtnByLabel = label => document.querySelector(`[aria-label="${label}"]`);
  const clickBtn = name => {
    const $btn = getBtnByLabel(labels[name]);
    if ($btn) {
      $btn.click();
      return true;
    }
    return false;
  };

  const closeImgView = () => clickBtn('close');
  const prevImg = () => clickBtn('prev');
  const nextImg = () => clickBtn('next');

  /**
   * @param {HTMLElement} el
   */
  const isTwitterImg = el => el.tagName == 'IMG' && el.baseURI.includes('/photo/');

  window.addEventListener(
    'wheel',
    ({ deltaY, target }) => {
      if (isTwitterImg(target) || target.dataset.testid === 'swipe-to-dismiss') {
        if (deltaY < 0) prevImg();
        else if (deltaY > 0) nextImg();
      }
    },
    { passive: true }
  );

  if (enableDragToSwitch) {
    let x = 0;
    let y = 0;
    window.addEventListener('mousedown', ({ clientX, clientY }) => {
      x = clientX;
      y = clientY;
    });
    window.addEventListener(
      'mouseup',
      ({ button, clientX, clientY, target }) => {
        if (button !== 0 || !isTwitterImg(target)) return;
        const [sx, sy] = [clientX - x, clientY - y].map(Math.abs);
        const mx = clientX - x;
        if (sx <= 10 && sy <= 10) closeImgView();
        if (sy <= sx) {
          if (mx > 0) prevImg();
          else if (mx < 0) nextImg();
        }
      },
      { passive: true }
    );
  } else {
    document.addEventListener(
      'click',
      e => {
        if (!isTwitterImg(e.target)) return;
        closeImgView();
        e.stopPropagation();
      },
      { capture: true, passive: true }
    );
  }
})();