// ==UserScript==
// @name 抖音直播间自动点赞
// @namespace https://scriptcat.org/
// @version 2.6.0
// @description 抖音直播间自动点赞小面板集成:选择点赞区/状态切换+间隔设置+次数设置+状态显示,实现真正动态随机间隔
// @author Xubai0224
// @match *://live.douyin.com/*
// @icon https://www.douyin.com/favicon.ico
// @icon64 https://www.douyin.com/favicon.ico
// @grant GM_addStyle
// @tag Douyin
// @license MIT
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
/**************************
* 1. 常量与全局状态管理
**************************/
// 全局配置默认值(常量)
const DEFAULT_LIKE_CONFIG = {
minInterval: 0.3, // 每组点赞最短间隔(秒)
maxInterval: 1, // 每组点赞最长间隔(秒)
maxCount: 1000, // 最大点赞组数(建议3000以内)
isLiking: false // 是否正在点赞中
};
// 全局状态变量
const globalState = {
clickPosition: { x: 0, y: 0 },
isSelectingArea: false,
autoClickTimer: null,
groupCount: 0,
highlightMarker: null,
countdownTimer: null,
countdownNum: 3,
isPreSelectCountdown: false,
likeConfig: { ...DEFAULT_LIKE_CONFIG } // 深拷贝默认配置
};
/**************************
* 2. 样式定义(模块化)
**************************/
function injectStyles() {
const styles = `
/* 核心小面板样式 */
#like-control-panel {
position: fixed;
top: 20px;
right: 20px;
width: 220px;
background: rgba(255, 255, 255, 0.95);
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 114, 255, 0.3);
z-index: 99999999;
padding: 16px;
font-family: system-ui, -apple-system, sans-serif;
transition: all 0.3s ease;
}
/* 面板标题 */
.panel-title {
font-size: 16px;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f3f4f6;
}
/* 功能按钮 */
#like-control-btn {
width: 100%;
padding: 8px 0;
background: linear-gradient(135deg, #00c6ff, #0072ff);
border: none;
border-radius: 8px;
color: white;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
margin-bottom: 12px;
}
#like-control-btn:hover {
opacity: 0.9;
transform: scale(1.02);
}
#like-control-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
/* 设置项容器 */
.setting-group {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
}
/* 单个设置项 */
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #666;
}
/* 输入框样式 */
.setting-input {
width: 80px;
padding: 4px 8px;
border: 1px solid #e5e7eb;
border-radius: 6px;
font-size: 12px;
text-align: center;
outline: none;
transition: border-color 0.2s ease;
}
.setting-input:focus {
border-color: #0072ff;
box-shadow: 0 0 0 2px rgba(0, 114, 255, 0.1);
}
/* 状态显示栏 */
#like-status-display {
width: 100%;
padding: 6px 0;
background: #f9fafb;
border-radius: 6px;
text-align: center;
font-size: 12px;
color: #333;
border: 1px solid #e5e7eb;
transition: color 0.2s ease;
}
/* 坐标高亮标记样式 */
.coordinate-highlight {
position: fixed;
width: 30px;
height: 30px;
border: 2px solid #ff4d4f;
border-radius: 50%;
background: rgba(255, 77, 79, 0.2);
z-index: 99999998;
pointer-events: none;
transform: translate(-50%, -50%);
}
/* 倒计时样式 */
.countdown-box {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 48px;
font-weight: bold;
color: #ff4d4f;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
z-index: 99999999;
pointer-events: none;
}
/* 坐标确认弹窗样式 */
.popup-box {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px 24px;
background: rgba(255, 255, 255, 0.95);
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
z-index: 99999999;
text-align: center;
font-family: system-ui, -apple-system, sans-serif;
}
.popup-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
}
.popup-btns {
display: flex;
justify-content: center;
gap: 12px;
}
.popup-btn {
padding: 6px 16px;
border: none;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.popup-confirm-btn {
background: #0072ff;
color: white;
}
.popup-cancel-btn {
background: #e5e7eb;
color: #666;
}
.popup-btn:hover {
opacity: 0.9;
transform: scale(1.02);
}
/* 通用隐藏样式 */
.hidden {
display: none !important;
}
/* Toast通知样式 */
.data-tool-notification {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding: 8px 16px;
background: #4CAF50;
color: white;
border-radius: 4px;
z-index: 99999;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: opacity 0.3s;
font-family: system-ui, -apple-system, sans-serif;
font-size: 14px;
}
`;
GM_addStyle(styles);
}
/**************************
* 3. DOM元素创建(模块化)
**************************/
function createDOMElements() {
// 创建核心小面板
createControlPanel();
// 创建坐标确认弹窗
createConfirmPopup();
// 创建坐标高亮标记
createHighlightMarker();
// 创建倒计时元素
createCountdownBox();
// 绑定所有事件
bindAllEvents();
// 初始化状态显示
updateStatusDisplay();
}
/**
* 创建控制小面板
*/
function createControlPanel() {
const { likeConfig } = globalState;
const panel = document.createElement('div');
panel.id = 'like-control-panel';
panel.innerHTML = `
抖音自动连点设置
等待选择点赞区
`;
document.body.appendChild(panel);
}
/**
* 创建坐标确认弹窗
*/
function createConfirmPopup() {
const popup = document.createElement('div');
popup.id = 'coordinate-confirm-popup';
popup.className = 'popup-box hidden';
popup.innerHTML = `
`;
document.body.appendChild(popup);
}
/**
* 创建坐标高亮标记
*/
function createHighlightMarker() {
const marker = document.createElement('div');
marker.id = 'coordinate-highlight';
marker.className = 'coordinate-highlight hidden';
document.body.appendChild(marker);
globalState.highlightMarker = marker;
}
/**
* 创建倒计时元素
*/
function createCountdownBox() {
const countdownBox = document.createElement('div');
countdownBox.id = 'countdown-box';
countdownBox.className = 'countdown-box hidden';
document.body.appendChild(countdownBox);
}
/**************************
* 4. 事件绑定(模块化)
**************************/
function bindAllEvents() {
// 获取DOM元素
const elements = {
controlBtn: document.getElementById('like-control-btn'),
confirmBtn: document.getElementById('confirm-btn'),
cancelBtn: document.getElementById('cancel-btn'),
minIntervalInput: document.getElementById('min-interval'),
maxIntervalInput: document.getElementById('max-interval'),
maxCountInput: document.getElementById('max-count'),
confirmPopup: document.getElementById('coordinate-confirm-popup')
};
// 功能按钮点击事件
elements.controlBtn.addEventListener('click', handleControlBtnClick);
// 页面点击事件(获取点赞坐标)
document.addEventListener('click', handlePageClick);
// 确认弹窗-确认按钮事件
elements.confirmBtn.addEventListener('click', handleConfirmBtnClick);
// 确认弹窗-取消按钮事件
elements.cancelBtn.addEventListener('click', handleCancelBtnClick);
// 最短间隔输入框变更事件
elements.minIntervalInput.addEventListener('change', handleMinIntervalChange);
// 最长间隔输入框变更事件
elements.maxIntervalInput.addEventListener('change', handleMaxIntervalChange);
// 最大点赞次数输入框变更事件
elements.maxCountInput.addEventListener('change', handleMaxCountChange);
}
/**
* 处理控制按钮点击事件
*/
function handleControlBtnClick() {
const { likeConfig } = globalState;
const controlBtn = document.getElementById('like-control-btn');
// 若正在点赞,停止所有流程
if (likeConfig.isLiking) {
stopAllProcess();
controlBtn.textContent = '选择点赞区域';
likeConfig.isLiking = false;
updateStatusDisplay();
showToast('已手动停止点赞');
return;
}
// 停止现有流程
stopAllProcess();
// 进入选区前倒计时状态
globalState.isPreSelectCountdown = true;
controlBtn.textContent = '准备选区中...';
controlBtn.disabled = true;
showToast('即将进入选区模式,倒计时3秒...');
updateStatusDisplay();
// 启动选区前倒计时
startPreSelectCountdown();
}
/**
* 处理页面点击事件(获取点赞坐标)
* @param {MouseEvent} e 鼠标事件对象
*/
function handlePageClick(e) {
if (!globalState.isSelectingArea) return;
e.preventDefault();
// 保存点击坐标
globalState.clickPosition.x = e.clientX;
globalState.clickPosition.y = e.clientY;
// 退出选区状态
globalState.isSelectingArea = false;
document.body.style.cursor = 'default';
// 高亮显示坐标
highlightCoordinate(globalState.clickPosition.x, globalState.clickPosition.y);
// 显示确认弹窗
document.getElementById('coordinate-confirm-popup').classList.remove('hidden');
// 恢复按钮状态
const controlBtn = document.getElementById('like-control-btn');
controlBtn.textContent = '选择点赞区域';
controlBtn.disabled = false;
}
/**
* 处理确认弹窗确认按钮点击事件
*/
function handleConfirmBtnClick() {
const { likeConfig } = globalState;
const confirmPopup = document.getElementById('coordinate-confirm-popup');
const controlBtn = document.getElementById('like-control-btn');
// 隐藏弹窗和高亮标记
confirmPopup.classList.add('hidden');
globalState.highlightMarker.classList.add('hidden');
// 更新状态
likeConfig.isLiking = true;
controlBtn.textContent = '停止点赞';
updateStatusDisplay();
showToast('3秒后将开始自动点赞,请稍候');
// 延迟显示倒计时
setTimeout(() => {
startAutoLikeCountdown();
}, 2000);
}
/**
* 处理确认弹窗取消按钮点击事件
*/
function handleCancelBtnClick() {
const confirmPopup = document.getElementById('coordinate-confirm-popup');
const controlBtn = document.getElementById('like-control-btn');
// 隐藏弹窗和高亮标记
confirmPopup.classList.add('hidden');
globalState.highlightMarker.classList.add('hidden');
// 恢复按钮状态
controlBtn.textContent = '选择点赞区域';
controlBtn.disabled = false;
updateStatusDisplay();
showToast('已取消点赞区域选择');
}
/**
* 处理最短间隔输入框变更事件
* @param {Event} e 输入事件对象
*/
function handleMinIntervalChange(e) {
const { likeConfig } = globalState;
const maxIntervalInput = document.getElementById('max-interval');
let value = parseFloat(e.target.value);
// 数据校验与修正
if (isNaN(value)) value = DEFAULT_LIKE_CONFIG.minInterval;
value = Math.max(0.1, Math.min(5, value));
// 更新配置
likeConfig.minInterval = value;
e.target.value = value;
// 同步修正最长间隔(确保最长≥最短)
if (likeConfig.maxInterval < likeConfig.minInterval) {
likeConfig.maxInterval = value;
maxIntervalInput.value = value;
}
// 正在点赞时重启定时器
if (likeConfig.isLiking && globalState.autoClickTimer) {
restartAutoLike();
}
}
/**
* 处理最长间隔输入框变更事件
* @param {Event} e 输入事件对象
*/
function handleMaxIntervalChange(e) {
const { likeConfig } = globalState;
let value = parseFloat(e.target.value);
// 数据校验与修正
if (isNaN(value)) value = DEFAULT_LIKE_CONFIG.maxInterval;
value = Math.max(likeConfig.minInterval, Math.min(5, value));
// 更新配置
likeConfig.maxInterval = value;
e.target.value = value;
// 正在点赞时重启定时器
if (likeConfig.isLiking && globalState.autoClickTimer) {
restartAutoLike();
}
}
/**
* 处理最大点赞次数输入框变更事件
* @param {Event} e 输入事件对象
*/
function handleMaxCountChange(e) {
const { likeConfig } = globalState;
let value = parseInt(e.target.value);
// 数据校验与修正
if (isNaN(value)) value = DEFAULT_LIKE_CONFIG.maxCount;
value = Math.max(1, Math.min(999, value));
// 更新配置
likeConfig.maxCount = value;
e.target.value = value;
updateStatusDisplay();
}
/**************************
* 5. 通用工具函数
**************************/
/**
* 显示Toast通知提示
* @param {string} message - 通知内容
*/
function showToast(message) {
// 移除现有通知
const existingToast = document.querySelector('.data-tool-notification');
if (existingToast) existingToast.remove();
// 创建新通知
const toast = document.createElement('div');
toast.className = 'data-tool-notification';
toast.textContent = message;
document.body.appendChild(toast);
// 自动消失逻辑
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
/**
* 高亮显示指定坐标
* @param {number} x 横坐标
* @param {number} y 纵坐标
*/
function highlightCoordinate(x, y) {
const { highlightMarker } = globalState;
highlightMarker.style.left = `${x}px`;
highlightMarker.style.top = `${y}px`;
highlightMarker.classList.remove('hidden');
}
/**
* 更新状态显示栏
*/
function updateStatusDisplay() {
const { likeConfig, groupCount } = globalState;
const statusDisplay = document.getElementById('like-status-display');
if (likeConfig.isLiking) {
statusDisplay.textContent = `点赞中 | 已完成${groupCount}/${likeConfig.maxCount}次`;
statusDisplay.style.color = '#0072ff';
} else if (groupCount >= likeConfig.maxCount && groupCount > 0) {
statusDisplay.textContent = `点赞结束 | 共完成${groupCount}/${likeConfig.maxCount}次`;
statusDisplay.style.color = '#4CAF50';
} else {
statusDisplay.textContent = '等待选择点赞区';
statusDisplay.style.color = '#333';
}
}
/**************************
* 6. 流程控制函数
**************************/
/**
* 停止所有正在执行的流程
*/
function stopAllProcess() {
const { countdownTimer, autoClickTimer, likeConfig } = globalState;
const controlBtn = document.getElementById('like-control-btn');
// 停止倒计时
if (countdownTimer) {
clearInterval(countdownTimer);
globalState.countdownTimer = null;
globalState.countdownNum = 3;
document.getElementById('countdown-box').classList.add('hidden');
}
// 停止自动连点
if (autoClickTimer) {
clearTimeout(autoClickTimer);
globalState.autoClickTimer = null;
}
// 重置状态变量
globalState.isPreSelectCountdown = false;
globalState.isSelectingArea = false;
likeConfig.isLiking = false;
globalState.groupCount = 0;
// 恢复页面样式和元素状态
document.body.style.cursor = 'default';
document.getElementById('coordinate-confirm-popup').classList.add('hidden');
globalState.highlightMarker.classList.add('hidden');
document.querySelector('.data-tool-notification')?.remove();
// 恢复按钮状态
if (controlBtn) {
controlBtn.textContent = '选择点赞区域';
controlBtn.disabled = false;
}
// 更新状态显示
updateStatusDisplay();
}
/**
* 重启自动点赞流程(配置变更后生效)
*/
function restartAutoLike() {
if (globalState.autoClickTimer) {
clearTimeout(globalState.autoClickTimer);
globalState.autoClickTimer = null;
}
startAutoDoubleClick();
}
/**
* 启动选区前3秒倒计时
*/
function startPreSelectCountdown() {
const countdownBox = document.getElementById('countdown-box');
globalState.countdownNum = 3;
// 显示倒计时
countdownBox.textContent = globalState.countdownNum;
countdownBox.classList.remove('hidden');
// 启动倒计时定时器
globalState.countdownTimer = setInterval(() => {
globalState.countdownNum--;
countdownBox.textContent = globalState.countdownNum;
if (globalState.countdownNum <= 0) {
clearInterval(globalState.countdownTimer);
globalState.countdownTimer = null;
countdownBox.classList.add('hidden');
globalState.isPreSelectCountdown = false;
enterSelectAreaMode();
}
}, 1000);
}
/**
* 启动连点前3秒倒计时
*/
function startAutoLikeCountdown() {
const countdownBox = document.getElementById('countdown-box');
globalState.countdownNum = 3;
// 显示倒计时
countdownBox.textContent = globalState.countdownNum;
countdownBox.classList.remove('hidden');
// 启动倒计时定时器
globalState.countdownTimer = setInterval(() => {
globalState.countdownNum--;
if (globalState.countdownNum <= 0) {
clearInterval(globalState.countdownTimer);
globalState.countdownTimer = null;
countdownBox.classList.add('hidden');
startAutoDoubleClick();
} else {
countdownBox.textContent = globalState.countdownNum;
}
}, 1000);
}
/**
* 进入选区模式
*/
function enterSelectAreaMode() {
globalState.isSelectingArea = true;
showToast('请点击你要设置的点赞区域');
document.body.style.cursor = 'crosshair';
updateStatusDisplay();
}
/**************************
* 7. 核心业务函数
**************************/
/**
* 模拟指定坐标的鼠标点击
* @param {number} x 横坐标
* @param {number} y 纵坐标
*/
function simulateClick(x, y) {
try {
const targetElement = document.elementFromPoint(x, y);
if (!targetElement) {
console.warn('[自动连点] 未获取到目标元素,跳过本次点击');
return;
}
// 创建并触发原生鼠标点击事件
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
button: 0 // 左键点击
});
targetElement.dispatchEvent(clickEvent);
} catch (error) {
console.error('[自动连点] 模拟点击失败:', error);
}
}
/**
* 启动自动双连点流程(递归实现动态随机间隔)
*/
function startAutoDoubleClick() {
const { likeConfig } = globalState;
// 重置当前组数
globalState.groupCount = 0;
/**
* 递归执行下一组连点
*/
function executeNextLikeGroup() {
const { clickPosition, groupCount } = globalState;
// 执行当前组双连点(组内间隔10毫秒)
simulateClick(clickPosition.x, clickPosition.y);
setTimeout(() => {
simulateClick(clickPosition.x, clickPosition.y);
}, 10);
// 更新组数和状态
globalState.groupCount++;
updateStatusDisplay();
console.log(`[自动连点] 已完成第${globalState.groupCount}组双连点`);
// 判断是否继续执行
if (globalState.groupCount < likeConfig.maxCount) {
// 计算随机间隔(秒转毫秒)
const nextRandomInterval = Math.random() *
(likeConfig.maxInterval - likeConfig.minInterval) * 1000 +
likeConfig.minInterval * 1000;
// 递归设置下一组定时器
globalState.autoClickTimer = setTimeout(executeNextLikeGroup, nextRandomInterval);
} else {
// 完成所有点赞,恢复状态
globalState.autoClickTimer = null;
likeConfig.isLiking = false;
const controlBtn = document.getElementById('like-control-btn');
if (controlBtn) controlBtn.textContent = '选择点赞区域';
showToast(`已完成${likeConfig.maxCount}组双连点,点击结束!`);
updateStatusDisplay();
}
}
// 启动第一组连点
executeNextLikeGroup();
}
/**************************
* 8. 初始化入口
**************************/
function initScript() {
// 注入样式
injectStyles();
// 等待页面完全渲染后创建DOM
setTimeout(() => {
createDOMElements();
console.log('[指定区域自动双连点] 脚本初始化完成,小面板已就绪');
}, 1500);
}
// 启动脚本
initScript();
})();