一键复制明日方舟一图流所需的森空岛token

脚本用于复制森空岛token到明日方舟一图流中使用,并支持Shift+C快捷键

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name               Copy token from skland for ark.yituliu.cn
// @name:zh-CN         一键复制明日方舟一图流所需的森空岛token
// @namespace          ling921
// @version            0.2.0
// @description        Script to copy skland token to ark.yituliu.cn
// @description:zh-CN  脚本用于复制森空岛token到明日方舟一图流中使用,并支持Shift+C快捷键
// @author             ling921
// @match              https://www.skland.com/*
// @icon               https://ark.yituliu.cn/favicon.ico
// @grant              none
// @run-at             document-idle
// @tag                utilities
// @tag                game
// @license            MIT
// ==/UserScript==

/**
 * 通知管理器类
 */
class NotificationManager {
  static #instance = null;
  static #maxNotifications = 3;
  static #defaultDuration = 3000;
  static #queue = [];
  static #active = new Set();
  static #container = null;

  /**
   * 获取通知管理器实例
   * @param {{
   *  maxNotifications?: number,
   *  duration?: number
   * }} options - 配置选项
   */
  static getInstance(options = {}) {
    if (!NotificationManager.#instance) {
      if (options.maxNotifications && options.maxNotifications > 0) {
        NotificationManager.#maxNotifications = options.maxNotifications;
      }
      if (options.duration && options.duration > 0) {
        NotificationManager.#defaultDuration = options.duration;
      }
      NotificationManager.#instance = new NotificationManager(options);
    }
    return NotificationManager.#instance;
  }

  constructor() {
    if (NotificationManager.#instance) {
      return NotificationManager.#instance;
    }

    NotificationManager.#container = document.getElementById(
      "notification-container"
    );
    if (!NotificationManager.#container) {
      const style = document.createElement("style");
      style.textContent = `
        #notification-container {
          position: fixed;
          top: 20px;
          left: 0;
          right: 0;
          display: flex;
          flex-direction: column;
          align-items: center;
          gap: 12px;
          pointer-events: none;
          z-index: 10000;
        }
        
        #notification-container .notification {
          position: relative;
          padding: 10px 25px;
          padding-right: 35px !important;
          border-radius: 20px;
          font-size: 14px;
          color: white;
          font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
          opacity: 0;
          transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
          text-align: center;
          backdrop-filter: blur(8px);
          -webkit-backdrop-filter: blur(8px);
          transform: translateY(-20px);
          pointer-events: auto;
        }

        #notification-container .notification:hover {
          filter: brightness(1.1);
        }

        #notification-container .notification .close {
          position: absolute !important;
          right: 10px !important;
          top: 50% !important;
          transform: translateY(-50%) !important;
          width: 16px !important;
          height: 16px !important;
          cursor: pointer !important;
          opacity: 0.7 !important;
          transition: opacity 0.2s !important;
          display: flex !important;
          align-items: center !important;
          justify-content: center !important;
          user-select: none !important;
        }

        #notification-container .notification .close:hover {
          opacity: 1 !important;
        }

        #notification-container .success {
          background-color: #52c41a !important;
          box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3) !important;
          border-color: rgba(255, 255, 255, 0.2) !important;
        }

        #notification-container .info {
          background-color: #1890ff !important;
          box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3) !important;
          border-color: rgba(255, 255, 255, 0.15) !important;
        }

        #notification-container .warning {
          background-color: #faad14 !important;
          box-shadow: 0 4px 12px rgba(250, 173, 20, 0.3) !important;
          border-color: rgba(255, 255, 255, 0.1) !important;
        }

        #notification-container .error {
          background-color: #ff4d4f !important;
          box-shadow: 0 4px 12px rgba(255, 77, 79, 0.3) !important;
          border-color: rgba(255, 255, 255, 0.1) !important;
        }
      `;
      document.head.appendChild(style);

      NotificationManager.#container = document.createElement("div");
      NotificationManager.#container.id = "notification-container";
      document.body.appendChild(NotificationManager.#container);
    }

    NotificationManager.#instance = this;
  }

  success(message, duration) {
    this.#show(message, "success", duration);
  }

  info(message, duration) {
    this.#show(message, "info", duration);
  }

  warning(message, duration) {
    this.#show(message, "warning", duration);
  }

  error(message, duration) {
    this.#show(message, "error", duration);
  }

  #show(message, type, duration) {
    if (!duration || duration <= 0) {
      duration = NotificationManager.#defaultDuration;
    }
    NotificationManager.#queue.push({ message, type, duration });
    NotificationManager.#processQueue();
  }

  static #processQueue() {
    if (
      NotificationManager.#queue.length === 0 ||
      NotificationManager.#active.size >= NotificationManager.#maxNotifications
    ) {
      return;
    }

    const { message, type, duration } = NotificationManager.#queue.shift();
    NotificationManager.#showNotification(message, type, duration);

    if (NotificationManager.#queue.length > 0) {
      NotificationManager.#processQueue();
    }
  }

  static #showNotification(
    message,
    type,
    duration = NotificationManager.#defaultDuration
  ) {
    const notify = document.createElement("div");
    notify.classList.add("notification", type);

    const messageText = document.createElement("span");
    messageText.textContent = message;
    notify.appendChild(messageText);

    const removeNotify = () => {
      notify.style.opacity = "0";
      notify.style.transform = "translateY(-20px)";
      setTimeout(() => {
        notify.remove();
        NotificationManager.#active.delete(notify);
        NotificationManager.#processQueue();
      }, 300);
    };

    const closeBtn = document.createElement("div");
    closeBtn.classList.add("close");
    closeBtn.innerHTML = "✕";
    closeBtn.onclick = removeNotify;
    notify.appendChild(closeBtn);

    let timer;

    notify.addEventListener("mouseenter", () => {
      clearTimeout(timer);
    });

    notify.addEventListener("mouseleave", () => {
      timer = setTimeout(removeNotify, duration);
    });

    NotificationManager.#container.appendChild(notify);
    NotificationManager.#active.add(notify);

    requestAnimationFrame(() => {
      notify.style.opacity = "1";
      notify.style.transform = "translateY(0)";
    });

    timer = setTimeout(removeNotify, duration);
  }
}

/**
 * 防抖复制令牌
 */
const debouncedCopyToken = debounce(copyToken, 300);

/**
 * 消息通知
 */
const message = NotificationManager.getInstance();

(function () {
  ("use strict");

  // 添加按钮
  const button = createButton();
  document.body.appendChild(button);

  // 添加快捷键
  document.addEventListener("keydown", (event) => {
    if (event.shiftKey && event.key.toLowerCase() === "c") {
      event.preventDefault();
      debouncedCopyToken();
    }
  });
})();

/**
 * 创建按钮
 * @returns {HTMLElement} - 按钮元素
 */
function createButton() {
  // 创建按钮
  const button = document.createElement("div", { class: "copy-button" });
  button.innerHTML = `
      <div class="main-text">复制</div>
      <div class="shortcut">Shift + C</div>
  `;
  button.style.cssText = `
    position: fixed;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    background-color: rgba(71, 120, 224, 0.85);
    color: rgba(255, 255, 255, 0.95);
    border: none;
    padding: 10px 20px;
    border-radius: 20px 0 0 20px;
    cursor: pointer;
    font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
    font-size: 14px;
    transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
    z-index: 9999;
    overflow: hidden;
    white-space: nowrap;
    text-align: center;
    line-height: 1.4;
    box-shadow: 0 2px 8px rgba(71, 120, 224, 0.2);
    min-width: 80px;
  `;

  // 添加内部元素的样式
  const style = document.createElement("style");
  style.textContent = `
    .copy-button {
      display: flex;
      flex-direction: column;
      align-items: center;
      transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
      user-select: none;
    }
    .main-text {
      font-size: 14px;
      transition: min-width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
      width: 100%;
    }
    .shortcut {
      font-size: 10px;
      opacity: 0.8;
      margin-top: 2px;
    }
    .copy-button {
      transform-origin: right center;
    }
    .copy-button:hover {
      min-width: 140px;
      padding-right: 25px;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }
    .copy-button:active {
      transform: translateY(-50%) scale(0.98);
    }
  `;
  document.head.appendChild(style);

  let isAnimating = false;
  let currentAnimation = null; // 用于存储当前动画的定时器
  const fullText = "一图流令牌";
  const typingSpeed = 50; // 打字速度(ms)

  /**
   * 打字动画
   * @param {HTMLElement} element - 要打字的元素
   * @param {string} text - 要打字的文本
   * @param {number} currentIndex - 当前索引
   * @returns {void}
   */
  function typeText(element, text, currentIndex = 0) {
    if (currentAnimation) {
      clearTimeout(currentAnimation);
    }

    if (currentIndex <= text.length) {
      element.textContent = "复制" + text.slice(0, currentIndex);
      currentAnimation = setTimeout(() => {
        typeText(element, text, currentIndex + 1);
      }, typingSpeed);
    } else {
      isAnimating = false;
      currentAnimation = null;
    }
  }

  /**
   * 删除文本
   * @param {HTMLElement} element - 要删除文本的元素
   * @param {string} text - 要删除的文本
   * @param {number} currentIndex - 当前索引
   * @returns {void}
   */
  function deleteText(element, text, currentIndex = text.length) {
    if (currentAnimation) {
      clearTimeout(currentAnimation);
    }

    if (currentIndex >= 0) {
      element.textContent = "复制" + text.slice(0, currentIndex);
      currentAnimation = setTimeout(() => {
        deleteText(element, text, currentIndex - 1);
      }, typingSpeed);
    } else {
      isAnimating = false;
      currentAnimation = null;
    }
  }

  // 添加悬停效果
  button.addEventListener("mouseenter", () => {
    if (currentAnimation) {
      clearTimeout(currentAnimation);
    }
    isAnimating = true;
    button.style.backgroundColor = "rgba(86, 146, 255, 0.95)";
    typeText(button.querySelector(".main-text"), fullText);
  });

  button.addEventListener("mouseleave", () => {
    if (currentAnimation) {
      clearTimeout(currentAnimation);
    }
    isAnimating = true;
    button.style.backgroundColor = "rgba(71, 120, 224, 0.85)";
    deleteText(button.querySelector(".main-text"), fullText);
  });

  // 添加点击事件
  button.addEventListener("click", debouncedCopyToken);

  return button;
}

/**
 * 复制令牌
 * @returns {void}
 */
function copyToken() {
  try {
    const skOauthCredKey = localStorage.getItem("SK_OAUTH_CRED_KEY");
    const skTokenCacheKey = localStorage.getItem("SK_TOKEN_CACHE_KEY");

    if (!skOauthCredKey || !skTokenCacheKey) {
      const missingKeys = [];
      if (!skOauthCredKey) missingKeys.push("SK_OAUTH_CRED_KEY");
      if (!skTokenCacheKey) missingKeys.push("SK_TOKEN_CACHE_KEY");

      message.error(`缺少必要的密钥:${missingKeys.join("、")}`);
      return;
    }

    const combinedData = `${skOauthCredKey},${skTokenCacheKey}`;

    navigator.clipboard
      .writeText(combinedData)
      .then(() => message.success("令牌复制成功!"))
      .catch((err) => {
        console.error("复制失败!", err);
        message.error("复制失败!");
      });
  } catch (error) {
    console.error("操作过程出现错误!", error);
    message.error("操作过程出现错误!");
  }
}

/**
 * 防抖函数
 * @param {Function} func - 要防抖的函数
 * @param {number} wait - 等待时间(毫秒)
 * @returns {Function} - 防抖后的函数
 */
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}