AniGamer Screenshot Helper

AniGamer Screenshot Tool – supports hotkey capture, burst mode, customizable hotkeys, burst interval settings, and menu language switch between Chinese and English.

// ==UserScript==
// @name         AniGamer Screenshot Helper
// @name:zh-TW   動畫瘋截圖助手
// @name:zh-CN   动画疯截图助手
// @namespace    https://www.tampermonkey.net/
// @version      2.0
// @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       ChatGPT
// @match        https://ani.gamer.com.tw/*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  // 預設快捷鍵
  const DEFAULT_SHORTCUT = 'S';
  // 預設連拍間隔(毫秒)
  const DEFAULT_INTERVAL = 1000;
  // 最小連拍間隔(毫秒)
  const MIN_INTERVAL = 100;

  // 多語系字典
  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):',
      invalidInterval: 'Invalid input. Must be ≥ 100.'
    },
    ZH: {
      langSwitch: '語言 中文',
      setKey: key => `設定快捷鍵(目前:${key})`,
      setInterval: ms => `設定連拍間隔(目前:${ms}ms)`,
      inputKey: '請輸入新的截圖快捷鍵(單一字母):',
      inputInterval: '請輸入新的連拍間隔(單位:毫秒,最小值:100):',
      invalidInterval: '請輸入一個不小於 100 的有效數字。'
    }
  };

  // 取得目前設定(語言、快捷鍵、連拍間隔)
  function getSettings() {
    return {
      lang: GM_getValue('lang', 'EN'),
      shortcutKey: GM_getValue('screenshotKey', DEFAULT_SHORTCUT),
      interval: parseInt(GM_getValue('burstInterval', DEFAULT_INTERVAL), 10)
    };
  }

  // 產生檔名:影片標題_小時_分鐘_秒_毫秒_解析度.png
  function generateFilename(video, canvas) {
    // 補零用
    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);
    // 取得網頁標題並移除非法字元
    const rawTitle = document.title;
    const cleanedTitle = rawTitle.replace(/[\s\\/:*?"<>|]/g, '_');
    // 解析度
    const resolution = `${canvas.width}x${canvas.height}`;
    // 修改命名規則:影片標題_小時_分鐘_秒_毫秒_解析度
    return `${cleanedTitle}_${h}_${m}_${s}_${ms}_${resolution}.png`;
  }

  // 截圖主程式
  function captureScreenshot() {
    const video = document.querySelector('video');
    // 若找不到影片或影片未載入完成則不執行
    if (!video || video.readyState < 2) 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);

    // 轉成 blob 並觸發下載
    canvas.toBlob(blob => {
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = filename;
      a.click();
      // 延遲釋放 blob,避免尚未下載即釋放
      setTimeout(() => URL.revokeObjectURL(a.href), 1000);
    }, 'image/png');
  }

  // 綁定快捷鍵與連拍事件
  (function bindHotkeys() {
    let isPressing = false;   // 是否正在按壓
    let burstTimer = null;    // 連拍計時器

    window.addEventListener('keydown', e => {
      const { shortcutKey, interval } = getSettings();
      // 按下自訂快捷鍵且未重複觸發時
      if (e.key.toUpperCase() === shortcutKey.toUpperCase() && !isPressing) {
        isPressing = true;
        captureScreenshot();
        burstTimer = setInterval(captureScreenshot, interval);
      }
    });

    window.addEventListener('keyup', e => {
      const { shortcutKey } = getSettings();
      // 放開自訂快捷鍵時停止連拍
      if (e.key.toUpperCase() === shortcutKey.toUpperCase()) {
        isPressing = false;
        if (burstTimer) clearInterval(burstTimer);
      }
    });
  })();

  // 註冊油猴右上角選單
  (function registerMenu() {
    // 動態取得目前語系與設定
    function t() {
      const { lang, shortcutKey, interval } = getSettings();
      return { ...i18n[lang], shortcutKey, interval, lang };
    }

    // 設定快捷鍵
    GM_registerMenuCommand(t().setKey(t().shortcutKey), () => {
      const input = prompt(t().inputKey, t().shortcutKey);
      // 僅接受單一字元
      if (input && input.length === 1) {
        GM_setValue('screenshotKey', input.toUpperCase());
        location.reload(); // 直接重新整理,不跳提示
      }
    });

    // 設定連拍間隔
    GM_registerMenuCommand(t().setInterval(t().interval), () => {
      const input = prompt(t().inputInterval, t().interval);
      const newVal = parseInt(input, 10);
      if (!isNaN(newVal) && newVal >= MIN_INTERVAL) {
        GM_setValue('burstInterval', newVal);
        location.reload(); // 直接重新整理,不跳提示
      } else {
        alert(t().invalidInterval); // 僅錯誤時提示
      }
    });

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

})();