微信读书多功能插件

多种颜色护眼模式、调整页面宽度(状态持久化,页面刷新不变)、自动/挂机阅读(增加定时模式,单双烂阅读通用)

当前为 2025-10-27 提交的版本,查看 最新版本

// ==UserScript==
// @name         微信读书多功能插件
// @version      0.8.3
// @namespace    http://tampermonkey.net/
// @description  多种颜色护眼模式、调整页面宽度(状态持久化,页面刷新不变)、自动/挂机阅读(增加定时模式,单双烂阅读通用)
// @author       Chloe
// @match        https://weread.qq.com/web/reader/*
// @icon         https://weread.qq.com/favicon.ico
// @require      https://code.jquery.com/jquery-3.7.1.min.js
// @grant        GM_log
// @grant        GM_addStyle
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_openInTab
// @grant        GM_download
// @grant        GM_setClipboard
// @grant        GM_notification
// @license      MIT
// ==/UserScript==
(function () {
  'use strict';

  // 常量定义
  const DEFAULT_WIDTH = 800;
  const EYE_PROTECTION_COLORS = {
    'green': { name: '绿色', color: 'rgba(216,226,200,1)' },
    'yellow': { name: '黄色', color: 'rgba(240,234,214,1)' },
    'blue': { name: '蓝色', color: 'rgba(200,220,240,1)' },
    'pink': { name: '粉色', color: 'rgba(255,230,230,1)' },
    'purple': { name: '紫色', color: 'rgba(230,220,250,1)' },
    'gray': { name: '灰色', color: 'rgba(240,240,240,1)' }
  };

  // 状态变量 - 从存储中恢复
  let scrollInterval = null;
  let timerInterval = null;
  let isAutoReading = GM_getValue('weread_auto_reading', false);
  let isPageTurning = false;
  let pageTurnCooldown = false;
  let currentScrollSpeed = GM_getValue('weread_scroll_speed', 1.0);
  let remainingTime = GM_getValue('weread_remaining_time', 0);
  let lastTimerValue = GM_getValue('weread_last_timer', 0);
  let windowTop = 0;

  // 自动翻页相关变量
  let bottomReachedTimer = null;
  let isWaitingForPageTurn = false;
  let lastScrollPosition = 0;
  let progressInterval = null;

  // 样式注入
  GM_addStyle(`
        *{font-family: TsangerJinKai05 !important;}
        .readerTopBar{font-family: SourceHanSerifCN-Bold !important;}
        .bookInfo_title{font-family: SourceHanSerifCN-Bold !important;}
        .readerTopBar_title_link{font-family: SourceHanSerifCN-Bold; !important; font-weight:bold !important;}
        .readerTopBar_title_chapter{font-family: SourceHanSerifCN-Bold !important;}
        .readerChapterContent{color: rgba(0,0,0,100) !important;}
        .readerControls{margin-left: calc(50% - 60px) !important; margin-bottom: -28px !important;}

        /* 控制面板样式 */
        .control-panel {
            position: fixed;
            right: 40px;
            top: 50%;
            transform: translateY(-50%);
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 30px 15px 15px 15px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            z-index: 99998;
            min-width: 150px;
            transition: background-color 0.3s ease;
        }
        .control-panel-close {
            position: absolute;
            right: 8px;
            top: 8px;
            background: none;
            border: none;
            font-size: 16px;
            cursor: pointer;
            color: #999;
            width: 20px;
            height: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
        }
        .control-panel-close:hover {
            background: #f0f0f0;
            color: #333;
        }
        .control-section {
            margin: 15px 0;
            padding-bottom: 15px;
            border-bottom: 1px solid #eee;
        }
        .control-section:last-child {
            border-bottom: none;
            padding-bottom: 0;
        }
        .control-section-title {
            font-size: 14px;
            font-weight: bold;
            color: #333;
            margin-bottom: 10px;
            text-align: center;
        }
        .control-item {
            margin: 10px 0;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }
        .control-label {
            font-size: 12px;
            color: #666;
            margin-right: 10px;
        }
        .control-slider {
            width: 120px;
            height: 4px;
            background: #ddd;
            outline: none;
            opacity: 0.7;
            transition: opacity .2s;
            border-radius: 2px;
        }
        .control-slider:hover {
            opacity: 1;
        }
        .timer-slider {
            background: linear-gradient(to right,
                #4CAF50 0%, #4CAF50 4.17%,
                #ddd 4.17%, #ddd 12.5%,
                #4CAF50 12.5%, #4CAF50 25%,
                #ddd 25%, #ddd 50%,
                #4CAF50 50%, #4CAF50 100%);
        }
        .speed-slider {
            background: linear-gradient(to right,
                #4CAF50 0%, #4CAF50 10%,
                #ddd 10%, #ddd 20%,
                #4CAF50 20%, #4CAF50 30%,
                #ddd 30%, #ddd 40%,
                #4CAF50 40%, #4CAF50 60%,
                #ddd 60%, #ddd 100%);
        }
        .control-value {
            font-size: 12px;
            color: #333;
            min-width: 40px;
            text-align: center;
            font-family: monospace;
        }
        .control-buttons {
            display: flex;
            gap: 5px;
            margin-top: 10px;
            flex-wrap: wrap;
        }
        .control-btn {
            flex: 1;
            padding: 6px 8px;
            font-size: 12px;
            border: 1px solid #ddd;
            background: #f5f5f5;
            border-radius: 4px;
            cursor: pointer;
            text-align: center;
            min-width: 60px;
        }
        .control-btn:hover {
            background: #e9e9e9;
        }
        .control-btn.active {
            background: #4CAF50;
            color: white;
            border-color: #4CAF50;
        }
        .control-btn.reset {
            background: #ff9800;
            color: white;
            border-color: #ff9800;
        }
        .control-btn.reset:hover {
            background: #f57c00;
        }
        .control-btn.disabled {
            background: #cccccc;
            color: #666666;
            cursor: not-allowed;
            border-color: #cccccc;
        }
        .control-btn.secondary {
            background: #e0e0e0;
            color: #333;
            border-color: #bdbdbd;
        }
        .color-options {
            display: flex;
            gap: 10px;
            margin: 10px 0;
            justify-content: center;
            flex-wrap: wrap;
        }
        .color-option-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 5px;
            cursor: pointer;
        }
        .color-option {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            border: 2px solid #ddd;
            cursor: pointer;
            transition: all 0.2s;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        .color-option:hover {
            transform: scale(1.1);
            box-shadow: 0 3px 6px rgba(0,0,0,0.15);
        }
        .color-option.active {
            border-color: #333;
            transform: scale(1.1);
            box-shadow: 0 3px 8px rgba(0,0,0,0.2);
        }
        .color-name {
            font-size: 10px;
            color: #666;
            text-align: center;
            min-width: 40px;
        }
        .color-green { background-color: rgba(216,226,200,1); }
        .color-yellow { background-color: rgba(240,234,214,1); }
        .color-blue { background-color: rgba(200,220,240,1); }
        .color-pink { background-color: rgba(255,230,230,1); }
        .color-purple { background-color: rgba(230,220,250,1); }
        .color-gray { background-color: rgba(240,240,240,1); }

        /* 提示框样式 */
        .custom-notification {
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 20px;
            border-radius: 4px;
            z-index: 100000;
            font-size: 14px;
            transition: opacity 0.3s ease-in-out;
        }
        .custom-notification.fade-out {
            opacity: 0;
        }

        /* 护眼模式样式 */
        .eye-protection-green body {
            background-color: rgba(216,226,200,1) !important;
        }
        .eye-protection-green .readerContent .app_content {
            background-color: rgba(216,226,200,1) !important;
        }
        .eye-protection-green .readerTopBar {
            background-color: rgba(216,226,200,1) !important;
        }
        .eye-protection-yellow body {
            background-color: rgba(240,234,214,1) !important;
        }
        .eye-protection-yellow .readerContent .app_content {
            background-color: rgba(240,234,214,1) !important;
        }
        .eye-protection-yellow .readerTopBar {
            background-color: rgba(240,234,214,1) !important;
        }
        .eye-protection-blue body {
            background-color: rgba(200,220,240,1) !important;
        }
        .eye-protection-blue .readerContent .app_content {
            background-color: rgba(200,220,240,1) !important;
        }
        .eye-protection-blue .readerTopBar {
            background-color: rgba(200,220,240,1) !important;
        }
        .eye-protection-pink body {
            background-color: rgba(255,230,230,1) !important;
        }
        .eye-protection-pink .readerContent .app_content {
            background-color: rgba(255,230,230,1) !important;
        }
        .eye-protection-pink .readerTopBar {
            background-color: rgba(255,230,230,1) !important;
        }
        .eye-protection-purple body {
            background-color: rgba(230,220,250,1) !important;
        }
        .eye-protection-purple .readerContent .app_content {
            background-color: rgba(230,220,250,1) !important;
        }
        .eye-protection-purple .readerTopBar {
            background-color: rgba(230,220,250,1) !important;
        }
        .eye-protection-gray body {
            background-color: rgba(240,240,240,1) !important;
        }
        .eye-protection-gray .readerContent .app_content {
            background-color: rgba(240,240,240,1) !important;
        }
        .eye-protection-gray .readerTopBar {
            background-color: rgba(240,240,240,1) !important;
        }

        /* 定时器显示样式 */
        .timer-display {
            font-size: 12px;
            color: #666;
            text-align: center;
            margin-top: 5px;
        }

        /* 分割条样式 */
        .section-divider {
            height: 1px;
            background: linear-gradient(to right, transparent, #ddd, transparent);
            margin: 10px 0;
        }

        /* 复制成功提示框 */
        #module_box {
            position: fixed;
            left: 0;
            top: 200px;
            bottom: 0;
            right: 0;
            margin: auto;
            width: 200px;
            height: 100px;
            text-align: center;
            line-height: 100px;
            background-color: rgba(0, 0, 0, 0.3);
            font-size: 24px;
            z-index: 999999;
            display: none;
        }

        /* 自动翻页进度条样式 */
        #auto-turn-progress {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: white;
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 10px 15px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            z-index: 99997;
            min-width: 160px;
            display: none;
        }
        .progress-text {
            font-size: 12px;
            color: #333;
            margin-bottom: 5px;
            text-align: center;
        }
        .progress-bar {
            width: 100%;
            height: 6px;
            background: #f0f0f0;
            border-radius: 3px;
            overflow: hidden;
        }
        .progress-fill {
            height: 100%;
            background: #2196F3;
            border-radius: 3px;
            transition: width 0.1s linear;
            width: 100%;
        }

        /* 设置按钮图标样式 */
        .settings-icon {
            display: inline-block;
            width: 16px;
            height: 16px;
            background: currentColor;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.22,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.22,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.68 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z'/%3E%3C/svg%3E") no-repeat center;
            -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.22,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.22,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.68 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z'/%3E%3C/svg%3E") no-repeat center;
        }
    `);

  // 工具函数
  const utils = {
    // 显示通知
    notificationManager: {
      currentNotification: null,
      timeoutId: null,

      show: function (message, duration = 3000) {
        this.clear();
        this.currentNotification = $(`<div class="custom-notification">${message}</div>`);
        $('body').append(this.currentNotification);

        this.timeoutId = setTimeout(() => {
          this.close();
        }, duration);
      },

      close: function () {
        if (this.currentNotification) {
          this.currentNotification.addClass('fade-out');
          setTimeout(() => {
            if (this.currentNotification && this.currentNotification.parent().length) {
              this.currentNotification.remove();
            }
            this.currentNotification = null;
          }, 300);
        }

        if (this.timeoutId) {
          clearTimeout(this.timeoutId);
          this.timeoutId = null;
        }
      },

      clear: function () {
        this.close();
        $('.custom-notification').remove();
      }
    },

    // 检测DOM元素出现
    waitForElement: function (selector, maxAttempts = 80) {
      return new Promise(resolve => {
        let attempts = 0;
        const checkInterval = setInterval(() => {
          if (document.querySelectorAll(selector).length) {
            clearInterval(checkInterval);
            resolve(true);
          }
          if (attempts >= maxAttempts) {
            clearInterval(checkInterval);
            resolve(false);
          }
          attempts++;
        }, 100);
      });
    },

    // 检查当前是否是白色主题
    isWhiteTheme: function () {
      return document.body.classList.contains('wr_whiteTheme');
    },

    // 获取页面背景颜色并转换为不透明
    getPageBackgroundColor: function () {
      const appContent = document.querySelector('.readerContent .app_content');
      if (appContent) {
        let bgColor = window.getComputedStyle(appContent).backgroundColor;
        // 将颜色转换为不透明
        if (bgColor.startsWith('rgba')) {
          const rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
          if (rgbaMatch) {
            const r = rgbaMatch[1];
            const g = rgbaMatch[2];
            const b = rgbaMatch[3];
            return `rgb(${r}, ${g}, ${b})`;
          }
        }
        return bgColor;
      }
      return 'rgb(255, 255, 255)';
    }
  };

  // 宽度控制功能 - 确保状态正确保存和恢复
  const widthControl = {
    init: function () {
      const savedWidth = GM_getValue('weread_max_width', DEFAULT_WIDTH);
      this.applyWidth(savedWidth);
      return savedWidth;
    },

    applyWidth: function (width) {
      const content = document.querySelector(".readerContent .app_content");
      const topBar = document.querySelector('.readerTopBar');

      if (content && topBar) {
        content.style.maxWidth = width + 'px';
        topBar.style.maxWidth = width + 'px';

        // 立即保存设置
        GM_setValue('weread_max_width', width);

        // 更新滑块显示
        if ($('#widthSlider').length) {
          $('#widthSlider').val(width);
          $('#widthValue').text(width + 'px');
        }

        const resizeEvent = new Event('resize');
        window.dispatchEvent(resizeEvent);
      }
    },

    reset: function () {
      this.applyWidth(DEFAULT_WIDTH);
      utils.notificationManager.show('已恢复默认宽度');
    },

    // 获取当前实际宽度
    getCurrentWidth: function () {
      const content = document.querySelector(".readerContent .app_content");
      if (content) {
        const style = window.getComputedStyle(content);
        return parseInt(style.maxWidth) || DEFAULT_WIDTH;
      }
      return DEFAULT_WIDTH;
    }
  };

  // 护眼模式功能 - 改进的主题检测逻辑
  const eyeProtection = {
    init: function () {
      const isEnabled = GM_getValue('weread_eye_protection', false);
      const selectedColor = GM_getValue('weread_eye_protection_color', 'green');
      const isWhite = utils.isWhiteTheme();

      // 更新颜色选择按钮
      $(`.color-option`).removeClass('active');
      $(`.color-option.color-${selectedColor}`).addClass('active');

      // 关键改进:如果不是白色主题,直接禁用按钮并关闭护眼模式
      if (!isWhite) {
        // 强制关闭护眼模式
        if (isEnabled) {
          GM_setValue('weread_eye_protection', false);
          this.disable();
        }

        // 禁用按钮
        $('#eyeProtectionBtn').addClass('disabled');
        $('#eyeProtectionBtn').text('护眼模式:关');

        // 显示提示
        if (isEnabled) {
          utils.notificationManager.show('护眼模式仅在白色主题下可用,已自动关闭');
        }
        return false;
      }

      // 如果是白色主题,正常初始化
      if (isEnabled) {
        this.enable(selectedColor);
        $('#eyeProtectionBtn').removeClass('disabled');
      } else {
        this.disable();
        $('#eyeProtectionBtn').removeClass('disabled');
      }

      return isEnabled;
    },

    enable: function (color) {
      Object.keys(EYE_PROTECTION_COLORS).forEach(colorKey => {
        document.body.classList.remove(`eye-protection-${colorKey}`);
      });
      document.body.classList.add(`eye-protection-${color}`);

      $('#eyeProtectionBtn').addClass('active');
      $('#eyeProtectionBtn').text('护眼模式:开');
    },

    disable: function () {
      Object.keys(EYE_PROTECTION_COLORS).forEach(color => {
        document.body.classList.remove(`eye-protection-${color}`);
      });

      $('#eyeProtectionBtn').removeClass('active');
      $('#eyeProtectionBtn').text('护眼模式:关');
    },

    toggle: function () {
      // 检查是否禁用 - 如果不是白色主题,按钮应该已经被禁用
      if ($('#eyeProtectionBtn').hasClass('disabled')) {
        utils.notificationManager.show('护眼模式仅在白色主题下可用');
        return;
      }

      const isActive = $('#eyeProtectionBtn').hasClass('active');
      const selectedColor = GM_getValue('weread_eye_protection_color', 'green');
      const colorInfo = EYE_PROTECTION_COLORS[selectedColor];

      if (isActive) {
        this.disable();
        GM_setValue('weread_eye_protection', false);
        utils.notificationManager.show('护眼模式已关闭');
      } else {
        this.enable(selectedColor);
        GM_setValue('weread_eye_protection', true);
        utils.notificationManager.show(`${colorInfo.name}护眼模式已开启`);
      }
    },

    changeColor: function (color) {
      // 检查是否禁用
      if ($('#eyeProtectionBtn').hasClass('disabled')) {
        utils.notificationManager.show('护眼模式仅在白色主题下可用');
        return;
      }

      const colorInfo = EYE_PROTECTION_COLORS[color];
      GM_setValue('weread_eye_protection_color', color);

      // 如果护眼模式已开启,立即应用新颜色
      if ($('#eyeProtectionBtn').hasClass('active')) {
        this.enable(color);
        utils.notificationManager.show(`已切换为${colorInfo.name}护眼模式`);
      }
    }
  };

  // 自动翻页功能
  const autoPageTurn = {
    trigger: function () {
      if (isPageTurning || pageTurnCooldown) return;

      isPageTurning = true;

      // 触发键盘右键事件
      const event = new KeyboardEvent('keydown', {
        bubbles: true,
        cancelable: true,
        key: 'ArrowRight',
        code: 'ArrowRight',
        keyCode: 39,
        charCode: 0
      });

      document.dispatchEvent(event);

      // 也触发keyup事件
      const keyUpEvent = new KeyboardEvent('keyup', {
        bubbles: true,
        cancelable: true,
        key: 'ArrowRight',
        code: 'ArrowRight',
        keyCode: 39,
        charCode: 0
      });
      document.dispatchEvent(keyUpEvent);

      console.log('触发自动翻页');

      // 设置冷却时间,防止连续触发
      pageTurnCooldown = true;
      setTimeout(() => {
        pageTurnCooldown = false;
      }, 2000);

      // 翻页后等待页面加载完成
      setTimeout(() => {
        isPageTurning = false;
        // 翻页后自动滚动到页面顶部附近,确保内容可见
        window.scrollTo(0, 100);
      }, 1500);
    }
  };

  // 自动翻页进度条
  const progressBar = {
    init: function() {
      $('body').append(`
        <div id="auto-turn-progress">
          <div class="progress-text">0秒后自动翻页</div>
          <div class="progress-bar">
            <div class="progress-fill"></div>
          </div>
        </div>
      `);
    },

    show: function(waitTime) {
      this.waitTime = waitTime;
      this.startTime = Date.now();
      $('#auto-turn-progress').show();
      this.update();

      progressInterval = setInterval(() => {
        this.update();
      }, 100);
    },

    update: function() {
      const elapsed = (Date.now() - this.startTime) / 1000;
      const remaining = Math.max(0, this.waitTime - elapsed);
      const percentage = (remaining / this.waitTime) * 100;

      $('.progress-text').text(`${remaining.toFixed(1)}秒后自动翻页`);
      $('.progress-fill').css('width', percentage + '%');

      if (remaining <= 0) {
        this.hide();
      }
    },

    hide: function() {
      $('#auto-turn-progress').hide();
      if (progressInterval) {
        clearInterval(progressInterval);
        progressInterval = null;
      }
    }
  };

  // 自动阅读功能 - 添加状态保存和恢复
  const autoRead = {
    // 根据速度计算等待时间
    calculateWaitTime: function() {
      const speed = currentScrollSpeed;
      if (speed <= 0.5) return 10;
      if (speed <= 1) return 8;
      if (speed <= 2) return 6;
      if (speed <= 3) return 4;
      return 2; // 4倍及以上速度
    },

    start: function () {
      // 先停止可能存在的滚动
      if (scrollInterval) {
        clearInterval(scrollInterval);
        scrollInterval = null;
      }

      // 清除可能存在的底部计时器
      this.clearBottomTimer();

      // 记录定时时间
      const timerMinutes = parseInt($('#timerSlider').val());
      if (timerMinutes > 0) {
        lastTimerValue = timerMinutes;
        GM_setValue('weread_last_timer', lastTimerValue);
        this.updateLastTimerButton();
      }

      const baseSpeed = 1;
      const speedMultiplier = currentScrollSpeed;

      let lastScrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      let stuckCount = 0;

      scrollInterval = setInterval(() => {
        // 如果正在翻页,暂停滚动但保持计时
        if (isPageTurning) {
          return;
        }

        const currentScrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
        const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;

        // 检查是否到达底部
        if (currentScrollTop + clientHeight >= scrollHeight - 10) {
          // 所有模式统一处理:等待指定时间后自动翻页
          if (!isWaitingForPageTurn) {
            this.schedulePageTurn();
          }
          return;
        }

        // 检查是否卡住
        if (currentScrollTop === lastScrollTop) {
          stuckCount++;
          if (stuckCount > 5) {
            window.scrollBy(0, baseSpeed * speedMultiplier * 3);
          } else {
            window.scrollBy(0, baseSpeed * speedMultiplier);
          }
        } else {
          stuckCount = 0;
          window.scrollBy(0, baseSpeed * speedMultiplier);
        }

        lastScrollTop = currentScrollTop;

        // 记录滚动位置用于检测用户手动翻页
        lastScrollPosition = currentScrollTop;
      }, 20);

      isAutoReading = true;
      this.updateButton();

      // 启动定时器
      this.startTimer();

      // 保存状态
      this.saveState();

      utils.notificationManager.show('自动阅读已开始');
    },

    stop: function () {
      if (scrollInterval) {
        clearInterval(scrollInterval);
        scrollInterval = null;
      }
      isAutoReading = false;
      isPageTurning = false;
      isWaitingForPageTurn = false;
      this.updateButton();

      // 清除底部计时器
      this.clearBottomTimer();

      // 隐藏进度条
      progressBar.hide();

      // 停止定时器
      this.stopTimer();

      // 保存状态
      this.saveState();
    },

    toggle: function () {
      if (isAutoReading) {
        this.stop();
      } else {
        this.start();
      }
    },

    // 安排自动翻页
    schedulePageTurn: function () {
      isWaitingForPageTurn = true;
      const waitTime = this.calculateWaitTime();
      console.log(`到达底部,等待${waitTime}秒后自动翻页`);

      // 显示进度条
      progressBar.show(waitTime);

      bottomReachedTimer = setTimeout(() => {
        if (isWaitingForPageTurn) {
          console.log('等待结束,执行自动翻页');
          autoPageTurn.trigger();
          isWaitingForPageTurn = false;
          progressBar.hide();

          // 翻页后重新开始滚动
          setTimeout(() => {
            if (isAutoReading) {
              lastScrollPosition = 0;
            }
          }, 2000);
        }
      }, waitTime * 1000);
    },

    // 清除底部计时器
    clearBottomTimer: function () {
      if (bottomReachedTimer) {
        clearTimeout(bottomReachedTimer);
        bottomReachedTimer = null;
      }
      isWaitingForPageTurn = false;
      progressBar.hide();
    },

    // 检查用户是否手动翻页
    checkManualPageTurn: function () {
      if (!isWaitingForPageTurn) return;

      const currentScrollTop = document.documentElement.scrollTop || document.body.scrollTop;

      // 如果滚动位置显著变化,认为用户手动翻页,重新计时
      if (Math.abs(currentScrollTop - lastScrollPosition) > 50) {
        console.log('检测到用户手动翻页,重新计时');
        this.clearBottomTimer();
        this.schedulePageTurn();
      }

      lastScrollPosition = currentScrollTop;
    },

    startTimer: function () {
      const timerMinutes = parseInt($('#timerSlider').val());
      if (timerMinutes > 0) {
        // 如果是从恢复状态而来,可能已经设置了remainingTime,否则使用定时器滑块的值
        if (remainingTime <= 0) {
          remainingTime = timerMinutes * 60;
        }
        this.updateTimerDisplay();

        timerInterval = setInterval(() => {
          remainingTime--;
          this.updateTimerDisplay();

          // 每秒保存一次剩余时间
          GM_setValue('weread_remaining_time', remainingTime);

          if (remainingTime <= 0) {
            this.stop();
            utils.notificationManager.show('定时时间到,自动阅读已停止');
          }
        }, 1000);
      }
    },

    stopTimer: function () {
      if (timerInterval) {
        clearInterval(timerInterval);
        timerInterval = null;
      }
      remainingTime = 0;
      GM_setValue('weread_remaining_time', 0);
      this.updateTimerDisplay();
    },

    updateTimerDisplay: function () {
      if (remainingTime > 0) {
        const minutes = Math.floor(remainingTime / 60);
        const seconds = remainingTime % 60;
        $('#timerDisplay').text(`剩余: ${minutes}:${seconds.toString().padStart(2, '0')}`);
      } else {
        $('#timerDisplay').text('');
      }
    },

    updateButton: function () {
      const button = $('#toggleAutoRead');
      if (isAutoReading) {
        button.text('停止阅读');
        button.addClass('active');
      } else {
        button.text('开始阅读');
        button.removeClass('active');
      }
    },

    updateLastTimerButton: function () {
      const lastTimerBtn = $('#lastTimerBtn');
      if (lastTimerValue > 0) {
        lastTimerBtn.removeClass('disabled');
        lastTimerBtn.css('background', '#e0e0e0');
      } else {
        lastTimerBtn.addClass('disabled');
        lastTimerBtn.css('background', '#cccccc');
      }
    },

    applyLastTimer: function () {
      if (lastTimerValue > 0) {
        $('#timerSlider').val(lastTimerValue);
        $('#timerValue').text(lastTimerValue + '分钟');
        utils.notificationManager.show(`已设置为上次定时时间: ${lastTimerValue}分钟`);
      } else {
        utils.notificationManager.show('没有找到上次定时时间');
      }
    },

    // 保存自动阅读状态
    saveState: function () {
      GM_setValue('weread_auto_reading', isAutoReading);
      GM_setValue('weread_scroll_speed', currentScrollSpeed);
    },

    // 恢复自动阅读状态
    restoreState: function () {
      if (isAutoReading) {
        // 设置速度滑块
        $('#speedSlider').val(currentScrollSpeed);
        $('#speedValue').text(currentScrollSpeed.toFixed(1) + 'x');

        // 设置定时器滑块
        const timerMinutes = Math.ceil(remainingTime / 60);
        if (timerMinutes > 0) {
          $('#timerSlider').val(timerMinutes);
          $('#timerValue').text(timerMinutes + '分钟');
        }

        // 更新按钮状态
        this.updateButton();

        // 开始自动阅读
        this.start();

        utils.notificationManager.show('已恢复自动阅读状态');
      }
    }
  };

  // 复制和图片下载功能
  const contentTools = {
    init: async function () {
      // 添加复制成功提示框
      $("body").append(`
                <div id="module_box">
                    复制成功
                </div>
            `);

      // 添加复制和图片下载按钮
      await this.addCopyImgButtons();
    },

    addCopyImgButtons: async function () {
      let resDomImg = await utils.waitForElement('.wr_readerImage_opacity');
      let openImgBtnLength = $("button[name='btn_cxy_open_img_page']").length;
      let getImgBtnLength = $("button[name='btn_cxy_get_img']").length;

      if (resDomImg && openImgBtnLength === 0 && getImgBtnLength === 0) {
        console.log("图片个数===", $('.wr_readerImage_opacity').length);
        $('.wr_readerImage_opacity').each((ind, ele) => {
          let btn = document.createElement("button");
          btn.name = "btn_cxy_open_img_page";
          btn.innerHTML = "📋";
          btn.style.cssText = `position: absolute;right: 0px;top: ${ele.offsetTop - 50}px;color:white;z-index:9999; cursor:pointer`;

          let btn2 = document.createElement("button");
          btn2.name = "btn_cxy_get_img";
          btn2.innerHTML = "▼";
          btn2.style.cssText = `position: absolute;right: 0px;top: ${ele.offsetTop - 20}px;color:#888;z-index:9999; cursor:pointer`;

          // 添加按钮
          ele.after(btn);
          ele.after(btn2);
        });
      }
    },

    copyCode: function () {
      let codeText = $(this).closest('pre')[0].textContent.replace("📋", "");
      GM_setClipboard(codeText);
      $("#module_box").fadeIn();
      setTimeout(() => {
        $("#module_box").fadeOut(300, function () {
          $(this).hide();
        });
      }, 1000);
    },

    openImagePage: function () {
      let link = $(this).prev().prev().attr("src");
      GM_openInTab(link, { active: true });
    },

    downloadImage: function () {
      let link = $(this).prev().attr("src");
      GM_download({
        url: link,
        name: new Date().getTime() + '.jpg',
        headers: {
          "User-Agent": "netdisk;6.7.1.9;PC;PC-Windows;10.0.17763;WindowsBaiduYunGuanJia",
        },
        onprogress: function (e) {
          // 下载进度处理
        },
      });
    }
  };

  // 控制面板功能 - 改进宽度初始化
  const controlPanel = {
    init: function () {
      // 从存储中获取保存的宽度
      const savedWidth = GM_getValue('weread_max_width', DEFAULT_WIDTH);

      // 添加控制面板
      $("body").append(`
                <div class="control-panel" style="display: none;" id="mainControlPanel">
                    <button class="control-panel-close" id="closeControlPanel">×</button>
                    <div class="control-section">
                        <div class="control-section-title">宽度控制</div>
                        <div class="control-item">
                            <span class="control-label">页面宽度</span>
                            <input type="range" class="control-slider" id="widthSlider" min="600" max="1400" value="${savedWidth}">
                            <span class="control-value" id="widthValue">${savedWidth}px</span>
                        </div>
                        <div class="control-buttons">
                            <button class="control-btn reset" id="resetWidth">恢复默认</button>
                        </div>
                    </div>
                    <div class="section-divider"></div>
                    <div class="control-section">
                        <div class="control-section-title">自动阅读</div>
                        <div class="control-item">
                            <span class="control-label">阅读速度</span>
                            <input type="range" class="control-slider speed-slider" id="speedSlider" min="0.5" max="4" step="0.1" value="${currentScrollSpeed}">
                            <span class="control-value" id="speedValue">${currentScrollSpeed.toFixed(1)}x</span>
                        </div>
                        <div class="control-item">
                            <span class="control-label">定时关闭</span>
                            <input type="range" class="control-slider timer-slider" id="timerSlider" min="0" max="120" step="1" value="0">
                            <span class="control-value" id="timerValue">0分钟</span>
                        </div>
                        <div class="timer-display" id="timerDisplay"></div>
                        <div class="control-buttons">
                            <button class="control-btn" id="toggleAutoRead">${isAutoReading ? '停止阅读' : '开始阅读'}</button>
                            <button class="control-btn secondary" id="lastTimerBtn">上次定时</button>
                        </div>
                    </div>
                    <div class="section-divider"></div>
                    <div class="control-section">
                        <div class="control-section-title">显示设置</div>
                        <div class="color-options" id="colorOptionsContainer"></div>
                        <div class="control-buttons">
                            <button class="control-btn" id="eyeProtectionBtn">护眼模式:关</button>
                        </div>
                    </div>
                </div>
            `);

      // 生成颜色选项
      this.generateColorOptions();

      // 添加控制按钮 - 使用图标按钮
      $('.readerControls').append(`
        <div class="wr_tooltip_container" style="--offset: 6px;">
            <button class="readerControls_item" id="mainControl" style="color:#6a6c6c;cursor:pointer;">
                <span class="settings-icon"></span>
            </button>
            <div class="wr_tooltip_item wr_tooltip_item--right" style="display: none;">
                设置
            </div>
        </div>
      `);

      // 绑定事件
      this.bindEvents();
    },

    generateColorOptions: function () {
      const container = $('#colorOptionsContainer');
      container.empty();

      Object.keys(EYE_PROTECTION_COLORS).forEach(colorKey => {
        const colorInfo = EYE_PROTECTION_COLORS[colorKey];
        const isActive = colorKey === GM_getValue('weread_eye_protection_color', 'green');

        const colorOption = $(`
                    <div class="color-option-container" data-color="${colorKey}">
                        <div class="color-option color-${colorKey} ${isActive ? 'active' : ''}"></div>
                        <div class="color-name">${colorInfo.name}</div>
                    </div>
                `);

        container.append(colorOption);
      });
    },

    bindEvents: function () {
      // 控制面板显示/隐藏
      $('#mainControl').click(function () {
        $('#mainControlPanel').toggle();
      });

      // 工具提示
      $('#mainControl').hover(
        function() {
          $(this).siblings('.wr_tooltip_item').show();
        },
        function() {
          $(this).siblings('.wr_tooltip_item').hide();
        }
      );

      // 关闭按钮
      $(document).on('click', '#closeControlPanel', function (e) {
        e.stopPropagation();
        $('#mainControlPanel').hide();
      });

      // 宽度控制 - 确保滑块值正确反映当前宽度
      $('#widthSlider').on('input', function () {
        const newWidth = parseInt($(this).val());
        $('#widthValue').text(newWidth + 'px');
        widthControl.applyWidth(newWidth);
      });

      $('#resetWidth').click(function () {
        $('#widthSlider').val(DEFAULT_WIDTH);
        $('#widthValue').text(DEFAULT_WIDTH + 'px');
        widthControl.reset();
      });

      // 颜色选择
      $(document).on('click', '.color-option-container', function () {
        const color = $(this).data('color');
        $('.color-option').removeClass('active');
        $(this).find('.color-option').addClass('active');
        eyeProtection.changeColor(color);
      });

      // 护眼模式切换
      $(document).on('click', '#eyeProtectionBtn', function () {
        if ($(this).hasClass('disabled')) {
          utils.notificationManager.show('护眼模式仅在白色主题下可用');
          return;
        }
        eyeProtection.toggle();
      });

      // 自动阅读控制
      $('#speedSlider').on('input', function () {
        currentScrollSpeed = parseFloat($(this).val());
        $('#speedValue').text(currentScrollSpeed.toFixed(1) + 'x');
        GM_setValue('weread_scroll_speed', currentScrollSpeed);
        // 如果正在自动阅读,更新速度
        if (isAutoReading) {
          autoRead.stop();
          autoRead.start();
        }
      });

      $('#timerSlider').on('input', function () {
        const timerValue = parseInt($(this).val());
        $('#timerValue').text(timerValue + '分钟');
      });

      $('#lastTimerBtn').click(function () {
        if ($(this).hasClass('disabled')) {
          utils.notificationManager.show('没有可用的上次定时记录');
          return;
        }
        autoRead.applyLastTimer();
      });

      $('#toggleAutoRead').click(function () {
        autoRead.toggle();
      });

      // 点击页面其他地方隐藏控制面板
      $(document).on('click', function (e) {
        if (!$(e.target).closest('.control-panel').length &&
          !$(e.target).closest('#mainControl').length &&
          !$(e.target).closest('#closeControlPanel').length) {
          $('.control-panel').hide();
        }
      });
    },

    updateBackground: function () {
      const bgColor = utils.getPageBackgroundColor();
      $('#mainControlPanel').css('background', bgColor);
    }
  };

  // 头部隐藏功能
  const headerControl = {
    init: function () {
      $(window).scroll(function () {
        let scrollS = $(this).scrollTop();
        let windowHeight = $(this).height();
        let documentHeight = $(document).height();

        // 头部隐藏逻辑
        let selBtn = document.querySelector('.readerTopBar');
        let readerControl = document.querySelector(".readerControls");

        $('.readerControls').mouseenter(function () {
          $('.readerControls').css('opacity', '1');
        });

        $('.readerControls').mouseleave(function () {
          $('.readerControls').css('opacity', '0');
        });

        if (scrollS >= windowTop) {
          selBtn.style.opacity = 0;
          windowTop = scrollS;
        } else {
          selBtn.style.opacity = 1;
          windowTop = scrollS;
        }

        // 检测用户手动翻页
        if (isAutoReading) {
          autoRead.checkManualPageTurn();
        }
      });
    }
  };

  // 初始化函数
  function initialize () {
    // 初始化各模块
    const currentWidth = widthControl.init();

    // 关键改进:确保滑块值与实际宽度一致
    $('#widthSlider').val(currentWidth);
    $('#widthValue').text(currentWidth + 'px');

    // 初始化护眼模式
    eyeProtection.init();

    // 初始化进度条
    progressBar.init();

    autoRead.updateLastTimerButton();

    controlPanel.init();
    headerControl.init();
    contentTools.init();

    // 初始化控制面板背景颜色
    controlPanel.updateBackground();

    // 恢复自动阅读状态(如果有)
    if (isAutoReading) {
      setTimeout(() => {
        autoRead.restoreState();
      }, 1000);
    }

    // 绑定复制和图片事件
    $(document).on("click", "#copy_code", contentTools.copyCode);
    $(document).on("click", "button[name='btn_cxy_open_img_page']", contentTools.openImagePage);
    $(document).on("click", "button[name='btn_cxy_get_img']", contentTools.downloadImage);

    // 监听主题变化和背景颜色变化
    const observer = new MutationObserver(function (mutations) {
      let shouldUpdate = false;

      mutations.forEach(function (mutation) {
        if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
          // 关键改进:主题变化时重新初始化护眼模式
          setTimeout(() => {
            eyeProtection.init();
          }, 100);
          shouldUpdate = true;
        }

        if (mutation.type === 'attributes' &&
          (mutation.target.classList.contains('app_content') ||
            mutation.target.classList.contains('readerContent') ||
            document.body.classList.contains('eye-protection-'))) {
          shouldUpdate = true;
        }
      });

      if (shouldUpdate) {
        setTimeout(() => {
          controlPanel.updateBackground();
        }, 100);
      }
    });

    // 开始观察body和.app_content的变化
    observer.observe(document.body, {
      attributes: true,
      attributeFilter: ['class']
    });

    const appContent = document.querySelector('.readerContent .app_content');
    if (appContent) {
      observer.observe(appContent, {
        attributes: true,
        attributeFilter: ['class', 'style']
      });
    }

    // 添加窗口resize监听
    $(window).on('resize', function () {
      setTimeout(() => {
        controlPanel.updateBackground();
      }, 100);
    });
  }

  // 页面加载完成后初始化
  $(window).on('load', initialize);
})();

QingJ © 2025

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