小红书清理点赞

自动批量取消和添加小红书点赞

// ==UserScript==
// @name        小红书清理点赞
// @namespace   http://tampermonkey.net/
// @version     2025-06-27
// @description 自动批量取消和添加小红书点赞
// @author      You
// @match       https://www.xiaohongshu.com/*
// @icon        https://www.google.com/s2/favicons?sz=64&domain=xiaohongshu.com
// @grant       none
// ==/UserScript==

(function () {
  "use strict";

  // 配置项
  const config = {
    unlikeButtonSelector: '.like-wrapper.like-active svg use[*|href="#liked"]',
    likeButtonSelector: '.like-wrapper.like-active svg use[*|href="#like"]',
    clickDelay: 50,
    scrollDelay: 200,
  };

  let isCanceling = false;
  let isLiking = false;

  // 创建控制面板
  function createControlPanel() {
    // 创建开始取消点赞按钮
    const startUnlikeButton = document.createElement("button");
    startUnlikeButton.textContent = "开始取消点赞";
    startUnlikeButton.style.position = "fixed";
    startUnlikeButton.style.top = "20px";
    startUnlikeButton.style.right = "340px";
    startUnlikeButton.style.zIndex = "9999";
    startUnlikeButton.style.padding = "12px 24px";
    startUnlikeButton.style.backgroundColor = "#ff4444";
    startUnlikeButton.style.color = "white";
    startUnlikeButton.style.border = "none";
    startUnlikeButton.style.borderRadius = "4px";
    startUnlikeButton.style.fontSize = "16px";
    startUnlikeButton.style.cursor = "pointer";
    startUnlikeButton.style.boxShadow = "0 2px 10px rgba(0, 0, 0, 0.2)";
    startUnlikeButton.style.transition = "background-color 0.3s";
    startUnlikeButton.addEventListener("mouseenter", () => {
      startUnlikeButton.style.backgroundColor = "#cc0000";
    });
    startUnlikeButton.addEventListener("mouseleave", () => {
      startUnlikeButton.style.backgroundColor = "#ff4444";
    });
    document.body.appendChild(startUnlikeButton);

    // 创建开始批量点赞按钮
    const startLikeButton = document.createElement("button");
    startLikeButton.textContent = "开始批量点赞";
    startLikeButton.style.position = "fixed";
    startLikeButton.style.top = "20px";
    startLikeButton.style.right = "180px";
    startLikeButton.style.zIndex = "9999";
    startLikeButton.style.padding = "12px 24px";
    startLikeButton.style.backgroundColor = "#4CAF50";
    startLikeButton.style.color = "white";
    startLikeButton.style.border = "none";
    startLikeButton.style.borderRadius = "4px";
    startLikeButton.style.fontSize = "16px";
    startLikeButton.style.cursor = "pointer";
    startLikeButton.style.boxShadow = "0 2px 10px rgba(0, 0, 0, 0.2)";
    startLikeButton.style.transition = "background-color 0.3s";
    startLikeButton.addEventListener("mouseenter", () => {
      startLikeButton.style.backgroundColor = "#388E3C";
    });
    startLikeButton.addEventListener("mouseleave", () => {
      startLikeButton.style.backgroundColor = "#4CAF50";
    });
    document.body.appendChild(startLikeButton);

    // 创建停止按钮
    const stopButton = document.createElement("button");
    stopButton.textContent = "停止操作";
    stopButton.style.position = "fixed";
    stopButton.style.top = "20px";
    stopButton.style.right = "20px";
    stopButton.style.zIndex = "9999";
    stopButton.style.padding = "12px 24px";
    stopButton.style.backgroundColor = "#999999";
    stopButton.style.color = "white";
    stopButton.style.border = "none";
    stopButton.style.borderRadius = "4px";
    stopButton.style.fontSize = "16px";
    stopButton.style.cursor = "pointer";
    stopButton.style.boxShadow = "0 2px 10px rgba(0, 0, 0, 0.2)";
    stopButton.style.transition = "background-color 0.3s";
    stopButton.addEventListener("mouseenter", () => {
      stopButton.style.backgroundColor = "#666666";
    });
    stopButton.addEventListener("mouseleave", () => {
      stopButton.style.backgroundColor = "#999999";
    });
    document.body.appendChild(stopButton);

    // 绑定停止按钮点击事件
    stopButton.addEventListener("click", () => {
      isCanceling = false;
      isLiking = false;
      console.log("已触发停止操作");
    });

    // 绑定点击事件
    startUnlikeButton.addEventListener("click", () => batchAction('unlike'));
    startLikeButton.addEventListener("click", () => batchAction('like'));

    return {
      startUnlikeButton,
      startLikeButton,
      stopButton,
    };
  }

  // 批量操作函数
  async function batchAction(action) {
    if (action === 'unlike') {
      isCanceling = true;
      console.log('开始批量取消点赞操作');
    } else {
      isLiking = true;
      console.log('开始批量点赞操作');
    }
    let isScrollEnd = false;
    const selector = action === 'unlike' ? config.unlikeButtonSelector : config.likeButtonSelector;
    const isActive = action === 'unlike' ? () => isCanceling : () => isLiking;

    while (!isScrollEnd && isActive()) {
        console.log(`查找页面中的${action === 'unlike' ? '取消' : '点赞'}按钮`);
        const useElements = document.querySelectorAll(selector);
        console.log(`找到 ${useElements.length} 个${action === 'unlike' ? '取消' : '点赞'}按钮`);
        const clickedElements = [];

        for (const useElement of useElements) {
            if (!isActive()) break;
            const outerSpan = useElement.closest('.like-wrapper.like-active');
            if (outerSpan && isInViewport(outerSpan)) {
                console.log(`找到可视${action === 'unlike' ? '取消' : '点赞'}按钮,准备点击`);
                outerSpan.click();
                console.log(`已点击${action === 'unlike' ? '取消' : '点赞'}按钮,等待 ${config.clickDelay}ms`);
                await new Promise(resolve => setTimeout(resolve, config.clickDelay));
                clickedElements.push(outerSpan);
            }
        }
        console.log(`已点击按钮数/找到按钮数: ${clickedElements.length}/${useElements.length}`);

        if (!isActive()) break;
        const oldScrollY = window.scrollY;
        if (clickedElements.length > 0) {
          const lastClickedElement = clickedElements[clickedElements.length - 1];
          scrollUntilElementExitsViewport(lastClickedElement, 'down', false);
        } else {
          window.scrollBy({ 
            top: window.innerHeight, 
            behavior: 'auto' 
          });
        }
        await new Promise(resolve => setTimeout(resolve, config.scrollDelay));

        if (window.scrollY === oldScrollY) {
            console.log(`已到达页面底部,停止批量${action === 'unlike' ? '取消' : '点赞'}操作`);
            isScrollEnd = true;
        }
    }
    if (!isActive()) {
        console.log(`用户手动停止了批量${action === 'unlike' ? '取消' : '点赞'}操作`);
    }
    console.log(`批量${action === 'unlike' ? '取消' : '点赞'}操作结束`);
    if (action === 'unlike') {
      isCanceling = false;
    } else {
      isLiking = false;
    }
  }

  // 判断元素是否在视口内
  function isInViewport(element) {
    const rect = element.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
  }

  function scrollUntilElementExitsViewport(element, direction = 'down', isSmooth = true) {
    // 获取元素的边界矩形
    const rect = element.getBoundingClientRect();
    const windowHeight = window.innerHeight || document.documentElement.clientHeight;
    
    // 根据滚动方向计算目标滚动位置
    let scrollTarget;
    if (direction === 'down') {
        // 向下滚动直到元素顶部离开视口顶部
        scrollTarget = window.scrollY + rect.top;
    } else if (direction === 'up') {
        // 向上滚动直到元素底部离开视口底部
        scrollTarget = window.scrollY + (rect.bottom - windowHeight);
    } else if (direction === 'left') {
        // 向左滚动直到元素右侧离开视口左侧
        scrollTarget = window.scrollX + rect.right;
    } else if (direction === 'right') {
        // 向右滚动直到元素左侧离开视口右侧
        const windowWidth = window.innerWidth || document.documentElement.clientWidth;
        scrollTarget = window.scrollX + (rect.left - windowWidth);
    }
    
    // 执行平滑滚动
    if (direction === 'up' || direction === 'down') {
        window.scrollTo({
            top: scrollTarget,
            behavior: isSmooth ? 'smooth' : 'auto',
        });
    } else {
        window.scrollTo({
            left: scrollTarget,
            behavior: isSmooth ? 'smooth' : 'auto',
        });
    }
}

  // 创建控制面板
  createControlPanel();
})();