B站"稍后再看"重定向 / Bilibili Watch Later Redirect

在访问Bilibili的"稍后再看"列表播放器时重定向到常规播放页面,并允许自定义跳转延迟

当前为 2024-11-20 提交的版本,查看 最新版本

// ==UserScript==
// @name B站"稍后再看"重定向 / Bilibili Watch Later Redirect
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  在访问Bilibili的"稍后再看"列表播放器时重定向到常规播放页面,并允许自定义跳转延迟
// @author       Ellu
// @match        https://www.bilibili.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // 获取当前设置的延迟,如果没有设置则默认为1000毫秒
  let redirectDelay = GM_getValue("redirectDelay", 1000);

  function updateDelay() {
    const input = prompt(
      "请输入自定义延迟时间,可以输入小数(单位:秒)",
      (redirectDelay / 1000).toString()
    );

    if (input === null) return; // 用户取消输入
    const delay = parseFloat(input);
    if (isNaN(delay) || delay < 0) {
      showToast("无效输入", 1000);
      return;
    }
    const delayMs = Math.round(delay * 1000); // 转换为毫秒
    GM_setValue("redirectDelay", delayMs);
    redirectDelay = delayMs;
    showToast(`跳转延迟已设置为 ${delay} 秒`, 1000);
  }

  GM_registerMenuCommand(`自定义延迟`, updateDelay);

  // 全局变量,用于跟踪当前正在显示的 Toast
  let currentToast = null;
  let toastQueue = [];

  /**
   * 显示 Toast 消息
   * @param {string} message - 要显示的消息内容
   * @param {number} duration - 消失的持续时间(毫秒)
   */
  function showToast(message, duration = 3000) {
    // 如果有当前 Toast 在显示,先将新 Toast 加入队列
    if (currentToast) {
      hideToast(currentToast); // 先隐藏当前 Toast
      toastQueue.push({ message, duration });
      // 如果当前 Toast 正在淡出,则无需做额外处理
      return;
    }

    // 创建 toast 容器
    const toast = document.createElement("div");
    toast.innerText = message;

    // 设置样式
    Object.assign(toast.style, {
      position: "fixed",
      top: "50%",
      left: "50%",
      transform: "translate(-50%, -50%)",
      backgroundColor: "rgba(0, 0, 0, 0.8)",
      color: "#fff",
      padding: "15px 25px",
      borderRadius: "8px",
      boxShadow: "0 2px 10px rgba(0, 0, 0, 0.3)",
      zIndex: "9999",
      fontSize: "16px",
      opacity: "0",
      transition: "opacity 0.15s",
      textAlign: "center",
      pointerEvents: "none", // 确保 Toast 不会拦截鼠标事件
    });

    // 添加 Keyframes 动画到文档(仅添加一次)
    if (!document.getElementById("toast-animations")) {
      const styleSheet = document.createElement("style");
      styleSheet.type = "text/css";
      styleSheet.id = "toast-animations";
      styleSheet.innerText = `
@keyframes fadein {
    from { opacity: 0; }
    to { opacity: 1; }
}
@keyframes fadeout {
    from { opacity: 1; }
    to { opacity: 0; }
}
    `;
      document.head.appendChild(styleSheet);
    }

    // 添加到 body
    document.body.appendChild(toast);

    // 设置当前 Toast
    currentToast = toast;

    // 触发淡入效果
    // 使用 requestAnimationFrame 确保样式应用后再开始动画
    requestAnimationFrame(() => {
      toast.style.opacity = "1";
    });

    // 设置自动消失
    const hideTimeout = setTimeout(() => {
      hideToast(toast);
    }, duration);

    // 可以在需要时清除超时
    toast.hideTimeout = hideTimeout;
  }

  /**
   * 隐藏并移除 Toast
   * @param {HTMLElement} toast - 要隐藏的 Toast 元素
   */
  function hideToast(toast) {
    if (!toast) return;

    // 清除之前的隐藏超时
    clearTimeout(toast.hideTimeout);

    // 触发淡出效果
    toast.style.opacity = "0";

    // 等待淡出过渡完成后移除元素
    toast.addEventListener("transitionend", function onTransitionEnd() {
      toast.removeEventListener("transitionend", onTransitionEnd);
      if (toast.parentNode) {
        document.body.removeChild(toast);
      }
      currentToast = null;
      // 检查队列中是否有待显示的 Toast
      if (toastQueue.length > 0) {
        const nextToast = toastQueue.shift();
        showToast(nextToast.message, nextToast.duration);
      }
    });
  }

  // 如果当前URL不匹配https://www.bilibili.com/list/watchlater*,则不执行后续脚本
  if (!window.location.href.includes("/list/watchlater")) {
    return;
  }

  // 获取当前页面的URL参数
  const urlParams = new URLSearchParams(window.location.search);
  const bvid = urlParams.get("bvid");

  // 如果存在 bvid 参数,则执行重定向逻辑
  if (bvid) {
    // 定义跳转函数
    const redirect = () => {
      const newUrl = `https://www.bilibili.com/video/${bvid}`;
      window.location.replace(newUrl);
    };
    // 如果延迟为0则直接跳转
    if (redirectDelay === 0) {
      showToast("跳转中……", 1000);
      redirect();
      return;
    }
    // 显示初始的 Toast 信息
    showToast(
      `${redirectDelay / 1000} 秒后跳转到常规播放页面,按任意键或点击鼠标取消`,
      100000
    );
    // 设置一个标志来检测是否已取消
    let isCanceled = false;

    // 定义取消函数
    const cancelRedirect = () => {
      if (!isCanceled) {
        isCanceled = true;
        // 取消跳转
        clearTimeout(timer);
        // 移除事件监听器
        window.removeEventListener("keydown", keyPressHandler, true);
        window.removeEventListener("click", mouseClickHandler, true);
        // 隐藏当前 Toast
        hideToast(currentToast);
        // 显示取消的 Toast 信息
        showToast("跳转已取消", 600);
      }
    };

    // 定义按键事件处理器
    const keyPressHandler = (event) => {
      console.log("按键事件:", event);
      event.preventDefault(); // 阻止默认行为
      event.stopPropagation(); // 阻止事件传播
      cancelRedirect();
    };

    // 定义鼠标点击事件处理器
    const mouseClickHandler = (event) => {
      if (event.isTrusted) {
        console.log("鼠标点击事件:", event);
        event.preventDefault(); // 阻止默认行为
        event.stopPropagation(); // 阻止事件传播
        cancelRedirect();
      }
    };

    // 添加按键和鼠标点击事件监听器
    window.addEventListener("keydown", keyPressHandler, true);
    window.addEventListener("click", mouseClickHandler, true);

    // 设置定时器
    const timer = setTimeout(() => {
      showToast("跳转中……", 1000);
      window.removeEventListener("keydown", keyPressHandler, true);
      window.removeEventListener("click", mouseClickHandler, true);
      if (!isCanceled) {
        redirect();
      }
    }, redirectDelay);
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址