// ==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;
}
});
})();