bilibili-screenshot

快速获取Bilibili当前视频的截图并复制到剪贴板

// ==UserScript==
// @name        bilibili-screenshot
// @description 快速获取Bilibili当前视频的截图并复制到剪贴板
// @namespace   github.com/frostime
// @match       *://www.bilibili.com/video/*
// @icon        https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @version     0.2.3
// @author      frostime
// @license     MIT
// @grant       none
// ==/UserScript==
(function () {
    'use strict';

    // 创建按钮组容器
    const createButtonGroup = () => {
        const container = document.createElement('div');
        container.style.position = 'fixed';
        container.style.bottom = '20px';
        container.style.right = '20px';
        container.style.zIndex = '9999';
        // 创建按钮容器
        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.flexDirection = 'column';
        buttonContainer.style.gap = '10px';
        buttonContainer.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
        buttonContainer.style.transformOrigin = 'bottom right';
        buttonContainer.style.opacity = '0';
        buttonContainer.style.transform = 'scale(0.8) translateY(-10px)';
        buttonContainer.style.pointerEvents = 'none'; // 初始状态不可点击
        buttonContainer.style.position = 'absolute';
        buttonContainer.style.bottom = '50px'; // 在折叠按钮上方留出空间
        buttonContainer.style.right = '0';
        buttonContainer.style.width = '120px'; // 设置固定宽度
        // 创建折叠/展开按钮
        const toggleButton = document.createElement('button');
        toggleButton.innerHTML = '⚙️'; // 使用齿轮图标
        toggleButton.style.width = '40px';
        toggleButton.style.height = '40px';
        toggleButton.style.borderRadius = '50%';
        toggleButton.style.backgroundColor = '#00A1D6';
        toggleButton.style.color = '#fff';
        toggleButton.style.border = 'none';
        toggleButton.style.cursor = 'pointer';
        toggleButton.style.fontSize = '20px';
        toggleButton.style.position = 'absolute';
        toggleButton.style.bottom = '0';
        toggleButton.style.right = '0';
        toggleButton.style.transition = 'transform 0.3s ease';
        toggleButton.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)';
        let isExpanded = false;
        toggleButton.addEventListener('click', () => {
            isExpanded = !isExpanded;
            if (isExpanded) {
                buttonContainer.style.opacity = '1';
                buttonContainer.style.transform = 'scale(1) translateY(0)';
                buttonContainer.style.pointerEvents = 'auto';
                toggleButton.style.transform = 'rotate(180deg)';
            }
            else {
                buttonContainer.style.opacity = '0';
                buttonContainer.style.transform = 'scale(0.8) translateY(-10px)';
                buttonContainer.style.pointerEvents = 'none';
                toggleButton.style.transform = 'rotate(0deg)';
            }
        });
        container.appendChild(buttonContainer);
        container.appendChild(toggleButton);
        return {
            container,
            buttonContainer
        };
    };
    // 创建单个按钮
    const createButton = (config) => {
        const button = document.createElement('button');
        button.innerText = config.text;
        button.style.padding = '8px 12px';
        button.style.backgroundColor = config.backgroundColor || '#00A1D6';
        button.style.color = '#fff';
        button.style.border = 'none';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';
        button.style.fontSize = '12px';
        button.style.width = '100%'; // 让按钮填满容器宽度
        button.style.whiteSpace = 'nowrap'; // 防止文字换行
        button.style.textAlign = 'center'; // 文字居中
        button.addEventListener('click', config.onClick);
        return button;
    };
    // 复制视频截图
    const copyScreenshot = async () => {
        const video = document.querySelector('video');
        if (!video) {
            showMessage('无法找到视频元素');
            return;
        }
        const canvas = document.createElement('canvas');
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        const context = canvas.getContext('2d');
        if (context) {
            context.drawImage(video, 0, 0, canvas.width, canvas.height);
            canvas.toBlob(async (blob) => {
                if (blob) {
                    try {
                        await navigator.clipboard.write([
                            new ClipboardItem({
                                'image/png': blob
                            })
                        ]);
                        showMessage('截图已复制到剪贴板!');
                    }
                    catch (err) {
                        console.error('复制截图失败: ', err);
                        showMessage('复制截图失败,请检查浏览器支持的权限。');
                    }
                }
            }, 'image/png');
        }
    };
    // 复制分享链接
    const copyShareLink = async (timestamp = true) => {
        // 1. 获取当前 URL
        const currentUrl = new URL(window.location.href);
        const baseUrl = currentUrl.origin + currentUrl.pathname;
        // h1.video-title 
        let title = document.querySelector('h1.video-title')?.textContent;
        // 替换 title 内部的 [ ] 符号,防止 markdown 格式化错误,替换为 【 】
        title = title?.replace(/\[([^\]]+)\]/g, '【$1】');
        const copyLink = (text) => {
            navigator.clipboard.writeText(text);
            showMessage('复制分享链接');
        };
        if (timestamp === false) {
            const text = `[${title}](${baseUrl})`;
            copyLink(text);
            return;
        }
        // 获取视频时间戳 div.bpx-player-ctrl-time-label span.bpx-player-ctrl-time-current
        const current = document.querySelector('div.bpx-player-ctrl-time-label span.bpx-player-ctrl-time-current')?.textContent;
        if (!current) {
            showMessage('无法找到视频时间戳');
            return;
        }
        const parts = current.split(':');
        let time = 0;
        if (parts.length === 3) {
            time = parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]);
        }
        else {
            time = parseInt(parts[0]) * 60 + parseInt(parts[1]);
        }
        const text = `[空降到 ${current}](${baseUrl}?t=${time})`;
        copyLink(text);
    };
    // 消息框显示功能
    const showMessage = (message) => {
        const messageBox = document.createElement('div');
        messageBox.innerText = message;
        messageBox.style.position = 'fixed';
        messageBox.style.bottom = '70px';
        messageBox.style.right = '20px';
        messageBox.style.zIndex = '9999';
        messageBox.style.padding = '10px 20px';
        messageBox.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
        messageBox.style.color = '#fff';
        messageBox.style.borderRadius = '5px';
        messageBox.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)';
        messageBox.style.fontSize = '14px';
        messageBox.style.transition = 'opacity 0.5s';
        messageBox.style.opacity = '1';
        document.body.appendChild(messageBox);
        setTimeout(() => {
            messageBox.style.opacity = '0';
            setTimeout(() => {
                messageBox.remove();
            }, 250);
        }, 1500);
    };
    // 初始化按钮组
    const { container, buttonContainer } = createButtonGroup();
    // 创建截图按钮
    const screenshotButton = createButton({
        text: '截图并复制',
        onClick: copyScreenshot
    });
    // 创建分享链接按钮
    const shareLinkButton = createButton({
        text: '视频分享链接',
        onClick: () => copyShareLink(false),
        backgroundColor: '#FF6699'
    });
    const preciseJumpButton = createButton({
        text: '精准空降链接',
        onClick: () => copyShareLink(true),
        backgroundColor: '#FF6699'
    });
    // 添加按钮到按钮容器
    buttonContainer.appendChild(screenshotButton);
    buttonContainer.appendChild(shareLinkButton);
    buttonContainer.appendChild(preciseJumpButton);
    // 将按钮组添加到页面
    document.body.appendChild(container);

})();
//# sourceMappingURL=bundle.user.js.map