Tools Extension

tools插件

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Tools Extension
// @namespace    https://github.com/aoi-umi
// @version      0.0.3
// @description  tools插件
// @author       aoi-umi
// @match        http://*/*
// @match        https://*/*
// @grant        none
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @license MIT
// ==/UserScript==
(function () {
	'use strict';

	function getDefaultExportFromCjs (x) {
		return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
	}

	var lib = {};

	var utils = {};

	var hasRequiredUtils;

	function requireUtils () {
		if (hasRequiredUtils) return utils;
		hasRequiredUtils = 1;
		Object.defineProperty(utils, "__esModule", { value: true });
		utils.throttle = throttle;
		utils.isElementAtPoint = isElementAtPoint;
		utils.formatTime = formatTime;
		function throttle(func, wait, options) {
		    let context, args, result;
		    let timeout = null;
		    let previous = 0;
		    if (!options)
		        options = {
		            trailing: false,
		        };
		    let later = function () {
		        previous = options.leading === false ? 0 : Date.now();
		        timeout = null;
		        result = func.apply(context, args);
		        if (!timeout)
		            context = args = null;
		    };
		    return function () {
		        let now = Date.now();
		        if (!previous && options.leading === false)
		            previous = now;
		        let remaining = wait - (now - previous);
		        context = this;
		        args = arguments;
		        if (remaining <= 0 || remaining > wait) {
		            if (timeout) {
		                clearTimeout(timeout);
		                timeout = null;
		            }
		            previous = now;
		            result = func.apply(context, args);
		            if (!timeout)
		                context = args = null;
		        }
		        else if (!timeout && options.trailing !== false) {
		            timeout = setTimeout(later, remaining);
		        }
		        return result;
		    };
		}
		function isElementAtPoint(el, x, y) {
		    var rect = el.getBoundingClientRect();
		    return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
		}
		function formatTime(seconds) {
		    const h = Math.floor(seconds / 3600);
		    const m = Math.floor((seconds % 3600) / 60);
		    const s = Math.floor(seconds % 60);
		    let hourStr = "";
		    if (h > 0) {
		        hourStr = `${String(h).padStart(2, "0")}:`;
		    }
		    return `${hourStr}${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
		}
		
		return utils;
	}

	var menu = {};

	var icons = {};

	var ArrowRight = {};

	var hasRequiredArrowRight;

	function requireArrowRight () {
		if (hasRequiredArrowRight) return ArrowRight;
		hasRequiredArrowRight = 1;
		Object.defineProperty(ArrowRight, "__esModule", { value: true });
		ArrowRight.default = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
  <path
    fill="currentColor"
    d="M340.864 149.312a30.59 30.59 0 0 0 0 42.752L652.736 512 340.864 831.872a30.59 30.59 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z"
  ></path>
</svg>
`;
		
		return ArrowRight;
	}

	var hasRequiredIcons;

	function requireIcons () {
		if (hasRequiredIcons) return icons;
		hasRequiredIcons = 1;
		(function (exports$1) {
			Object.defineProperty(exports$1, "__esModule", { value: true });
			exports$1.ArrowRight = void 0;
			var ArrowRight_1 = requireArrowRight();
			Object.defineProperty(exports$1, "ArrowRight", { enumerable: true, get: function () { return ArrowRight_1.default; } });
			
		} (icons));
		return icons;
	}

	var hasRequiredMenu;

	function requireMenu () {
		if (hasRequiredMenu) return menu;
		hasRequiredMenu = 1;
		Object.defineProperty(menu, "__esModule", { value: true });
		menu.Menu = void 0;
		const utils = requireUtils();
		const icons_1 = requireIcons();
		class Menu {
		    get floatMenuName() {
		        return `${this.prefix}float-menu`;
		    }
		    get defaultMenuName() {
		        return `${this.prefix}default-menu`;
		    }
		    get contextMenuName() {
		        return `${this.prefix}context-menu`;
		    }
		    get contextMenuItemName() {
		        return `${this.prefix}context-menu-item`;
		    }
		    get moreName() {
		        return `${this.prefix}more`;
		    }
		    get iconName() {
		        return `${this.prefix}icon`;
		    }
		    constructor(opt) {
		        this.prefix = "";
		        this.isUsingOriginalMenu = false;
		        this.isTriggeringContextMenu = false;
		        opt = { ...opt };
		        this.prefix = opt.prefix || "";
		        this.initStyle();
		        this.initEvents();
		    }
		    initStyle() {
		        let { floatMenuName, defaultMenuName, contextMenuName, moreName, iconName, } = this;
		        let style = jQuery("<style></style>").text(`
      .${floatMenuName} {
      display: none;
      min-width: 100px;
      min-height: 20px;
      position: fixed;
      z-index: 900000;
    }
    .${defaultMenuName} {
      background: white;
      padding: 15px 15px;
      cursor: pointer;
      border-radius: 5px;
      box-shadow: 1px 1px 5px #888888;
    }
    .${contextMenuName} {
      padding: 5px 0;
      font-size: 12px;
      color: black;
      text-shadow: none;
    }
    .${contextMenuName} > * {
      padding: 5px 10px;
      display: flex;
      align-items: center;
    }

    .${moreName} {
      font-size: 16px;
    }
    .${iconName} {
      height: 1em;
      width: 1em;
      line-height: 1em;
    }
    `);
		        jQuery("head").append(style);
		    }
		    initEvents() {
		        let that = this;
		        jQuery(document).on("click", function () {
		            that.getMenus().remove();
		        });
		        jQuery(document).on("click mouseenter mouseleave", `.${this.contextMenuItemName}`, function (e) {
		            var _a;
		            let dom = jQuery(this);
		            let item = dom.data("item");
		            if (e.type === "click")
		                that.handleMenuItemClick(item);
		            else if (e.type === "mouseenter") {
		                dom.css("background-color", "#bec8ff");
		                let level = dom.parent(`.${that.contextMenuName}`).data("level");
		                if ((_a = item.children) === null || _a === void 0 ? void 0 : _a.length) {
		                    let pos = {
		                        x: dom.offset().left + dom.outerWidth(),
		                        y: dom.offset().top,
		                    };
		                    that.createMenu(item.children, pos, level + 1);
		                }
		                else {
		                    that.getMenus().each(function () {
		                        let menuDom = jQuery(this);
		                        if (menuDom.data("level") > level) {
		                            menuDom.hide();
		                        }
		                    });
		                }
		            }
		            else if (e.type === "mouseleave")
		                dom.css("background-color", "");
		        });
		    }
		    getClickPos(event) {
		        return {
		            x: event.x || 0,
		            y: event.y || 0,
		        };
		    }
		    showContextMenu(opt) {
		        let { event } = opt;
		        if (!event)
		            return;
		        let pos = this.getClickPos(event);
		        let list = [];
		        // list.push({
		        //   text: "测试",
		        //   key: "test",
		        //   data: "test",
		        // });
		        if (opt.type === "video") {
		            let video = opt.el || event.target;
		            list.push({
		                text: "跳op/ed",
		                key: "skipOpOrEd",
		                fn: () => {
		                    let time = 80;
		                    video.currentTime += time;
		                },
		            });
		            list.push({
		                text: "倍速",
		                key: "playSpeed",
		                children: [0.5, 1, 1.3, 1.5, 2, 4].map((ele) => {
		                    return {
		                        text: `${ele}`,
		                        key: `playSpeed_${ele}`,
		                        fn: () => {
		                            video.playbackRate = ele;
		                        },
		                    };
		                }),
		            });
		            let currentTimeStr = utils.formatTime(Math.round(video.currentTime));
		            list.push({
		                text: `复制当前时间 (${currentTimeStr})`,
		                key: "copyCurrentTime",
		                fn: () => {
		                    navigator.clipboard.writeText(currentTimeStr);
		                },
		            });
		        }
		        else if (opt.type === "danmaku") {
		            let video = opt.el || event.target;
		            list.push({
		                text: `空降到 ${opt.data.timeStr}`,
		                key: "setCurrentTime",
		                fn: () => {
		                    video.currentTime = opt.data.time;
		                },
		            });
		        }
		        list.push({
		            text: "原菜单",
		            key: "originalMenu",
		            fn: () => {
		                this.isUsingOriginalMenu = true;
		                this.conetextMenuHandler({ event });
		            },
		        });
		        list.push({
		            text: "关闭",
		            key: "close",
		        });
		        if (list.length) {
		            this.createMenu(list, pos);
		            return true;
		        }
		    }
		    getMenus() {
		        return jQuery(`.${this.contextMenuName}`);
		    }
		    getMenu(level = 0) {
		        let menu = jQuery(`.${this.contextMenuName}[data-level=${level}]`);
		        if (!menu.length) {
		            menu = jQuery(`<div class="${this.contextMenuName} ${this.floatMenuName} ${this.defaultMenuName}" data-level=${level}></div>`);
		            let target = document.fullscreenElement || document.body;
		            jQuery(target).append(menu);
		        }
		        return menu;
		    }
		    createMenu(item, pos, level = 0) {
		        let menu = this.getMenu(level);
		        menu
		            .css({
		            left: pos.x + "px",
		            top: pos.y + "px",
		        })
		            .empty()
		            .append(item.map((ele) => {
		            var _a;
		            let dom = jQuery([
		                `<div class="${this.contextMenuItemName}">`,
		                `${ele.text}`,
		                ((_a = ele.children) === null || _a === void 0 ? void 0 : _a.length) &&
		                    `<span style="flex: 1;"></span><i class="${this.moreName} ${this.iconName}">${icons_1.ArrowRight}</i>`,
		                "</div>",
		            ].join(""));
		            dom.data("item", ele);
		            return dom;
		        }))
		            .show();
		    }
		    handleMenuItemClick(item) {
		        item.fn && item.fn();
		    }
		    conetextMenuHandler(opt) {
		        if (this.isUsingOriginalMenu) {
		            this.triggerContextMenu(opt.event);
		            this.isUsingOriginalMenu = false;
		            return true;
		        }
		        opt.event.stopPropagation();
		        opt.event.preventDefault();
		        this.showContextMenu(opt);
		    }
		    triggerContextMenu(event) {
		        var _a;
		        if (this.isTriggeringContextMenu)
		            return;
		        this.isTriggeringContextMenu = true;
		        const newEvent = new PointerEvent("contextmenu", {
		            bubbles: true,
		            cancelable: true,
		            clientX: event.clientX,
		            clientY: event.clientY,
		        });
		        (_a = event.target) === null || _a === void 0 ? void 0 : _a.dispatchEvent(newEvent);
		        this.isTriggeringContextMenu = false;
		    }
		}
		menu.Menu = Menu;
		
		return menu;
	}

	var hasRequiredLib;

	function requireLib () {
		if (hasRequiredLib) return lib;
		hasRequiredLib = 1;
		Object.defineProperty(lib, "__esModule", { value: true });
		jQuery.noConflict();
		const utils = requireUtils();
		const menu_1 = requireMenu();
		class ToolsExtension {
		    constructor() {
		        this.prefix = "tools-";
		        this.init();
		    }
		    init() {
		        // 等待原网站css js加载
		        setTimeout(() => {
		            this.menu = new menu_1.Menu({ prefix: this.prefix });
		            this.bindEvents();
		            console.log("ToolsExtension initialized");
		        }, 1000);
		    }
		    getVideoAtPoint(pos) {
		        return jQuery("video")
		            .toArray()
		            .find((video) => {
		            return utils.isElementAtPoint(video, pos.x, pos.y);
		        });
		    }
		    getDanmakuAtPoint(pos) {
		        let danmaku;
		        // 获取弹幕容器
		        ["danmaku", "danmuku"].find((key) => {
		            let dom = jQuery(`[class*="${key}"]:eq(0)`)[0];
		            if (dom) {
		                danmaku = {
		                    key,
		                    dom,
		                };
		                return true;
		            }
		        });
		        if (!danmaku)
		            return;
		        // 同级或子级查找弹幕
		        return [
		            ...jQuery(danmaku.dom).siblings(":visible").toArray(),
		            ...jQuery(danmaku.dom).children(":visible").toArray(),
		        ].find((el) => {
		            let dom = jQuery(el);
		            return (parseFloat(dom.css("opacity")) > 0 &&
		                el.clientHeight < 50 &&
		                utils.isElementAtPoint(el, pos.x, pos.y));
		        });
		    }
		    bindEvents() {
		        document.addEventListener("contextmenu", (event) => {
		            let isVideo = jQuery(event.target).is("video");
		            let _video = this.getVideoAtPoint(event);
		            let video = isVideo ? event.target : _video;
		            if (!video)
		                return;
		            let opt = { event, type: "video", el: video, data: null };
		            let danmaku = this.getDanmakuAtPoint(event);
		            if (danmaku) {
		                let matchRs = /((?<hour>[\d]+):)?(?<minute>[\d]+):(?<second>[\d]+)/.exec(danmaku.innerText);
		                if (matchRs && matchRs.groups) {
		                    let hour = parseInt(matchRs.groups.hour || "0");
		                    let minute = parseInt(matchRs.groups.minute);
		                    let second = parseInt(matchRs.groups.second);
		                    // 不用管格式,直接加总秒数
		                    let time = hour * 3600 + minute * 60 + second;
		                    opt.type = "danmaku";
		                    opt.data = {
		                        timeStr: `${matchRs[0]}`,
		                        time,
		                    };
		                }
		            }
		            this.menu.conetextMenuHandler(opt);
		        }, true // 使用捕获阶段
		        );
		    }
		}
		window.tools = new ToolsExtension();
		
		return lib;
	}

	var libExports = requireLib();
	var index = /*@__PURE__*/getDefaultExportFromCjs(libExports);

	return index;

})();