Tools Extension

tools插件

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Tools Extension
// @namespace    https://github.com/aoi-umi
// @version      0.0.5
// @description  tools插件
// @author       aoi-umi
// @match        http://*/*
// @match        https://*/*
// @run-at       document-end
// @grant        unsafeWindow
// @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 ToolsExtension = {};

	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;
		utils.createHtmlElement = createHtmlElement;
		utils.svgToDataURL = svgToDataURL;
		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")}`;
		}
		function createHtmlElement(options) {
		    const dom = document.createElement(options.tag || "div");
		    if (options.attrs) {
		        Object.keys(options.attrs).forEach((key) => {
		            dom.setAttribute(key, options.attrs[key]);
		        });
		    }
		    if (options.textContent) {
		        dom.textContent = options.textContent;
		    }
		    if (options.children) {
		        options.children.forEach((child) => {
		            dom.appendChild(createHtmlElement(child));
		        });
		    }
		    return dom;
		}
		function svgToDataURL(svgString) {
		    const encodedSVG = encodeURIComponent(svgString)
		        .replace(/'/g, "%27") // 替换单引号
		        .replace(/"/g, "%22"); // 替换双引号
		    return `data:image/svg+xml,${encodedSVG}`;
		}
		
		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: 9999999999;
    }
    .${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 rect = dom[0].getBoundingClientRect();
		                    let pos = {
		                        x: rect.right,
		                        y: rect.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) {
		            let target = document.fullscreenElement || document.body;
		            const menuEle = utils.createHtmlElement({
		                attrs: {
		                    class: [
		                        this.contextMenuName,
		                        this.floatMenuName,
		                        this.defaultMenuName,
		                    ].join(" "),
		                    "data-level": level.toString(),
		                },
		            });
		            target.appendChild(menuEle);
		            menu = jQuery(menuEle);
		        }
		        return menu;
		    }
		    createMenu(item, pos, level = 0) {
		        let menu = this.getMenu(level);
		        menu
		            .css({
		            left: pos.x + "px",
		            top: pos.y + "px",
		        })
		            .empty();
		        item.forEach((ele) => {
		            var _a;
		            const dom = utils.createHtmlElement({
		                textContent: [`${ele.text}`].join(""),
		                attrs: {
		                    class: `${this.contextMenuItemName}`,
		                },
		                children: !((_a = ele.children) === null || _a === void 0 ? void 0 : _a.length)
		                    ? null
		                    : [
		                        {
		                            tag: "span",
		                            attrs: { style: "flex:1;" },
		                        },
		                        {
		                            tag: "img",
		                            attrs: {
		                                class: `${this.moreName} ${this.iconName}`,
		                                src: utils.svgToDataURL(icons_1.ArrowRight),
		                            },
		                        },
		                    ],
		            });
		            jQuery(dom).data("item", ele);
		            menu[0].appendChild(dom);
		        });
		        menu.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 hasRequiredToolsExtension;

	function requireToolsExtension () {
		if (hasRequiredToolsExtension) return ToolsExtension;
		hasRequiredToolsExtension = 1;
		Object.defineProperty(ToolsExtension, "__esModule", { value: true });
		ToolsExtension.ToolsExtension = void 0;
		const utils = requireUtils();
		const menu_1 = requireMenu();
		let ToolsExtension$1 = class ToolsExtension {
		    constructor() {
		        this.prefix = "tools-";
		        this.init();
		    }
		    init() {
		        this.menu = new menu_1.Menu({ prefix: this.prefix });
		        this.bindEvents();
		        console.log("ToolsExtension initialized on", location.href);
		    }
		    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 text = danmaku.innerText || "";
		                text = text.replace(/:/g, ":");
		                let matchRs = /((?<hour>[\d]+):)?(?<minute>[\d]+):(?<second>[\d]+)/.exec(text);
		                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);
		    }
		};
		ToolsExtension.ToolsExtension = ToolsExtension$1;
		
		return ToolsExtension;
	}

	var hasRequiredLib;

	function requireLib () {
		if (hasRequiredLib) return lib;
		hasRequiredLib = 1;
		Object.defineProperty(lib, "__esModule", { value: true });
		jQuery.noConflict();
		const ToolsExtension_1 = requireToolsExtension();
		window.tools = new ToolsExtension_1.ToolsExtension();
		
		return lib;
	}

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

	return index;

})();