Greasy Fork

Greasy Fork is available in English.

Pixiv 工具箱

增强P站查看原图功能;显示原图尺寸

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Pixiv 工具箱
// @version      1.4.1
// @description  增强P站查看原图功能;显示原图尺寸
// @author       sakura-flutter
// @namespace    https://github.com/sakura-flutter/tampermonkey-scripts
// @license      GPL-3.0
// @compatible   chrome Latest
// @compatible   firefox Latest
// @compatible   edge Latest
// @noframes
// @match        https://www.pixiv.net
// @match        https://www.pixiv.net/*
// @grant        window.onurlchange
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @resource     viewerCSS https://unpkg.com/viewerjs@1/dist/viewer.min.css
// @require      https://unpkg.com/viewerjs@1/dist/viewer.min.js
// ==/UserScript==

/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	// The require scope
/******/ 	var __webpack_require__ = {};
/******/ 	
/************************************************************************/
/******/ 	/* webpack/runtime/compat get default export */
/******/ 	(() => {
/******/ 		// getDefaultExport function for compatibility with non-harmony modules
/******/ 		__webpack_require__.n = (module) => {
/******/ 			var getter = module && module.__esModule ?
/******/ 				() => (module['default']) :
/******/ 				() => (module);
/******/ 			__webpack_require__.d(getter, { a: getter });
/******/ 			return getter;
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/define property getters */
/******/ 	(() => {
/******/ 		// define getter functions for harmony exports
/******/ 		__webpack_require__.d = (exports, definition) => {
/******/ 			for(var key in definition) {
/******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 				}
/******/ 			}
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
/******/ 	(() => {
/******/ 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ 	})();
/******/ 	
/************************************************************************/
var __webpack_exports__ = {};

;// CONCATENATED MODULE: external "Viewer"
const external_Viewer_namespaceObject = Viewer;
var external_Viewer_default = /*#__PURE__*/__webpack_require__.n(external_Viewer_namespaceObject);
;// CONCATENATED MODULE: ./src/utils/selector.ts
const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);
;// CONCATENATED MODULE: ./src/utils/log.ts
const isDebug = "production" !== 'production';

function warn(...args) {
  isDebug && warn.force(...args);
}

warn.force = function (...args) {
  console.warn('%c      warn      ', 'background: #ffa500; padding: 1px; color: #fff;', ...args);
};

function error(...args) {
  isDebug && error.force(...args);
}

error.force = function (...args) {
  console.error('%c      error      ', 'background: red; padding: 1px; color: #fff;', ...args);
};

function table(...args) {
  isDebug && console.table(...args);
}


;// CONCATENATED MODULE: ./src/scripts/pixiv/previewer.ts
function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }

var id = 0;

function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }




GM_addStyle(GM_getResourceText('viewerCSS'));
GM_addStyle(['.viewer-backdrop { background-color: rgb(0 0 0 / 0.8) }', // 背景暗一点
'.viewer-container .viewer-title { text-shadow: 1px 1px 1px #000 }', // 添加标题阴影 在图片是白底时显示得清楚点
'.viewer-container .viewer-navbar ul, .viewer-container .viewer-navbar li { width: 66px; height: 110px }' // 加大导航栏
].join(''));

var _el = /*#__PURE__*/_classPrivateFieldLooseKey("el");

var _viewer = /*#__PURE__*/_classPrivateFieldLooseKey("viewer");

var _options = /*#__PURE__*/_classPrivateFieldLooseKey("options");

var _init = /*#__PURE__*/_classPrivateFieldLooseKey("init");

var _process = /*#__PURE__*/_classPrivateFieldLooseKey("process");

var _getArtworks = /*#__PURE__*/_classPrivateFieldLooseKey("getArtworks");

var _createOriginalImgEls = /*#__PURE__*/_classPrivateFieldLooseKey("createOriginalImgEls");

var _preview = /*#__PURE__*/_classPrivateFieldLooseKey("preview");

class Previewer {
  constructor(el, options) {
    Object.defineProperty(this, _preview, {
      value: _preview2
    });
    Object.defineProperty(this, _createOriginalImgEls, {
      value: _createOriginalImgEls2
    });
    Object.defineProperty(this, _getArtworks, {
      value: _getArtworks2
    });
    Object.defineProperty(this, _init, {
      value: _init2
    });
    Object.defineProperty(this, _el, {
      writable: true,
      value: void 0
    });
    Object.defineProperty(this, _viewer, {
      writable: true,
      value: void 0
    });
    Object.defineProperty(this, _options, {
      writable: true,
      value: void 0
    });
    Object.defineProperty(this, _process, {
      writable: true,
      value: function (event) {
        /**
         * 这么多的判断多数是没有意义的
         * 只是为了日后可能失效,尽量避免影响原点击事件
         */
        if (!_classPrivateFieldLooseBase(this, _options)[_options].includePathname.test(location.pathname)) return;

        const artworks = _classPrivateFieldLooseBase(this, _getArtworks)[_getArtworks]();

        if (artworks.length === 0) return;
        let index = -1; // 比较5层深度应该足够了

        event.composedPath().slice(0, 5).find(target => {
          index = artworks.findIndex(artwork => artwork === target);
          return index > -1;
        });
        warn(event, index);
        if (index === -1) return;

        const originalArtworks = _classPrivateFieldLooseBase(this, _createOriginalImgEls)[_createOriginalImgEls](artworks);

        if (originalArtworks.length === 0) return;
        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation();
        _classPrivateFieldLooseBase(this, _viewer)[_viewer] = _classPrivateFieldLooseBase(this, _preview)[_preview](originalArtworks, {
          initialViewIndex: index
        });
      }
    });
    _classPrivateFieldLooseBase(this, _process)[_process] = _classPrivateFieldLooseBase(this, _process)[_process].bind(this);
    _classPrivateFieldLooseBase(this, _el)[_el] = el;
    _classPrivateFieldLooseBase(this, _options)[_options] = options;

    _classPrivateFieldLooseBase(this, _init)[_init]();
  }

}

function _init2() {
  window.addEventListener('click', _classPrivateFieldLooseBase(this, _process)[_process], true);
  window.addEventListener('urlchange', info => {
    warn('urlchange', info);
    _classPrivateFieldLooseBase(this, _viewer)[_viewer]?.hide();
  });
}

function _getArtworks2() {
  return [...$$(_classPrivateFieldLooseBase(this, _el)[_el])];
}

function _createOriginalImgEls2(imgEls) {
  return imgEls.reduce((acc, img) => {
    const parentNode = img.parentNode; // 原图在其父级a标签href上

    if (parentNode.tagName === 'A') {
      const image = new Image();
      image.src = parentNode.href;
      image.alt = img.alt;
      acc.push(image);
    }

    return acc;
  }, []);
}

function _preview2(imgEls, viewerOpts) {
  // eslint-disable-next-line @typescript-eslint/no-this-alias
  const self = this;
  const container = document.createElement('div');
  container.append(...imgEls);
  viewerOpts = Object.assign({
    navbar: imgEls.length > 1,
    loop: false,
    zoomRatio: 0.5,
    minZoomRatio: 0.1,
    maxZoomRatio: 1.5,

    viewed() {
      this.viewer.tooltip();
    },

    // 销毁
    hide() {
      _classPrivateFieldLooseBase(self, _viewer)[_viewer] = undefined;
    },

    hidden() {
      this.viewer.destroy();
    }

  }, viewerOpts);
  const viewer = new (external_Viewer_default())(container, viewerOpts);
  viewer.show();
  warn('viewer:', container, viewer);
  return viewer;
}
;// CONCATENATED MODULE: ./src/utils/visibility-state.ts
/**
 * 页面 visible 时执行 setInterval
 * 参数同 setInterval,返回终止函数
 */
function onVisible(callback, delay = 500, ...rest) {
  let intervalId;

  function listener() {
    clearInterval(intervalId);
    if (document.visibilityState === 'hidden') return; // eslint-disable-next-line n/no-callback-literal

    callback(...rest);
    intervalId = setInterval(callback, delay, ...rest);
  }

  listener();
  document.addEventListener('visibilitychange', listener);
  return function abort() {
    clearInterval(intervalId);
    document.removeEventListener('visibilitychange', listener);
  };
}
;// CONCATENATED MODULE: ./src/scripts/pixiv/pixels.ts


function attachPixels(el, options) {
  const ws = new WeakSet();
  onVisible(() => {
    if (!options.includePathname.test(location.pathname)) return;
    $$(el).forEach(img => {
      if (ws.has(img)) return; // 获取原尺寸

      let [width, height] = [img.getAttribute('width'), img.getAttribute('height')];
      if (width === null || height === null) return;
      [width, height] = [+width, +height];
      img.parentElement.style.position = 'relative';
      const elem = createPixelsElement(img.parentElement);
      elem.innerText = `${width} × ${height} (${calcRectCoincide(width, height).percent})`;
      ws.add(img);
    });
  });
}

function createPixelsElement(parentElement) {
  const classname = 'artwork-pixels';

  for (const child of parentElement.children) {
    if (child.classList.contains(classname)) return child;
  } // 没有则插入一个


  const elem = document.createElement('span');
  elem.classList.add(classname);
  elem.style.cssText = ['position: absolute', 'z-index: 1', 'top: 32px', 'right: 8px', 'padding: 0 4px', 'border-radius: 8px', 'font-size: 12px', 'line-height: initial', 'color: #fff', 'background: rgb(0 0 0 / 0.32)'].join(';');
  parentElement.prepend(elem);
  return elem;
} // 计算图片与屏幕吻合度


function calcRectCoincide(width, height) {
  const {
    width: sw,
    height: sh
  } = window.screen;
  const rectRate = width / height;
  const screenRate = sw / sh;
  let rate;

  if (rectRate >= screenRate) {
    rate = screenRate / rectRate;
  } else {
    rate = rectRate / screenRate;
  } // 图片小于屏幕尺寸,降低值


  if (width < sw && height < sh) {
    rate *= width / sw * (height / sh);
  } // 符合屏幕比例且超过屏幕尺寸的图片,提高值
  // 接近比例也算符合


  if (rate >= 0.99) {
    if (width > sw) {
      rate *= width / sw;
    } else if (height > sh) {
      rate *= height / sh;
    }
  }

  return {
    rate,
    percent: (rate * 100).toFixed(0) + '%'
  };
}
;// CONCATENATED MODULE: ./src/scripts/pixiv/index.ts

 // eslint-disable-next-line no-new

new Previewer('figure [role="presentation"] a img', {
  includePathname: /^\/artworks\/(\w)+/
});
attachPixels('figure [role="presentation"] a img', {
  includePathname: /^\/artworks\/(\w)+/
});
/******/ })()
;