Greasy Fork

Greasy Fork is available in English.

动画疯截图助手

动画疯截图工具,支援快捷键截图、连拍模式、支援自定义快捷键、连拍间隔设定、中英菜单切换

目前为 2025-04-15 提交的版本,查看 最新版本

// ==UserScript==
// @name         AniGamer Screenshot Helper
// @name:zh-TW   動畫瘋截圖助手
// @name:zh-CN   动画疯截图助手
// @namespace    https://ani.gamer.com.tw/
// @version      1.5
// @description  AniGamer Screenshot Tool – supports hotkey capture, burst mode, customizable hotkeys, burst interval settings, and menu language switch between Chinese and English.
// @description:zh-TW 動畫瘋截圖工具,支援快捷鍵截圖、連拍模式、支援自定義快捷鍵、連拍間隔設定、中英菜單切換
// @description:zh-CN 动画疯截图工具,支援快捷键截图、连拍模式、支援自定义快捷键、连拍间隔设定、中英菜单切换
// @author       You
// @match        https://ani.gamer.com.tw/*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  const defaultShortcut = 'S';
  const defaultInterval = 1000;
  const minInterval = 100;
  const defaultLang = GM_getValue('lang', 'EN');

  let lang = defaultLang;
  let shortcutKey = GM_getValue('screenshotKey', defaultShortcut);
  let interval = parseInt(GM_getValue('burstInterval', defaultInterval), 10);
  let isPressing = false;
  let burstTimer = null;

  const i18n = {
    EN: {
      langSwitch: 'LANG EN',
      setKey: key => `Set Shortcut Key (Current: ${key})`,
      setInterval: ms => `Set Burst Interval (Current: ${ms}ms)`,
      inputKey: 'Enter a new shortcut key (one character):',
      inputInterval: 'Enter new burst interval in ms (min: 100):',
      updatedKey: 'Shortcut key updated. Please refresh the page.',
      updatedInterval: 'Burst interval updated. Please refresh the page.',
      invalidInterval: 'Invalid input. Must be ≥ 100.'
    },
    ZH: {
      langSwitch: '語言 中文',
      setKey: key => `設定快捷鍵(目前:${key})`,
      setInterval: ms => `設定連拍間隔(目前:${ms}ms)`,
      inputKey: '請輸入新的截圖快捷鍵(單一字母):',
      inputInterval: '請輸入新的連拍間隔(單位:毫秒,最小值:100):',
      updatedKey: '快捷鍵已更新,請重新整理頁面。',
      updatedInterval: '連拍間隔已更新,請重新整理頁面。',
      invalidInterval: '請輸入一個不小於 100 的有效數字。'
    }
  };

  const t = i18n[lang];

  function captureScreenshot() {
    const video = document.querySelector('video');
    if (!video || video.readyState < 2) 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);

    // 播放時間精準到毫秒
    const pad = (n, len = 2) => n.toString().padStart(len, '0');
    const time = video.currentTime;
    const h = pad(Math.floor(time / 3600));
    const m = pad(Math.floor((time % 3600) / 60));
    const s = pad(Math.floor(time % 60));
    const ms = pad(Math.floor((time % 1) * 1000), 3); // 毫秒 3 位數
    const videoTime = `${h}_${m}_${s}_${ms}`;

    // 標題處理(保留完整,移除非法字元)
    const rawTitle = document.title;
    const cleanedTitle = rawTitle.replace(/[\s\\/:*?"<>|]/g, '_');

    const resolution = `${canvas.width}x${canvas.height}`;
    const filename = `${videoTime}_${cleanedTitle}_${resolution}.png`;

    canvas.toBlob(blob => {
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = filename;
      a.click();
      URL.revokeObjectURL(a.href);
    }, 'image/png');
  }

  window.addEventListener('keydown', e => {
    if (e.key.toUpperCase() === shortcutKey.toUpperCase() && !isPressing) {
      isPressing = true;
      captureScreenshot();
      burstTimer = setInterval(() => {
        captureScreenshot();
      }, interval);
    }
  });

  window.addEventListener('keyup', e => {
    if (e.key.toUpperCase() === shortcutKey.toUpperCase()) {
      isPressing = false;
      clearInterval(burstTimer);
    }
  });

  GM_registerMenuCommand(t.setKey(shortcutKey), () => {
    const input = prompt(t.inputKey, shortcutKey);
    if (input && input.length === 1) {
      GM_setValue('screenshotKey', input.toUpperCase());
      alert(t.updatedKey);
    }
  });

  GM_registerMenuCommand(t.setInterval(interval), () => {
    const input = prompt(t.inputInterval, interval);
    const newVal = parseInt(input, 10);
    if (!isNaN(newVal) && newVal >= minInterval) {
      GM_setValue('burstInterval', newVal);
      alert(t.updatedInterval);
    } else {
      alert(t.invalidInterval);
    }
  });

  GM_registerMenuCommand(t.langSwitch, () => {
    const newLang = lang === 'EN' ? 'ZH' : 'EN';
    GM_setValue('lang', newLang);
    location.reload();
  });
})();