// ==UserScript== // @name Twitch Screenshot Helper // @name:zh-TW Twitch 截圖助手 // @name:zh-CN Twitch 截图助手 // @namespace https://yourdomain.com // @version 1.5 // @description Twitch screen capture tool with support for hotkeys, burst mode, customizable shortcuts, capture interval, and English/Chinese menu switching. // @description:zh-TW Twitch 擷取畫面工具,支援快捷鍵、連拍模式、自訂快捷鍵、連拍間隔與中英菜單切換 // @description:zh-CN Twitch 撷取画面工具,支援快捷键、连拍模式、自订快捷键、连拍间隔与中英菜单切换 // @author ChatGPT // @match https://www.twitch.tv/* // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; const lang = GM_getValue("lang", "EN"); // EN or ZH const screenshotKey = GM_getValue("screenshotKey", "s"); const intervalTime = parseInt(GM_getValue("shootInterval", "1000"), 10); let shootTimer = null; const text = { EN: { btnTooltip: `Screenshot (Shortcut: ${screenshotKey.toUpperCase()})`, setKey: `Set Screenshot Key (Current: ${screenshotKey.toUpperCase()})`, setInterval: `Set Interval (Current: ${intervalTime}ms)`, langSwitch: `language EN`, keySuccess: key => `New shortcut key set to: ${key.toUpperCase()}. Please refresh.`, keyError: `Please enter a single letter (A-Z).`, intervalSuccess: ms => `Interval updated to ${ms}ms. Please refresh.`, intervalError: `Please enter a number >= 100`, }, ZH: { btnTooltip: `擷取畫面(快捷鍵:${screenshotKey.toUpperCase()})`, setKey: `設定快捷鍵(目前為 ${screenshotKey.toUpperCase()})`, setInterval: `設定連拍間隔(目前為 ${intervalTime} 毫秒)`, langSwitch: `語言 中文`, keySuccess: key => `操作成功!新快捷鍵為:${key.toUpperCase()},請重新整理頁面以使設定生效。`, keyError: `請輸入單一英文字母(A-Z)!`, intervalSuccess: ms => `間隔時間已更新為:${ms}ms,請重新整理頁面以使設定生效。`, intervalError: `請輸入 100ms 以上的數字!`, } }[lang]; // 取得直播 ID function getStreamerId() { const match = window.location.pathname.match(/^\/([^\/?#]+)/); return match ? match[1] : "unknown"; } // 時間字串:精確到毫秒 function getTimeString() { const now = new Date(); const pad = n => n.toString().padStart(2, '0'); const ms = now.getMilliseconds().toString().padStart(3, '0'); return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}_${pad(now.getMinutes())}_${pad(now.getSeconds())}_${ms}`; } // 擷取畫面 function takeScreenshot() { const video = document.querySelector('video'); if (!video || !video.src) { console.warn("找不到影片"); return; } 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); canvas.toBlob(blob => { if (!blob) return; const timeStr = getTimeString(); const streamer = getStreamerId(); const resolution = `${canvas.width}x${canvas.height}`; const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `${timeStr}_${streamer}_${resolution}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); }, "image/png"); } // 開始連拍 function startContinuousShot() { if (shootTimer) return; takeScreenshot(); // 立即拍一次 shootTimer = setInterval(takeScreenshot, intervalTime); } function stopContinuousShot() { clearInterval(shootTimer); shootTimer = null; } // 插入截圖按鈕 function createIntegratedButton() { if (document.querySelector("#screenshot-btn")) return; const controls = document.querySelector('.player-controls__right-control-group'); if (!controls) return; const btn = document.createElement("button"); btn.id = "screenshot-btn"; btn.innerText = "📸"; btn.title = text.btnTooltip; btn.style.cssText = ` background: transparent; border: none; color: white; font-size: 20px; cursor: pointer; margin-left: 10px; `; btn.addEventListener("mousedown", startContinuousShot); btn.addEventListener("mouseup", stopContinuousShot); btn.addEventListener("mouseleave", stopContinuousShot); controls.appendChild(btn); } function init() { const observer = new MutationObserver(() => { createIntegratedButton(); }); observer.observe(document.body, { childList: true, subtree: true }); } // 判斷是否正在輸入文字(避免快捷鍵誤觸) function isTyping() { const active = document.activeElement; return active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || active.isContentEditable); } // 快捷鍵監聽 document.addEventListener("keydown", (e) => { if (e.key.toLowerCase() === screenshotKey.toLowerCase() && !shootTimer && !isTyping()) { e.preventDefault(); startContinuousShot(); } }); document.addEventListener("keyup", (e) => { if (e.key.toLowerCase() === screenshotKey.toLowerCase() && !isTyping()) { e.preventDefault(); stopContinuousShot(); } }); // 設定功能表 GM_registerMenuCommand(text.setKey, () => { const input = prompt(lang === "EN" ? "Enter new shortcut key (A-Z)" : "請輸入新的快捷鍵(A-Z)", screenshotKey); if (input && /^[a-zA-Z]$/.test(input)) { GM_setValue("screenshotKey", input.toLowerCase()); alert(text.keySuccess(input)); } else { alert(text.keyError); } }); GM_registerMenuCommand(text.setInterval, () => { const input = prompt(lang === "EN" ? "Enter interval in milliseconds (min: 100)" : "請輸入新的連拍間隔(最小100毫秒)", intervalTime); const val = parseInt(input, 10); if (!isNaN(val) && val >= 100) { GM_setValue("shootInterval", val); alert(text.intervalSuccess(val)); } else { alert(text.intervalError); } }); // 語言切換,點擊後直接更新值並重新整理 GM_registerMenuCommand(text.langSwitch, () => { GM_setValue("lang", lang === "EN" ? "ZH" : "EN"); location.reload(); // 自動重新整理頁面 }); init(); })();