Bilibili 直播截图助手

B站直播截图工具,支援快捷键截图、连拍功能,自定义快捷键、连拍间隔设定、中英菜单切换

安装此脚本?
作者推荐脚本

您可能也喜欢Bilibili 视频截图助手

安装此脚本
// ==UserScript==
// @name         Bilibili Live Screenshot Helper
// @name:zh-TW   Bilibili 直播截圖助手
// @name:zh-CN   Bilibili 直播截图助手
// @namespace    https://www.tampermonkey.net/
// @version      2.4
// @description  Bilibili Live Screenshot Tool – supports hotkey capture, burst mode, customizable hotkeys, burst interval settings, and menu language switch between Chinese and English.
// @description:zh-TW B站直播截圖工具,支援快捷鍵截圖、連拍功能,自定義快捷鍵、連拍間隔設定、中英菜單切換 
// @description:zh-CN B站直播截图工具,支援快捷键截图、连拍功能,自定义快捷键、连拍间隔设定、中英菜单切换
// @author       ChatGPT
// @match        https://live.bilibili.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  // === 預設設定 ===
  const DEFAULT_KEY = 'S';           // 預設截圖快捷鍵
  const DEFAULT_INTERVAL = 1000;     // 預設連拍間隔(毫秒)
  const MIN_INTERVAL = 100;          // 最小連拍間隔(毫秒)
  const SETTINGS_LOCK_KEY = 'screenshotHelperSettingsLock'; // prompt 彈窗鎖

  // === 多語言設定 ===
  const LANGS = {
    EN: {
      screenshot: 'Screenshot',
      keySetting: key => `Set Screenshot Key (Current: ${key})`,
      intervalSetting: val => `Set Burst Interval (Current: ${val}ms)`,
      langToggle: 'Language: EN',
      keyPrompt: 'Enter new key (A-Z)',
      intervalPrompt: 'Enter new interval in ms (>= 100)',
    },
    ZH: {
      screenshot: '截圖',
      keySetting: key => `設定截圖快捷鍵(目前:${key})`,
      intervalSetting: val => `設定連拍間隔(目前:${val} 毫秒)`,
      langToggle: '語言:中文',
      keyPrompt: '輸入新快捷鍵(A-Z)',
      intervalPrompt: '輸入新的連拍間隔(最小 100ms)',
    }
  };

  // === 取得/儲存設定值的封裝 ===
  function getSetting(key, def) {
    return GM_getValue(key, def);
  }
  function setSetting(key, val) {
    GM_setValue(key, val);
  }

  // === 讀取目前設定 ===
  let lang = getSetting('lang', 'EN');                    // 目前語言
  let currentKey = getSetting('hotkey', DEFAULT_KEY);     // 目前截圖快捷鍵
  let interval = getSetting('interval', DEFAULT_INTERVAL);// 目前連拍間隔

  // === 取得目前語言包 ===
  function t() {
    return LANGS[lang];
  }

  // === prompt 彈窗鎖,避免多重彈窗 ===
  async function promptWithLock(action) {
    if (localStorage.getItem(SETTINGS_LOCK_KEY) === '1') return;
    localStorage.setItem(SETTINGS_LOCK_KEY, '1');
    await new Promise(resolve => setTimeout(resolve, 0));
    localStorage.removeItem(SETTINGS_LOCK_KEY);
    action();
  }

  // === 註冊油猴右上角選單 ===
  function registerMenu() {
    // 快捷鍵設定
    GM_registerMenuCommand(t().keySetting(currentKey), () => {
      promptWithLock(() => {
        const input = prompt(t().keyPrompt, currentKey);
        if (input && /^[a-zA-Z]$/.test(input)) {
          const newKey = input.toUpperCase();
          if (newKey !== currentKey) {
            setSetting('hotkey', newKey);
            location.reload();
          }
        }
      });
    });

    // 連拍間隔設定
    GM_registerMenuCommand(t().intervalSetting(interval), () => {
      promptWithLock(() => {
        const input = prompt(t().intervalPrompt, interval);
        const val = parseInt(input, 10);
        if (!isNaN(val) && val >= MIN_INTERVAL && val !== interval) {
          setSetting('interval', val);
          location.reload();
        }
      });
    });

    // 語言切換
    GM_registerMenuCommand(t().langToggle, () => {
      promptWithLock(() => {
        const newLang = lang === 'EN' ? 'ZH' : 'EN';
        setSetting('lang', newLang);
        location.reload();
      });
    });
  }

  registerMenu();

  // === 產生截圖檔名(以本地時間為主) ===
  function generateFilename(video, canvas) {
    const pad = n => n.toString().padStart(2, '0');
    const padMs = n => n.toString().padStart(3, '0');
    const now = new Date();
    const h = pad(now.getHours());
    const m = pad(now.getMinutes());
    const s = pad(now.getSeconds());
    const ms = padMs(now.getMilliseconds());
    const year = now.getFullYear();
    const month = pad(now.getMonth() + 1);
    const day = pad(now.getDate());
    const roomIdMatch = location.pathname.match(/^\/(\d+)/);
    const roomId = roomIdMatch ? roomIdMatch[1] : 'UnknownRoom';
    // 新命名規則:[直播間ID]_年月日_小時_分鐘_秒_毫秒_解析度.png
    // 例如:123456_20240608_14_30_15_123_1920x1080.png
    return `[${roomId}]_${year}${month}${day}_${h}_${m}_${s}_${ms}_${canvas.width}x${canvas.height}.png`;
  }

  // === 截圖主函數 ===
  function takeScreenshot() {
    const video = document.querySelector('video');
    // 檢查 video 是否存在且可用
    if (!video || video.readyState < 2 || video.videoWidth === 0) return;

    // 建立 canvas 並繪製當前影像
    const canvas = document.createElement('canvas');
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

    // 產生檔名
    const filename = generateFilename(video, canvas);

    // 下載圖片
    canvas.toBlob(blob => {
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = filename;
      a.style.display = 'none';
      document.body.appendChild(a);
      a.click();
      setTimeout(() => {
        URL.revokeObjectURL(url);
        a.remove();
      }, 100);
    }, 'image/png');
  }

  // === 快捷鍵與連拍監控 ===
  let holdTimer = null;   // 連拍計時器
  let isKeyDown = false;  // 是否已按下快捷鍵

  // 按下快捷鍵時觸發截圖與連拍
  document.addEventListener('keydown', e => {
    if (isKeyDown) return; // 防止重複觸發
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) return;
    if (e.key.toUpperCase() === currentKey) {
      isKeyDown = true;
      takeScreenshot();
      holdTimer = setInterval(takeScreenshot, interval);
    }
  });

  // 放開快捷鍵時停止連拍
  document.addEventListener('keyup', e => {
    if (e.key.toUpperCase() === currentKey && holdTimer) {
      clearInterval(holdTimer);
      holdTimer = null;
      isKeyDown = false;
    }
  });

  // 視窗失焦時自動停止連拍
  window.addEventListener('blur', () => {
    if (holdTimer) {
      clearInterval(holdTimer);
      holdTimer = null;
      isKeyDown = false;
    }
  });
})();